/*
 * Copyright (c) 2018 IOTA Stiftung
 * https://github.com/iotaledger/iota_common
 *
 * Refer to the LICENSE file for licensing information
 */

#include <assert.h>
#include <string.h>

#include "common/trinary/trit_byte.h"
#include "utils/macros.h"

// Since the LUT can be quite heavy for little devices, it is possible to
// disable it and recompute values at runtime
#ifndef NO_BYTES_TRITS_LUT

// Values are ordered like this: 0, 1, ... 120, 121, -121, -120, ... -2, -1
static trit_t const BYTES_TRITS_LUT[BYTE_SPACE_SIZE][NUMBER_OF_TRITS_IN_A_BYTE] = {
    {0, 0, 0, 0, 0},     {1, 0, 0, 0, 0},     {-1, 1, 0, 0, 0},     {0, 1, 0, 0, 0},     {1, 1, 0, 0, 0},
    {-1, -1, 1, 0, 0},   {0, -1, 1, 0, 0},    {1, -1, 1, 0, 0},     {-1, 0, 1, 0, 0},    {0, 0, 1, 0, 0},
    {1, 0, 1, 0, 0},     {-1, 1, 1, 0, 0},    {0, 1, 1, 0, 0},      {1, 1, 1, 0, 0},     {-1, -1, -1, 1, 0},
    {0, -1, -1, 1, 0},   {1, -1, -1, 1, 0},   {-1, 0, -1, 1, 0},    {0, 0, -1, 1, 0},    {1, 0, -1, 1, 0},
    {-1, 1, -1, 1, 0},   {0, 1, -1, 1, 0},    {1, 1, -1, 1, 0},     {-1, -1, 0, 1, 0},   {0, -1, 0, 1, 0},
    {1, -1, 0, 1, 0},    {-1, 0, 0, 1, 0},    {0, 0, 0, 1, 0},      {1, 0, 0, 1, 0},     {-1, 1, 0, 1, 0},
    {0, 1, 0, 1, 0},     {1, 1, 0, 1, 0},     {-1, -1, 1, 1, 0},    {0, -1, 1, 1, 0},    {1, -1, 1, 1, 0},
    {-1, 0, 1, 1, 0},    {0, 0, 1, 1, 0},     {1, 0, 1, 1, 0},      {-1, 1, 1, 1, 0},    {0, 1, 1, 1, 0},
    {1, 1, 1, 1, 0},     {-1, -1, -1, -1, 1}, {0, -1, -1, -1, 1},   {1, -1, -1, -1, 1},  {-1, 0, -1, -1, 1},
    {0, 0, -1, -1, 1},   {1, 0, -1, -1, 1},   {-1, 1, -1, -1, 1},   {0, 1, -1, -1, 1},   {1, 1, -1, -1, 1},
    {-1, -1, 0, -1, 1},  {0, -1, 0, -1, 1},   {1, -1, 0, -1, 1},    {-1, 0, 0, -1, 1},   {0, 0, 0, -1, 1},
    {1, 0, 0, -1, 1},    {-1, 1, 0, -1, 1},   {0, 1, 0, -1, 1},     {1, 1, 0, -1, 1},    {-1, -1, 1, -1, 1},
    {0, -1, 1, -1, 1},   {1, -1, 1, -1, 1},   {-1, 0, 1, -1, 1},    {0, 0, 1, -1, 1},    {1, 0, 1, -1, 1},
    {-1, 1, 1, -1, 1},   {0, 1, 1, -1, 1},    {1, 1, 1, -1, 1},     {-1, -1, -1, 0, 1},  {0, -1, -1, 0, 1},
    {1, -1, -1, 0, 1},   {-1, 0, -1, 0, 1},   {0, 0, -1, 0, 1},     {1, 0, -1, 0, 1},    {-1, 1, -1, 0, 1},
    {0, 1, -1, 0, 1},    {1, 1, -1, 0, 1},    {-1, -1, 0, 0, 1},    {0, -1, 0, 0, 1},    {1, -1, 0, 0, 1},
    {-1, 0, 0, 0, 1},    {0, 0, 0, 0, 1},     {1, 0, 0, 0, 1},      {-1, 1, 0, 0, 1},    {0, 1, 0, 0, 1},
    {1, 1, 0, 0, 1},     {-1, -1, 1, 0, 1},   {0, -1, 1, 0, 1},     {1, -1, 1, 0, 1},    {-1, 0, 1, 0, 1},
    {0, 0, 1, 0, 1},     {1, 0, 1, 0, 1},     {-1, 1, 1, 0, 1},     {0, 1, 1, 0, 1},     {1, 1, 1, 0, 1},
    {-1, -1, -1, 1, 1},  {0, -1, -1, 1, 1},   {1, -1, -1, 1, 1},    {-1, 0, -1, 1, 1},   {0, 0, -1, 1, 1},
    {1, 0, -1, 1, 1},    {-1, 1, -1, 1, 1},   {0, 1, -1, 1, 1},     {1, 1, -1, 1, 1},    {-1, -1, 0, 1, 1},
    {0, -1, 0, 1, 1},    {1, -1, 0, 1, 1},    {-1, 0, 0, 1, 1},     {0, 0, 0, 1, 1},     {1, 0, 0, 1, 1},
    {-1, 1, 0, 1, 1},    {0, 1, 0, 1, 1},     {1, 1, 0, 1, 1},      {-1, -1, 1, 1, 1},   {0, -1, 1, 1, 1},
    {1, -1, 1, 1, 1},    {-1, 0, 1, 1, 1},    {0, 0, 1, 1, 1},      {1, 0, 1, 1, 1},     {-1, 1, 1, 1, 1},
    {0, 1, 1, 1, 1},     {1, 1, 1, 1, 1},     {-1, -1, -1, -1, -1}, {0, -1, -1, -1, -1}, {1, -1, -1, -1, -1},
    {-1, 0, -1, -1, -1}, {0, 0, -1, -1, -1},  {1, 0, -1, -1, -1},   {-1, 1, -1, -1, -1}, {0, 1, -1, -1, -1},
    {1, 1, -1, -1, -1},  {-1, -1, 0, -1, -1}, {0, -1, 0, -1, -1},   {1, -1, 0, -1, -1},  {-1, 0, 0, -1, -1},
    {0, 0, 0, -1, -1},   {1, 0, 0, -1, -1},   {-1, 1, 0, -1, -1},   {0, 1, 0, -1, -1},   {1, 1, 0, -1, -1},
    {-1, -1, 1, -1, -1}, {0, -1, 1, -1, -1},  {1, -1, 1, -1, -1},   {-1, 0, 1, -1, -1},  {0, 0, 1, -1, -1},
    {1, 0, 1, -1, -1},   {-1, 1, 1, -1, -1},  {0, 1, 1, -1, -1},    {1, 1, 1, -1, -1},   {-1, -1, -1, 0, -1},
    {0, -1, -1, 0, -1},  {1, -1, -1, 0, -1},  {-1, 0, -1, 0, -1},   {0, 0, -1, 0, -1},   {1, 0, -1, 0, -1},
    {-1, 1, -1, 0, -1},  {0, 1, -1, 0, -1},   {1, 1, -1, 0, -1},    {-1, -1, 0, 0, -1},  {0, -1, 0, 0, -1},
    {1, -1, 0, 0, -1},   {-1, 0, 0, 0, -1},   {0, 0, 0, 0, -1},     {1, 0, 0, 0, -1},    {-1, 1, 0, 0, -1},
    {0, 1, 0, 0, -1},    {1, 1, 0, 0, -1},    {-1, -1, 1, 0, -1},   {0, -1, 1, 0, -1},   {1, -1, 1, 0, -1},
    {-1, 0, 1, 0, -1},   {0, 0, 1, 0, -1},    {1, 0, 1, 0, -1},     {-1, 1, 1, 0, -1},   {0, 1, 1, 0, -1},
    {1, 1, 1, 0, -1},    {-1, -1, -1, 1, -1}, {0, -1, -1, 1, -1},   {1, -1, -1, 1, -1},  {-1, 0, -1, 1, -1},
    {0, 0, -1, 1, -1},   {1, 0, -1, 1, -1},   {-1, 1, -1, 1, -1},   {0, 1, -1, 1, -1},   {1, 1, -1, 1, -1},
    {-1, -1, 0, 1, -1},  {0, -1, 0, 1, -1},   {1, -1, 0, 1, -1},    {-1, 0, 0, 1, -1},   {0, 0, 0, 1, -1},
    {1, 0, 0, 1, -1},    {-1, 1, 0, 1, -1},   {0, 1, 0, 1, -1},     {1, 1, 0, 1, -1},    {-1, -1, 1, 1, -1},
    {0, -1, 1, 1, -1},   {1, -1, 1, 1, -1},   {-1, 0, 1, 1, -1},    {0, 0, 1, 1, -1},    {1, 0, 1, 1, -1},
    {-1, 1, 1, 1, -1},   {0, 1, 1, 1, -1},    {1, 1, 1, 1, -1},     {-1, -1, -1, -1, 0}, {0, -1, -1, -1, 0},
    {1, -1, -1, -1, 0},  {-1, 0, -1, -1, 0},  {0, 0, -1, -1, 0},    {1, 0, -1, -1, 0},   {-1, 1, -1, -1, 0},
    {0, 1, -1, -1, 0},   {1, 1, -1, -1, 0},   {-1, -1, 0, -1, 0},   {0, -1, 0, -1, 0},   {1, -1, 0, -1, 0},
    {-1, 0, 0, -1, 0},   {0, 0, 0, -1, 0},    {1, 0, 0, -1, 0},     {-1, 1, 0, -1, 0},   {0, 1, 0, -1, 0},
    {1, 1, 0, -1, 0},    {-1, -1, 1, -1, 0},  {0, -1, 1, -1, 0},    {1, -1, 1, -1, 0},   {-1, 0, 1, -1, 0},
    {0, 0, 1, -1, 0},    {1, 0, 1, -1, 0},    {-1, 1, 1, -1, 0},    {0, 1, 1, -1, 0},    {1, 1, 1, -1, 0},
    {-1, -1, -1, 0, 0},  {0, -1, -1, 0, 0},   {1, -1, -1, 0, 0},    {-1, 0, -1, 0, 0},   {0, 0, -1, 0, 0},
    {1, 0, -1, 0, 0},    {-1, 1, -1, 0, 0},   {0, 1, -1, 0, 0},     {1, 1, -1, 0, 0},    {-1, -1, 0, 0, 0},
    {0, -1, 0, 0, 0},    {1, -1, 0, 0, 0},    {-1, 0, 0, 0, 0}};

#else

static const byte_t byte_radix[] = {1, 3, 9, 27, 81};

#endif  // NO_BYTES_TRITS_LUT

byte_t trits_to_byte(trit_t const *const trits, size_t const num_trits) {
  byte_t byte = 0;

  assert(num_trits <= NUMBER_OF_TRITS_IN_A_BYTE);
  if (num_trits == 0) {
    return 0;
  }

  for (int i = num_trits - 1; i >= 0; i--) {
    byte = byte * RADIX + trits[i];
  }

  return byte;
}

void trits_to_bytes(trit_t const *const trits, byte_t *const bytes, size_t const num_trits) {
  if (num_trits == 0) {
    return;
  }

  for (size_t i = 0, j = 0; i < num_trits; i += NUMBER_OF_TRITS_IN_A_BYTE, j++) {
    bytes[j] = trits_to_byte(trits + i, MIN(num_trits - i, NUMBER_OF_TRITS_IN_A_BYTE));
  }
}

void byte_to_trits(byte_t const byte, trit_t *const trits, size_t const num_trits) {
  assert(num_trits <= NUMBER_OF_TRITS_IN_A_BYTE);
  if (num_trits == 0) {
    return;
  }

#ifndef NO_BYTES_TRITS_LUT
  memcpy(trits, BYTES_TRITS_LUT[byte < 0 ? BYTE_SPACE_SIZE + byte : byte], num_trits);
#else
  size_t j = NUMBER_OF_TRITS_IN_A_BYTE - 1;
  size_t i = num_trits - 1;
  byte_t value = byte;

  while (j < -1) {
    trit_t trit = 0;
    if (value > (byte_radix[j] >> 1)) {
      value -= byte_radix[j];
      trit = 1;
    } else if (value < -(byte_radix[j] >> 1)) {
      value += byte_radix[j];
      trit = -1;
    }
    if (j == i) {
      trits[i] = trit;
      i -= 1;
    }
    j -= 1;
  }
#endif  // NO_BYTES_TRITS_LUT
}

void bytes_to_trits(byte_t const *const bytes, size_t const num_bytes, trit_t *const trits, size_t const num_trits) {
  assert(num_trits <= NUMBER_OF_TRITS_IN_A_BYTE * num_bytes);
  if (num_bytes == 0 || num_trits == 0) {
    return;
  }

  for (size_t i = 0, j = 0; i < num_trits && j < num_bytes; i += NUMBER_OF_TRITS_IN_A_BYTE, j++) {
    byte_to_trits(bytes[j], &trits[i], MIN(num_trits - i, NUMBER_OF_TRITS_IN_A_BYTE));
  }
}
