/*
 * Copyright 2013-2019 Software Radio Systems Limited
 *
 * This file is part of srsLTE.
 *
 * srsLTE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * srsLTE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * A copy of the GNU Affero General Public License can be found in
 * the LICENSE file in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/.
 *
 */

#include "srslte/asn1/asn1_utils.h"
#include <cmath>
#include <stdio.h>

namespace asn1 {

/************************
        logging
************************/

void vlog_print(log_handler_t handler, void* ctx, srsasn_logger_level_t log_level, const char* format, va_list args)
{
  if (handler) {
    char* args_msg = NULL;
    if (vasprintf(&args_msg, format, args) > 0) {
      handler(log_level, ctx, args_msg);
    }
    if (args_msg) {
      free(args_msg);
    }
  } else {
    vprintf(format, args);
  }
}

static log_handler_t log_handler;
static void*         callback_ctx = NULL;

void srsasn_log_register_handler(void* ctx, log_handler_t handler)
{
  log_handler  = handler;
  callback_ctx = ctx;
}

void srsasn_log_print(srsasn_logger_level_t log_level, const char* format, ...)
{
  va_list args;
  va_start(args, format);
  vlog_print(log_handler, callback_ctx, log_level, format, args);
  va_end(args);
}

/************************
     error handling
************************/

void log_error_code(SRSASN_CODE code, const char* filename, int line)
{
  switch (code) {
    case SRSASN_ERROR_ENCODE_FAIL:
      srsasn_log_print(LOG_LEVEL_ERROR, "[%s][%d] Encoding failure.\n", filename, line);
      break;
    case SRSASN_ERROR_DECODE_FAIL:
      srsasn_log_print(LOG_LEVEL_ERROR, "[%s][%d] Decoding failure.\n", filename, line);
      break;
    default:
      srsasn_log_print(LOG_LEVEL_WARN, "[%s][%d] SRSASN_CODE=%d not recognized.\n", filename, line, (uint32_t)code);
  }
}

/*********************
       bit_ref
*********************/

bit_ref::bit_ref() : ptr(NULL), offset(0), start_ptr(NULL), max_ptr(NULL) {}

bit_ref::bit_ref(uint8_t* start_ptr_, uint32_t max_size_) :
  ptr(start_ptr_),
  offset(0),
  start_ptr(start_ptr_),
  max_ptr(max_size_ + start_ptr_)
{
}

int bit_ref::distance(const bit_ref& other) const
{
  return ((int)offset - (int)other.offset) + 8 * ((int)(ptr - other.ptr));
}
int bit_ref::distance(uint8_t* ref_ptr) const
{
  return (int)offset + 8 * ((int)(ptr - ref_ptr));
}
int bit_ref::distance() const
{
  return (int)offset + 8 * ((int)(ptr - start_ptr));
}
int bit_ref::distance_bytes(uint8_t* ref_ptr) const
{
  return ((int)(ptr - ref_ptr)) + ((offset) ? 1 : 0);
}
int bit_ref::distance_bytes() const
{
  return ((int)(ptr - start_ptr)) + ((offset) ? 1 : 0);
}

SRSASN_CODE bit_ref::pack(uint32_t val, uint32_t n_bits)
{
  uint32_t mask;
  while (n_bits > 0) {
    if (ptr >= max_ptr) {
      srsasn_log_print(LOG_LEVEL_ERROR, "Buffer size limit was achieved\n");
      return SRSASN_ERROR_ENCODE_FAIL;
    }
    mask             = ((1u << n_bits) - 1u);
    val              = val & mask;
    uint8_t keepmask = ((uint8_t)-1) - (uint8_t)((1 << (8 - offset)) - 1);
    if ((uint32_t)(8 - offset) > n_bits) {
      uint8_t bit = (uint8_t)(val << (8u - offset - n_bits));
      *ptr        = ((*ptr) & keepmask) + bit;
      offset += n_bits;
      n_bits = 0;
    } else {
      uint8_t bit = (uint8_t)(val >> (n_bits - 8u + offset));
      *ptr        = (*ptr & keepmask) + bit;
      n_bits -= (8 - offset);
      offset = 0;
      ptr++;
    }
  }
  return SRSASN_SUCCESS;
}

ValOrError unpack_bits(uint8_t*& ptr, uint8_t& offset, uint8_t* max_ptr, uint32_t n_bits)
{
  uint32_t val = 0;
  while (n_bits > 0) {
    if (ptr >= max_ptr) {
      srsasn_log_print(LOG_LEVEL_ERROR, "Buffer size limit was achieved\n");
      return ValOrError(val, SRSASN_ERROR_DECODE_FAIL);
    }
    if ((uint32_t)(8 - offset) > n_bits) {
      uint8_t mask = (uint8_t)(1u << (8u - offset)) - (uint8_t)(1u << (8u - offset - n_bits));
      val += ((uint32_t)((*ptr) & mask)) >> ((uint8_t)8 - offset - n_bits);
      offset += n_bits;
      n_bits = 0;
    } else {
      uint8_t mask = (uint8_t)((1u << (8u - offset)) - 1);
      val += ((uint32_t)((*ptr) & mask)) << (n_bits - 8 + offset);
      n_bits -= 8 - offset;
      offset = 0;
      ptr++;
    }
  }
  return ValOrError(val, SRSASN_SUCCESS);
}

SRSASN_CODE bit_ref::align_bytes()
{
  if (offset == 0)
    return SRSASN_SUCCESS;
  if (ptr >= max_ptr) {
    srsasn_log_print(LOG_LEVEL_ERROR, "Buffer size limit was achieved\n");
    return SRSASN_ERROR_DECODE_FAIL;
  }
  offset = 0;
  ptr++;
  return SRSASN_SUCCESS;
}

SRSASN_CODE bit_ref::align_bytes_zero()
{
  if (offset == 0)
    return SRSASN_SUCCESS;
  if (ptr >= max_ptr) {
    srsasn_log_print(LOG_LEVEL_ERROR, "Buffer size limit was achieved\n");
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  uint8_t mask = (uint8_t)(256u - (1u << (8u - offset)));
  *ptr &= mask;
  offset = 0;
  ptr++;
  return SRSASN_SUCCESS;
}

void bit_ref::set(uint8_t* start_ptr_, uint32_t max_size_)
{
  ptr       = start_ptr_;
  offset    = 0;
  start_ptr = start_ptr_;
  max_ptr   = max_size_ + start_ptr_;
}

/*********************
     ext packing
*********************/

SRSASN_CODE pack_unsupported_ext_flag(bit_ref& bref, bool ext)
{
  HANDLE_CODE(bref.pack(ext, 1));
  if (ext) {
    srsasn_log_print(LOG_LEVEL_ERROR, "asn1 error: ASN extensions not currently supported\n");
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE unpack_unsupported_ext_flag(bool& ext, bit_ref& bref)
{
  SRSASN_CODE ret = bref.unpack(ext, 1);
  if (ext) {
    srsasn_log_print(LOG_LEVEL_ERROR, "asn1 error: ASN extensions not currently supported\n");
    return SRSASN_ERROR_DECODE_FAIL;
  }
  return ret;
}

/************************
      enum packing
************************/

// Pack without extension
SRSASN_CODE pack_enum(bit_ref& bref, uint32_t enum_val, uint32_t nbits)
{
  return bref.pack(enum_val, nbits);
}

// Pack with extension
SRSASN_CODE pack_enum(bit_ref& bref, uint32_t enum_val, uint32_t nbits, uint32_t nof_noext)
{
  bool ext = enum_val >= nof_noext;
  HANDLE_CODE(bref.pack((bool)ext, 1));
  SRSASN_CODE ret;
  if (not ext) {
    ret = bref.pack(enum_val, nbits);
  } else {
    ret = pack_norm_small_integer(bref, enum_val - nof_noext);
  }
  return ret;
}

SRSASN_CODE pack_enum(bit_ref& bref, uint32_t e, uint32_t nof_types, uint32_t nof_exts, bool has_ext)
{
  if (e >= nof_types) {
    srsasn_log_print(LOG_LEVEL_ERROR, "The provided enum is not within the range of possible values (%d>=%d)\n", e,
                     nof_types);
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  SRSASN_CODE ret;
  if (has_ext) {
    uint32_t nof_bits = (uint32_t)ceilf(log2f(nof_types - nof_exts));
    ret               = pack_enum(bref, e, nof_bits, nof_types - nof_exts);
  } else {
    uint32_t nof_bits = (uint32_t)ceilf(log2f(nof_types));
    ret               = pack_enum(bref, e, nof_bits);
  }
  return ret;
}

ValOrError unpack_enum(uint32_t nof_types, uint32_t nof_exts, bool has_ext, bit_ref& bref)
{
  ValOrError ret;
  if (has_ext) {
    uint32_t nof_bits = (uint32_t)ceilf(log2f(nof_types - nof_exts));
    bool     ext;
    ret.code = bref.unpack(ext, 1);
    if (ret.code != SRSASN_SUCCESS) {
      return ret;
    }
    if (not ext) {
      ret.code = bref.unpack(ret.val, nof_bits);
    } else {
      ret.code = unpack_norm_small_integer(ret.val, bref);
      ret.val += nof_types - nof_exts;
    }
  } else {
    uint32_t nof_bits = (uint32_t)ceilf(log2f(nof_types));
    ret.code          = bref.unpack(ret.val, nof_bits);
  }
  if (ret.val >= nof_types) {
    srsasn_log_print(LOG_LEVEL_ERROR, "The provided enum is not within the range of possible values (%d>=%d)\n",
                     ret.val, nof_types);
    ret.code = SRSASN_ERROR_DECODE_FAIL;
  }
  return ret;
}

/************************
       pack int
************************/

/**
 * Encoder function for a constrained whole unaligned number
 * @tparam IntType type of integer (e.g. uint8_t, uint32_t, etc.)
 * @param bref bit_ref where to encode integer
 * @param n integer current value
 * @param lb
 * @param ub
 * @return success or failure
 */
template <class IntType>
SRSASN_CODE pack_unalign_integer(bit_ref& bref, IntType n, IntType lb, IntType ub)
{
  if (ub < lb or n < lb or n > ub) {
    srsasn_log_print(LOG_LEVEL_ERROR, "The condition lb <= n <= ub (%d <= %d <= %d) was not met\n", lb, n, ub);
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  uint64_t ra = ub - lb + 1; // NOTE: Can overflow if IntType is kept
  if (ra != 1) {
    uint32_t n_bits   = (uint32_t)ceilf(log2f((float)ra));
    IntType  toencode = n - lb;
    bref.pack(toencode, n_bits);
  }
  return SRSASN_SUCCESS;
}
/**
 * Encoder function for a constrained whole unaligned number
 * @tparam IntType type of integer (e.g. uint8_t, uint32_t, etc.)
 * @param bref bit_ref where to encode integer
 * @param n integer current value
 * @param lb
 * @param ub
 * @return success or failure
 */
template <class IntType>
SRSASN_CODE unpack_unalign_integer(IntType& n, bit_ref& bref, IntType lb, IntType ub)
{
  if (ub < lb) {
    srsasn_log_print(LOG_LEVEL_ERROR, "The condition lb <= ub (%d <= %d) was not met\n", lb, ub);
    return SRSASN_ERROR_DECODE_FAIL;
  }
  uint64_t ra = ub - lb + 1; // NOTE: Can overflow if IntType is kept.
  if (ra == 1) {
    n = lb;
    return SRSASN_SUCCESS;
  }
  uint32_t    n_bits = (uint32_t)ceilf(log2f(ra));
  SRSASN_CODE ret    = bref.unpack(n, n_bits);
  n += lb;
  if (n < lb or n > ub) {
    srsasn_log_print(LOG_LEVEL_ERROR, "The condition lb <= n <= ub (%d <= %d <= %d) was not met\n", lb, n, ub);
    return SRSASN_ERROR_DECODE_FAIL;
  }
  return ret;
}
template SRSASN_CODE pack_unalign_integer<int8_t>(bit_ref& bref, int8_t n, int8_t lb, int8_t ub);
template SRSASN_CODE pack_unalign_integer<int16_t>(bit_ref& bref, int16_t n, int16_t lb, int16_t ub);
template SRSASN_CODE pack_unalign_integer<int32_t>(bit_ref& bref, int32_t n, int32_t lb, int32_t ub);
template SRSASN_CODE pack_unalign_integer<int64_t>(bit_ref& bref, int64_t n, int64_t lb, int64_t ub);
template SRSASN_CODE pack_unalign_integer<uint8_t>(bit_ref& bref, uint8_t n, uint8_t lb, uint8_t ub);
template SRSASN_CODE pack_unalign_integer<uint16_t>(bit_ref& bref, uint16_t n, uint16_t lb, uint16_t ub);
template SRSASN_CODE pack_unalign_integer<uint32_t>(bit_ref& bref, uint32_t n, uint32_t lb, uint32_t ub);
template SRSASN_CODE pack_unalign_integer<uint64_t>(bit_ref& bref, uint64_t n, uint64_t lb, uint64_t ub);
template SRSASN_CODE unpack_unalign_integer<int8_t>(int8_t& n, bit_ref& bref, int8_t lb, int8_t ub);
template SRSASN_CODE unpack_unalign_integer<int16_t>(int16_t& n, bit_ref& bref, int16_t lb, int16_t ub);
template SRSASN_CODE unpack_unalign_integer<int32_t>(int32_t& n, bit_ref& bref, int32_t lb, int32_t ub);
template SRSASN_CODE unpack_unalign_integer<int64_t>(int64_t& n, bit_ref& bref, int64_t lb, int64_t ub);
template SRSASN_CODE unpack_unalign_integer<uint8_t>(uint8_t& n, bit_ref& bref, uint8_t lb, uint8_t ub);
template SRSASN_CODE unpack_unalign_integer<uint16_t>(uint16_t& n, bit_ref& bref, uint16_t lb, uint16_t ub);
template SRSASN_CODE unpack_unalign_integer<uint32_t>(uint32_t& n, bit_ref& bref, uint32_t lb, uint32_t ub);
template SRSASN_CODE unpack_unalign_integer<uint64_t>(uint64_t& n, bit_ref& bref, uint64_t lb, uint64_t ub);

template <class IntType>
UnalignedIntegerPacker<IntType>::UnalignedIntegerPacker(IntType lb_, IntType ub_) : lb(lb_), ub(ub_)
{
}
template <class IntType>
SRSASN_CODE UnalignedIntegerPacker<IntType>::pack(bit_ref& bref, IntType n) const
{
  return pack_unalign_integer(bref, n, lb, ub);
}
template <class IntType>
SRSASN_CODE UnalignedIntegerPacker<IntType>::unpack(IntType& n, bit_ref& bref) const
{
  return unpack_unalign_integer(n, bref, lb, ub);
}
template struct UnalignedIntegerPacker<int8_t>;
template struct UnalignedIntegerPacker<int16_t>;
template struct UnalignedIntegerPacker<int32_t>;
template struct UnalignedIntegerPacker<int64_t>;
template struct UnalignedIntegerPacker<uint8_t>;
template struct UnalignedIntegerPacker<uint16_t>;
template struct UnalignedIntegerPacker<uint32_t>;
template struct UnalignedIntegerPacker<uint64_t>;

/**
 * Encoder of a constrained whole aligned number
 * @tparam IntType type of integer (e.g. uint8_t, uint32_t, etc.)
 * @param bref bit_ref where to encode integer
 * @param n value of integer to pack
 * @param lb
 * @param ub
 * @return
 */
template <class IntType>
SRSASN_CODE pack_align_integer(bit_ref& bref, IntType n, IntType lb, IntType ub)
{
  // FIXME: Bound checker.
  IntType ra = ub - lb + 1;
  if (ra == 1) {
    return SRSASN_SUCCESS;
  }
  uint32_t    n_bits   = (uint32_t)ceilf(log2f(ra)); // bit-field size
  IntType     toencode = n - lb;
  SRSASN_CODE ret;
  if (ra < 256) {
    ret = bref.pack(toencode, n_bits);
  } else if (ra <= 65536) {
    uint32_t n_octets = (n_bits + 7) / 8;
    HANDLE_CODE(bref.align_bytes_zero());
    HANDLE_CODE(bref.pack(0, n_octets * 8 - n_bits));
    HANDLE_CODE(bref.pack(toencode, n_bits));
    ret = bref.align_bytes_zero();
  } else {
    // TODO: Check if this is correct
    uint32_t n_bits_len = (uint32_t)ceilf(log2f((n_bits + 7) / 8));
    n_bits              = (uint32_t)floorf(log2f(toencode) + 1);
    uint32_t n_octets   = (uint32_t)((n_bits + 7) / 8);
    HANDLE_CODE(bref.pack(n_octets - 1, n_bits_len));
    HANDLE_CODE(bref.align_bytes_zero());
    HANDLE_CODE(bref.pack(0, (n_octets * 8) - n_bits));
    ret = bref.pack(toencode, n_bits);
  }
  return ret;
}
template SRSASN_CODE pack_align_integer<uint8_t>(bit_ref& bref, uint8_t n, uint8_t lb, uint8_t ub);
template SRSASN_CODE pack_align_integer<uint16_t>(bit_ref& bref, uint16_t n, uint16_t lb, uint16_t ub);
template SRSASN_CODE pack_align_integer<uint32_t>(bit_ref& bref, uint32_t n, uint32_t lb, uint32_t ub);
template SRSASN_CODE pack_align_integer<uint64_t>(bit_ref& bref, uint64_t n, uint64_t lb, uint64_t ub);
template SRSASN_CODE pack_align_integer<int8_t>(bit_ref& bref, int8_t n, int8_t lb, int8_t ub);
template SRSASN_CODE pack_align_integer<int16_t>(bit_ref& bref, int16_t n, int16_t lb, int16_t ub);
template SRSASN_CODE pack_align_integer<int32_t>(bit_ref& bref, int32_t n, int32_t lb, int32_t ub);
template SRSASN_CODE pack_align_integer<int64_t>(bit_ref& bref, int64_t n, int64_t lb, int64_t ub);

template <typename IntType>
SRSASN_CODE unpack_align_integer(IntType& n, bit_ref& bref, IntType lb, IntType ub)
{
  uint32_t ra = ub - lb + 1;
  if (ra == 1) {
    n = lb;
    return SRSASN_SUCCESS;
  }
  SRSASN_CODE ret    = SRSASN_SUCCESS;
  uint32_t    n_bits = (uint32_t)ceil(log2(ra));
  if (ra < 256) {
    ret = bref.unpack(n, n_bits);
  } else if (ra < 65536) {
    uint32_t n_octets = (n_bits + 7) / 8;
    HANDLE_CODE(bref.align_bytes());
    HANDLE_CODE(bref.unpack(n, n_octets * 8));
    HANDLE_CODE(bref.align_bytes());
  } else {
    uint32_t n_bits_len = (uint32_t)ceilf(log2f((n_bits + 7) / 8));
    uint32_t n_octets;
    HANDLE_CODE(bref.unpack(n_octets, n_bits_len));
    n_octets += 1;
    HANDLE_CODE(bref.align_bytes());
    HANDLE_CODE(bref.unpack(n, n_octets * 8));
    n += lb;
  }
  n += lb;
  return ret;
}
template SRSASN_CODE unpack_align_integer<uint8_t>(uint8_t& intval, bit_ref& bref, uint8_t lb, uint8_t ub);
template SRSASN_CODE unpack_align_integer<uint16_t>(uint16_t& intval, bit_ref& bref, uint16_t lb, uint16_t ub);
template SRSASN_CODE unpack_align_integer<uint32_t>(uint32_t& intval, bit_ref& bref, uint32_t lb, uint32_t ub);
template SRSASN_CODE unpack_align_integer<uint64_t>(uint64_t& intval, bit_ref& bref, uint64_t lb, uint64_t ub);
template SRSASN_CODE unpack_align_integer<int8_t>(int8_t& intval, bit_ref& bref, int8_t lb, int8_t ub);
template SRSASN_CODE unpack_align_integer<int16_t>(int16_t& intval, bit_ref& bref, int16_t lb, int16_t ub);
template SRSASN_CODE unpack_align_integer<int32_t>(int32_t& intval, bit_ref& bref, int32_t lb, int32_t ub);
template SRSASN_CODE unpack_align_integer<int64_t>(int64_t& intval, bit_ref& bref, int64_t lb, int64_t ub);

template <class IntType>
AlignedIntegerPacker<IntType>::AlignedIntegerPacker(IntType lb_, IntType ub_) : lb(lb_), ub(ub_)
{
}
template <class IntType>
SRSASN_CODE AlignedIntegerPacker<IntType>::pack(bit_ref& bref, IntType n)
{
  return pack_align_integer(bref, n, lb, ub);
}
template <class IntType>
SRSASN_CODE AlignedIntegerPacker<IntType>::unpack(IntType& n, bit_ref& bref)
{
  return unpack_align_integer(n, bref, lb, ub);
}
template struct AlignedIntegerPacker<int8_t>;
template struct AlignedIntegerPacker<int16_t>;
template struct AlignedIntegerPacker<int32_t>;
template struct AlignedIntegerPacker<int64_t>;
template struct AlignedIntegerPacker<uint8_t>;
template struct AlignedIntegerPacker<uint16_t>;
template struct AlignedIntegerPacker<uint32_t>;
template struct AlignedIntegerPacker<uint64_t>;

template <typename UintType>
SRSASN_CODE pack_norm_small_integer(bit_ref& bref, UintType n)
{
  if (n <= 63) {
    HANDLE_CODE(bref.pack(0, 1));
    HANDLE_CODE(bref.pack(n, 6));
  } else {
    HANDLE_CODE(bref.pack(1, 1));
    srsasn_log_print(LOG_LEVEL_ERROR, "Long small integers not supported\n");
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  return SRSASN_SUCCESS;
}
template <typename UintType>
SRSASN_CODE unpack_norm_small_integer(UintType& n, bit_ref& bref)
{
  bool        ext;
  SRSASN_CODE ret = bref.unpack(ext, 1);
  HANDLE_CODE(ret);
  if (not ext) {
    ret = bref.unpack(n, 6);
  } else {
    srsasn_log_print(LOG_LEVEL_ERROR, "Long small integers not supported\n");
    return SRSASN_ERROR_DECODE_FAIL;
  }
  return ret;
}
template SRSASN_CODE pack_norm_small_integer<uint8_t>(bit_ref& bref, uint8_t n);
template SRSASN_CODE pack_norm_small_integer<uint16_t>(bit_ref& bref, uint16_t n);
template SRSASN_CODE pack_norm_small_integer<uint32_t>(bit_ref& bref, uint32_t n);
template SRSASN_CODE pack_norm_small_integer<uint64_t>(bit_ref& bref, uint64_t n);
template SRSASN_CODE unpack_norm_small_integer<uint8_t>(uint8_t& n, bit_ref& bref);
template SRSASN_CODE unpack_norm_small_integer<uint16_t>(uint16_t& n, bit_ref& bref);
template SRSASN_CODE unpack_norm_small_integer<uint32_t>(uint32_t& n, bit_ref& bref);
template SRSASN_CODE unpack_norm_small_integer<uint64_t>(uint64_t& n, bit_ref& bref);

/*********************
   varlength_packing
*********************/

template <typename IntType>
SRSASN_CODE pack_length(bit_ref& bref, IntType n, IntType lb, IntType ub)
{
  // TODO: FIXME if range higher than 64K
  return pack_unalign_integer(bref, n, lb, ub);
}
template SRSASN_CODE pack_length<uint8_t>(bit_ref& bref, uint8_t n, uint8_t lb, uint8_t ub);
template SRSASN_CODE pack_length<uint16_t>(bit_ref& bref, uint16_t n, uint16_t lb, uint16_t ub);
template SRSASN_CODE pack_length<uint32_t>(bit_ref& bref, uint32_t n, uint32_t lb, uint32_t ub);
template SRSASN_CODE pack_length<uint64_t>(bit_ref& bref, uint64_t n, uint64_t lb, uint64_t ub);
template SRSASN_CODE pack_length<int8_t>(bit_ref& bref, int8_t n, int8_t lb, int8_t ub);
template SRSASN_CODE pack_length<int16_t>(bit_ref& bref, int16_t n, int16_t lb, int16_t ub);
template SRSASN_CODE pack_length<int32_t>(bit_ref& bref, int32_t n, int32_t lb, int32_t ub);
template SRSASN_CODE pack_length<int64_t>(bit_ref& bref, int64_t n, int64_t lb, int64_t ub);
template <typename IntType>
SRSASN_CODE unpack_length(IntType& n, bit_ref& bref, IntType lb, IntType ub)
{
  // TODO: FIXME if range higher than 64K
  return unpack_unalign_integer(n, bref, lb, ub);
}

template SRSASN_CODE unpack_length<uint8_t>(uint8_t& n, bit_ref& bref, uint8_t lb, uint8_t ub);
template SRSASN_CODE unpack_length<uint16_t>(uint16_t& n, bit_ref& bref, uint16_t lb, uint16_t ub);
template SRSASN_CODE unpack_length<uint32_t>(uint32_t& n, bit_ref& bref, uint32_t lb, uint32_t ub);
template SRSASN_CODE unpack_length<uint64_t>(uint64_t& n, bit_ref& bref, uint64_t lb, uint64_t ub);
template SRSASN_CODE unpack_length<int8_t>(int8_t& n, bit_ref& bref, int8_t lb, int8_t ub);
template SRSASN_CODE unpack_length<int16_t>(int16_t& n, bit_ref& bref, int16_t lb, int16_t ub);
template SRSASN_CODE unpack_length<int32_t>(int32_t& n, bit_ref& bref, int32_t lb, int32_t ub);
template SRSASN_CODE unpack_length<int64_t>(int64_t& n, bit_ref& bref, int64_t lb, int64_t ub);

SRSASN_CODE pack_length(bit_ref& bref, uint32_t val)
{
  if (val < 128) {
    HANDLE_CODE(bref.pack(0, 1));
    HANDLE_CODE(bref.pack(val, 7));
  } else if (val < ASN_16K) {
    HANDLE_CODE(bref.pack(1, 1));
    HANDLE_CODE(bref.pack(0, 1));
    HANDLE_CODE(bref.pack(val, 14));
  } else {
    srsasn_log_print(LOG_LEVEL_ERROR, "Not handling sizes longer than 16383 octets\n");
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE unpack_length(uint32_t& val, bit_ref& bref)
{
  bool        ext;
  SRSASN_CODE ret = bref.unpack(ext, 1);
  HANDLE_CODE(ret);
  if (not ext) {
    ret = bref.unpack(val, 7);
  } else {
    ret = bref.unpack(ext, 1);
    HANDLE_CODE(ret);
    if (not ext) {
      ret = bref.unpack(val, 14);
    } else {
      srsasn_log_print(LOG_LEVEL_ERROR, "Not handling octet strings longer than 16383 octets\n");
      val = 0;
      return SRSASN_ERROR_DECODE_FAIL;
    }
  }
  return ret;
}

/************************
    common octstring
************************/

uint64_t octstring_to_number(const uint8_t* ptr, uint32_t nbytes)
{
  if (nbytes > 8) {
    srsasn_log_print(LOG_LEVEL_ERROR, "octstring of size=%d does not fit in an uint64_t\n", nbytes);
    return 0;
  }
  uint64_t val = 0;
  for (uint32_t i = 0; i < nbytes; ++i) {
    val += ((uint64_t)ptr[nbytes - 1 - i]) << (uint64_t)(i * 8);
  }
  return val;
}

void number_to_octstring(uint8_t* ptr, uint64_t number, uint32_t nbytes)
{
  if (nbytes > 8) {
    srsasn_log_print(LOG_LEVEL_ERROR, "octstring of size=%d does not fit in an uint64_t\n", nbytes);
    return;
  }
  for (uint32_t i = 0; i < nbytes; ++i) {
    ptr[nbytes - 1 - i] = (number >> (uint64_t)(i * 8u)) & 0xFF;
  }
}

void to_hex(char* cstr, uint8_t val)
{
  sprintf(cstr, "%02x", val);
}

// helper functions
std::string octstring_to_string(const uint8_t* ptr, uint32_t N)
{
  std::string s;
  s.resize(N * 2);
  char cstr[3];
  for (uint32_t i = 0; i < N; i++) {
    to_hex(cstr, ptr[i]);
    s.replace(i * 2, 2, cstr);
  }
  return s;
}

void string_to_octstring(uint8_t* ptr, const std::string& str)
{
  if (str.size() % 2 != 0) {
    srsasn_log_print(LOG_LEVEL_WARN, "The provided hex string size=%d is not a multiple of 2\n.", str.size());
  }
  char cstr[] = "\0\0\0";
  for (uint32_t i = 0; i < str.size(); i += 2) {
    memcpy(&cstr[0], &str[i], 2);
    ptr[i / 2] = strtoul(cstr, NULL, 16);
  }
}

/************************
     dyn_octstring
************************/

SRSASN_CODE dyn_octstring::pack(bit_ref& bref) const
{
  HANDLE_CODE(pack_length(bref, size()));
  for (uint32_t i = 0; i < size(); ++i) {
    HANDLE_CODE(bref.pack(octets_[i], 8));
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE dyn_octstring::unpack(bit_ref& bref)
{
  uint32_t len;
  HANDLE_CODE(unpack_length(len, bref));
  resize(len);
  for (uint32_t i = 0; i < size(); ++i) {
    HANDLE_CODE(bref.unpack(octets_[i], 8));
  }
  return SRSASN_SUCCESS;
}

std::string dyn_octstring::to_string() const
{
  return octstring_to_string(&octets_[0], size());
}

dyn_octstring& dyn_octstring::from_string(const std::string& hexstr)
{
  resize(hexstr.size() / 2);
  string_to_octstring(&octets_[0], hexstr);
  return *this;
}

/*********************
      bitstring
*********************/

SRSASN_CODE pack_common_bitstring(bit_ref& bref, const uint8_t* buf, uint32_t nbits)
{
  uint32_t n_octs = (uint32_t)ceilf(nbits / 8.0f);
  uint32_t offset = ((nbits - 1) % 8) + 1;
  HANDLE_CODE(bref.pack(buf[n_octs - 1], offset));
  for (uint32_t i = 1; i < n_octs; ++i) {
    HANDLE_CODE(bref.pack(buf[n_octs - 1 - i], 8));
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE unpack_common_bitstring(uint8_t* buf, bit_ref& bref, uint32_t nbits)
{
  uint32_t n_octs = (uint32_t)ceilf(nbits / 8.0f);
  uint32_t offset = ((nbits - 1) % 8) + 1;
  HANDLE_CODE(bref.unpack(buf[n_octs - 1], offset));
  for (uint32_t i = 1; i < n_octs; ++i) {
    HANDLE_CODE(bref.unpack(buf[n_octs - 1 - i], 8));
  }
  return SRSASN_SUCCESS;
}

uint64_t bitstring_to_number(const uint8_t* ptr, uint32_t nbits)
{
  if (nbits > 64) {
    srsasn_log_print(LOG_LEVEL_ERROR, "bitstring of size=%d does not fit in an uint64_t\n", nbits);
    return 0;
  }
  uint64_t val       = 0;
  uint32_t nof_bytes = (uint32_t)ceilf(nbits / 8.0f);
  for (uint32_t i = 0; i < nof_bytes; ++i) {
    val += ptr[i] << (i * 8);
  }
  return val;
}
void number_to_bitstring(uint8_t* ptr, uint64_t number, uint32_t nbits)
{
  if (nbits > 64) {
    srsasn_log_print(LOG_LEVEL_ERROR, "bitstring of size=%d does not fit in an uint64_t\n", nbits);
    return;
  }
  uint32_t nof_bytes = (uint32_t)ceilf(nbits / 8.0f);
  for (uint32_t i = 0; i < nof_bytes; ++i) {
    ptr[i] = (number >> (i * 8)) & 0xFF;
  }
  uint32_t offset = nbits % 8; // clean up any extra set bit
  if (offset > 0) {
    ptr[nof_bytes - 1] &= (uint8_t)((1 << offset) - 1);
  }
}

std::string bitstring_to_string(const uint8_t* ptr, uint32_t nbits)
{
  std::string str(nbits, '\0');
  for (uint32_t i = 0; i < nbits; ++i) {
    str[i] = bitstring_get(ptr, nbits - 1 - i) ? '1' : '0';
  }
  return str;
}

/*********************
   fixed_bitstring
*********************/

SRSASN_CODE pack_fixed_bitstring(bit_ref& bref, const uint8_t* buf, uint32_t nbits)
{
  //  if(nbits > 16) { // X.691 Section 15
  //    bref.align_bytes_zero();
  //  }
  return pack_common_bitstring(bref, buf, nbits);
  //  if(nbits > 16) { // X.691 Section 15
  //    bref.align_bytes_zero();
  //  }
}

SRSASN_CODE pack_fixed_bitstring(bit_ref& bref, const uint8_t* buf, uint32_t nbits, bool ext)
{
  HANDLE_CODE(pack_unsupported_ext_flag(bref, ext));
  HANDLE_CODE(pack_fixed_bitstring(bref, buf, nbits));
  return SRSASN_SUCCESS;
}

SRSASN_CODE unpack_fixed_bitstring(uint8_t* buf, bit_ref& bref, uint32_t nbits)
{
  //  if(nbits > 16) { // X.691 Section 15
  //    bref.align_bytes_zero();
  //  }
  return unpack_common_bitstring(buf, bref, nbits);
  //  if(nbits > 16) { // X.691 Section 15
  //    bref.align_bytes_zero();
  //  }
}

SRSASN_CODE unpack_fixed_bitstring(uint8_t* buf, bool& ext, bit_ref& bref, uint32_t nbits)
{
  HANDLE_CODE(unpack_unsupported_ext_flag(ext, bref));
  HANDLE_CODE(unpack_fixed_bitstring(buf, bref, nbits));
  return SRSASN_SUCCESS;
}

/*********************
    dyn_bitstring
*********************/

dyn_bitstring::dyn_bitstring(uint32_t n_bits_) : n_bits(n_bits_)
{
  uint32_t n_bytes = nof_octets();
  octets_.resize(n_bytes);
  memset(&octets_[0], 0, n_bytes);
}

dyn_bitstring::dyn_bitstring(const char* s)
{
  n_bits           = (uint32_t)strlen(s);
  uint32_t n_bytes = nof_octets();
  octets_.resize(n_bytes);
  memset(&octets_[0], 0, n_bytes);
  for (uint32_t i = 0; i < n_bits; ++i)
    this->set(i, s[n_bits - i - 1] == '1');
}

bool dyn_bitstring::operator==(const char* other_str) const
{
  return strlen(other_str) == length() and (*this) == dyn_bitstring(other_str);
}

void dyn_bitstring::resize(uint32_t new_size)
{
  n_bits = new_size;
  octets_.resize(nof_octets());
  memset(&octets_[0], 0, nof_octets());
}

SRSASN_CODE dyn_bitstring::pack(bit_ref& bref, uint32_t lb, uint32_t ub) const
{
  uint32_t len = length();
  if (len < lb) {
    srsasn_log_print(LOG_LEVEL_ERROR, "dynamic bitstring length=%d is lower than set lower bound=%d\n", len, lb);
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  if (ub > 0) {
    if (ub < len) {
      srsasn_log_print(LOG_LEVEL_ERROR, "asn1 error: dynamic bitstring length=%d is higher than set upper bound=%d\n",
                       len, ub);
      return SRSASN_ERROR_ENCODE_FAIL;
    }
    uint32_t len_bits = ceilf(log2(ub - lb));
    HANDLE_CODE(bref.pack(len - lb, len_bits));
    HANDLE_CODE(bref.align_bytes_zero());
  } else {
    HANDLE_CODE(pack_length(bref, len));
  }
  HANDLE_CODE(pack_common_bitstring(bref, &octets_[0], len));
  //  bref.align_bytes_zero();
  return SRSASN_SUCCESS;
}

SRSASN_CODE dyn_bitstring::pack(bit_ref& bref, bool ext, uint32_t lb, uint32_t ub) const
{
  HANDLE_CODE(pack_unsupported_ext_flag(bref, ext));
  HANDLE_CODE(pack(bref, lb, ub));
  return SRSASN_SUCCESS;
}

SRSASN_CODE dyn_bitstring::unpack(bit_ref& bref, uint32_t lb, uint32_t ub)
{
  uint32_t len;
  if (ub > 0) {
    uint32_t len_bits = ceilf(log2f(ub - lb));
    HANDLE_CODE(bref.unpack(len, len_bits));
    len += lb;
    resize(len);
    HANDLE_CODE(bref.align_bytes_zero());
  } else {
    HANDLE_CODE(unpack_length(len, bref));
    resize(len);
  }
  HANDLE_CODE(unpack_common_bitstring(&octets_[0], bref, len));
  //  bref.align_bytes_zero();
  return SRSASN_SUCCESS;
}

SRSASN_CODE dyn_bitstring::unpack(bit_ref& bref, bool& ext, uint32_t lb, uint32_t ub)
{
  HANDLE_CODE(unpack_unsupported_ext_flag(ext, bref));
  HANDLE_CODE(unpack(bref, lb, ub));
  return SRSASN_SUCCESS;
}

/*********************
     choice utils
*********************/
void log_invalid_access_choice_id(uint32_t val, uint32_t choice_id)
{
  srsasn_log_print(LOG_LEVEL_ERROR, "The access choide id is invalid (%d!=%d)\n", val, choice_id);
}
void log_invalid_choice_id(uint32_t val, const char* choice_type)
{
  srsasn_log_print(LOG_LEVEL_ERROR, "Invalid choice id=%d for choice type %s\n", val, choice_type);
}

/*********************
      ext group
*********************/

ext_groups_header::ext_groups_header(uint32_t max_nof_groups, uint32_t nof_nogroups_) : nof_nogroups(nof_nogroups_)
{
  if (max_nof_groups > 20) {
    srsasn_log_print(LOG_LEVEL_ERROR, "increase the size of ext group packer/unpacker\n");
  }
  groups.resize(max_nof_groups);
  for (uint32_t i = 0; i < groups.size(); ++i) {
    groups[i] = false;
  }
  nof_groups = groups.size() + 1; // unset
}

bool& ext_groups_header::operator[](uint32_t idx)
{
  return groups[idx];
}

SRSASN_CODE ext_groups_header::pack_nof_groups(bit_ref& bref) const
{
  nof_groups = 0;
  for (uint32_t i = 0; i < groups.size(); ++i) {
    if (groups[i]) {
      nof_groups = i + 1;
    }
  }
  if (nof_groups > groups.size()) {
    srsasn_log_print(LOG_LEVEL_ERROR, "Exceeded maximum number of groups (%d>%d)\n", nof_groups, groups.size());
    return SRSASN_ERROR_ENCODE_FAIL;
  }
  HANDLE_CODE(pack_norm_small_integer(bref, nof_groups + nof_nogroups - 1));
  return SRSASN_SUCCESS;
}

SRSASN_CODE ext_groups_header::pack_group_flags(bit_ref& bref) const
{
  // NOTE: nof_groups is cached
  for (uint32_t i = 0; i < nof_groups; ++i) {
    HANDLE_CODE(bref.pack(groups[i], 1));
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE ext_groups_header::pack(bit_ref& bref) const
{
  HANDLE_CODE(pack_nof_groups(bref));
  return pack_group_flags(bref);
}

SRSASN_CODE ext_groups_header::unpack_nof_groups(bit_ref& bref)
{
  HANDLE_CODE(unpack_norm_small_integer(nof_groups, bref));
  nof_groups += 1 - nof_nogroups;
  if (nof_groups > groups.size()) {
    srsasn_log_print(LOG_LEVEL_ERROR, "Exceeded maximum number of groups (%d>%d)\n", nof_groups, groups.size());
    return SRSASN_ERROR_DECODE_FAIL;
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE ext_groups_header::unpack_group_flags(bit_ref& bref)
{
  for (uint32_t i = 0; i < nof_groups; ++i) {
    HANDLE_CODE(bref.unpack(groups[i], 1));
  }
  return SRSASN_SUCCESS;
}

SRSASN_CODE ext_groups_header::unpack(bit_ref& bref)
{
  HANDLE_CODE(unpack_nof_groups(bref));
  return unpack_group_flags(bref);
}

/*********************
     Open Field
*********************/

varlength_field_pack_guard::varlength_field_pack_guard(bit_ref& bref)
{
  brefstart    = bref;
  bref         = bit_ref(&buffer[0], sizeof(buffer));
  bref_tracker = &bref;
}

varlength_field_pack_guard::~varlength_field_pack_guard()
{
  // fill the spare bits
  const bit_ref bref0    = bit_ref(&buffer[0], sizeof(buffer));
  uint32_t      leftover = 7 - ((bref_tracker->distance(bref0) - (uint32_t)1) % (uint32_t)8);
  bref_tracker->pack(0, leftover);

  // check how many bytes were written in total
  uint32_t nof_bytes = bref_tracker->distance(bref0) / (uint32_t)8;
  if (nof_bytes > sizeof(buffer)) {
    srsasn_log_print(LOG_LEVEL_ERROR, "The packed variable sized field is too long for the reserved buffer (%d > %d)\n",
                     nof_bytes, sizeof(buffer));
  }

  // go back in time to pack length
  pack_length(brefstart, nof_bytes);

  // pack encoded bytes
  for (uint32_t i = 0; i < nof_bytes; ++i) {
    brefstart.pack(buffer[i], 8);
  }
  *bref_tracker = brefstart;
}

varlength_field_unpack_guard::varlength_field_unpack_guard(bit_ref& bref)
{
  unpack_length(len, bref);
  bref0        = bref;
  bref_tracker = &bref;
}

varlength_field_unpack_guard::~varlength_field_unpack_guard()
{
  uint32_t pad;
  bref_tracker->unpack(pad, len * 8 - bref_tracker->distance(bref0));
}

/*******************
    JsonWriter
*******************/

json_writer::json_writer() : ident(""), sep(NONE) {}

void json_writer::write_fieldname(const std::string& fieldname)
{
  if (sep == COMMA) {
    ss << ",\n" << ident;
  } else if (sep == NEWLINE) {
    ss << "\n" << ident;
  }
  if (fieldname.size() > 0) {
    ss << "\"" << fieldname << "\": ";
  }
  sep = NONE;
}

void json_writer::write_str(const std::string& fieldname, const std::string& value)
{
  write_fieldname(fieldname);
  ss << "\"" << value << "\"";
  sep = COMMA;
}
void json_writer::write_str(const std::string& value)
{
  write_str("", value);
}

void json_writer::write_int(const std::string& fieldname, int64_t value)
{
  write_fieldname(fieldname);
  ss << value;
  sep = COMMA;
}
void json_writer::write_int(int64_t value)
{
  write_int("", value);
}

void json_writer::write_bool(const std::string& fieldname, bool value)
{
  write_fieldname(fieldname);
  ss << (value ? "true" : "false");
  sep = COMMA;
}
void json_writer::write_bool(bool value)
{
  write_bool("", value);
}

void json_writer::write_null(const std::string& fieldname)
{
  write_fieldname(fieldname);
  ss << "null";
  sep = COMMA;
}
void json_writer::write_null()
{
  write_null("");
}

void json_writer::start_obj(const std::string& fieldname)
{
  write_fieldname(fieldname);
  ss << "{";
  ident += "  ";
  sep = NEWLINE;
}
void json_writer::end_obj()
{
  ident.erase(ident.size() - 2, 2);
  ss << "\n" << ident << "}";
  sep = COMMA;
}
void json_writer::start_array(const std::string& fieldname)
{
  write_fieldname(fieldname);
  ss << "[";
  ident += "  ";
  sep = NEWLINE;
}
void json_writer::end_array()
{
  ident.erase(ident.size() - 2, 2);
  ss << "\n" << ident << "]";
  sep = COMMA;
}

std::string json_writer::to_string() const
{
  return ss.str();
}

} // namespace asn1
