/*
 * Copyright (c) 2017 Lev Walkin <vlm@lionet.info>. All rights reserved.
 * Redistribution and modifications are permitted subject to BSD license.
 */
#include <asn_internal.h>
#include <asn_codecs_prim.h>

/*
 * The OER encoder of any type.
 */
asn_enc_rval_t oer_encode(
    const asn_TYPE_descriptor_t* type_descriptor, const void* struct_ptr,
    asn_app_consume_bytes_f* consume_bytes, void* app_key) {
  ASN_DEBUG("OER encoder invoked for %s", type_descriptor->name);

  /*
   * Invoke type-specific encoder.
   */
  return type_descriptor->op->oer_encoder(
      type_descriptor, 0, struct_ptr, /* Pointer to the destination structure */
      consume_bytes, app_key);
}

/*
 * Argument type and callback necessary for oer_encode_to_buffer().
 */
typedef struct enc_to_buf_arg {
  void* buffer;
  size_t left;
} enc_to_buf_arg;
static int encode_to_buffer_cb(const void* buffer, size_t size, void* key) {
  enc_to_buf_arg* arg = (enc_to_buf_arg*) key;

  if (arg->left < size) return -1; /* Data exceeds the available buffer size */

  memcpy(arg->buffer, buffer, size);
  arg->buffer = ((char*) arg->buffer) + size;
  arg->left -= size;

  return 0;
}

/*
 * A variant of the oer_encode() which encodes the data into the provided buffer
 */
asn_enc_rval_t oer_encode_to_buffer(
    const asn_TYPE_descriptor_t* type_descriptor,
    const asn_oer_constraints_t* constraints,
    const void* struct_ptr, /* Structure to be encoded */
    void* buffer,           /* Pre-allocated buffer */
    size_t buffer_size      /* Initial buffer size (maximum) */
) {
  enc_to_buf_arg arg;
  asn_enc_rval_t ec;

  arg.buffer = buffer;
  arg.left   = buffer_size;

  if (type_descriptor->op->oer_encoder == NULL) {
    ec.encoded       = -1;
    ec.failed_type   = type_descriptor;
    ec.structure_ptr = struct_ptr;
    ASN_DEBUG("OER encoder is not defined for %s", type_descriptor->name);
  } else {
    ec = type_descriptor->op->oer_encoder(
        type_descriptor, constraints,
        struct_ptr, /* Pointer to the destination structure */
        encode_to_buffer_cb, &arg);
    if (ec.encoded != -1) {
      assert(ec.encoded == (ssize_t)(buffer_size - arg.left));
      /* Return the encoded contents size */
    }
  }
  return ec;
}

asn_enc_rval_t oer_encode_primitive(
    const asn_TYPE_descriptor_t* td, const asn_oer_constraints_t* constraints,
    const void* sptr, asn_app_consume_bytes_f* cb, void* app_key) {
  const ASN__PRIMITIVE_TYPE_t* st = (const ASN__PRIMITIVE_TYPE_t*) sptr;
  asn_enc_rval_t er               = {0, 0, 0};
  ssize_t ret;

  (void) constraints;

  if (!st) ASN__ENCODE_FAILED;

  ASN_DEBUG(
      "Encoding %s (%" ASN_PRI_SIZE " bytes)", td ? td->name : "", st->size);

  /*
   * X.696 (08/2015) #27.2
   */
  ret = oer_serialize_length(st->size, cb, app_key);
  if (ret < 0) {
    ASN__ENCODE_FAILED;
  }
  er.encoded += ret;

  er.encoded += st->size;
  if (cb(st->buf, st->size, app_key) < 0) {
    ASN__ENCODE_FAILED;
  } else {
    ASN__ENCODED_OK(er);
  }
}

static int oer__count_bytes(const void* buffer, size_t size, void* bytes_ptr) {
  size_t* bytes = bytes_ptr;
  (void) buffer;
  *bytes += size;
  return 0;
}

ssize_t oer_open_type_put(
    const asn_TYPE_descriptor_t* td, const asn_oer_constraints_t* constraints,
    const void* sptr, asn_app_consume_bytes_f* cb, void* app_key) {
  size_t serialized_byte_count = 0;
  asn_enc_rval_t er;
  ssize_t len_len;

  er = td->op->oer_encoder(
      td, constraints, sptr, oer__count_bytes, &serialized_byte_count);
  if (er.encoded < 0) return -1;
  assert(serialized_byte_count == (size_t) er.encoded);

  len_len = oer_serialize_length(serialized_byte_count, cb, app_key);
  if (len_len == -1) return -1;

  er = td->op->oer_encoder(td, constraints, sptr, cb, app_key);
  if (er.encoded < 0) return -1;
  assert(serialized_byte_count == (size_t) er.encoded);

  return len_len + er.encoded;
}
