/*
 * Copyright (c) 2014-2020 Pavel Kalvoda <me@pavelkalvoda.com>
 *
 * libcbor is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include "builder_callbacks.h"

#include <string.h>

#include "../arrays.h"
#include "../bytestrings.h"
#include "../common.h"
#include "../floats_ctrls.h"
#include "../ints.h"
#include "../maps.h"
#include "../strings.h"
#include "../tags.h"
#include "unicode.h"

// `_cbor_builder_append` takes ownership of `item`. If adding the item to
// parent container fails, `item` will be deallocated to prevent memory.
void _cbor_builder_append(cbor_item_t *item,
                          struct _cbor_decoder_context *ctx) {
  if (ctx->stack->size == 0) {
    /* Top level item */
    ctx->root = item;
    return;
  }
  /* Part of a bigger structure */
  switch (ctx->stack->top->item->type) {
    // Handle Arrays and Maps since they can contain subitems of any type.
    // Byte/string construction from chunks is handled in the respective chunk
    // handlers.
    case CBOR_TYPE_ARRAY: {
      if (cbor_array_is_definite(ctx->stack->top->item)) {
        // We don't need an explicit check for whether the item still belongs
        // into this array because if there are extra items, they will cause a
        // syntax error when decoded.
        CBOR_ASSERT(ctx->stack->top->subitems > 0);
        // This should never happen since the definite array should be
        // preallocated for the expected number of items.
        if (!cbor_array_push(ctx->stack->top->item, item)) {
          ctx->creation_failed = true;
          cbor_decref(&item);
          break;
        }
        cbor_decref(&item);
        ctx->stack->top->subitems--;
        if (ctx->stack->top->subitems == 0) {
          cbor_item_t *stack_item = ctx->stack->top->item;
          _cbor_stack_pop(ctx->stack);
          _cbor_builder_append(stack_item, ctx);
        }
      } else {
        /* Indefinite array, don't bother with subitems */
        if (!cbor_array_push(ctx->stack->top->item, item)) {
          ctx->creation_failed = true;
        }
        cbor_decref(&item);
      }
      break;
    }
    case CBOR_TYPE_MAP: {
      // Handle both definite and indefinite maps the same initially.
      // Note: We use 0 and 1 subitems to distinguish between keys and values in
      // indefinite items
      if (ctx->stack->top->subitems % 2) {
        // Odd record, this is a value.
        ctx->creation_failed =
            !_cbor_map_add_value(ctx->stack->top->item, item);
        // Adding a value never fails since the memory is allocated when the
        // key is added
        CBOR_ASSERT(!ctx->creation_failed);
      } else {
        // Even record, this is a key.
        if (!_cbor_map_add_key(ctx->stack->top->item, item)) {
          ctx->creation_failed = true;
          cbor_decref(&item);
          break;
        }
      }
      cbor_decref(&item);
      if (cbor_map_is_definite(ctx->stack->top->item)) {
        CBOR_ASSERT(ctx->stack->top->subitems > 0);
        ctx->stack->top->subitems--;
        if (ctx->stack->top->subitems == 0) {
          cbor_item_t *map_entry = ctx->stack->top->item;
          _cbor_stack_pop(ctx->stack);
          _cbor_builder_append(map_entry, ctx);
        }
      } else {
        ctx->stack->top->subitems ^=
            1; /* Flip the indicator for indefinite items */
      }
      break;
    }
    case CBOR_TYPE_TAG: {
      CBOR_ASSERT(ctx->stack->top->subitems == 1);
      cbor_tag_set_item(ctx->stack->top->item, item);
      cbor_decref(&item); /* Give up on our reference */
      cbor_item_t *tagged_item = ctx->stack->top->item;
      _cbor_stack_pop(ctx->stack);
      _cbor_builder_append(tagged_item, ctx);
      break;
    }
    // We have an item to append but nothing to append it to.
    default: {
      cbor_decref(&item);
      ctx->syntax_error = true;
    }
  }
}

#define CHECK_RES(ctx, res)        \
  do {                             \
    if (res == NULL) {             \
      ctx->creation_failed = true; \
      return;                      \
    }                              \
  } while (0)

// Check that the length fits into size_t. If not, we cannot possibly allocate
// the required memory and should fail fast.
#define CHECK_LENGTH(ctx, length)  \
  do {                             \
    if (length > SIZE_MAX) {       \
      ctx->creation_failed = true; \
      return;                      \
    }                              \
  } while (0)

#define PUSH_CTX_STACK(ctx, res, subitems)                     \
  do {                                                         \
    if (_cbor_stack_push(ctx->stack, res, subitems) == NULL) { \
      cbor_decref(&res);                                       \
      ctx->creation_failed = true;                             \
    }                                                          \
  } while (0)

void cbor_builder_uint8_callback(void *context, uint8_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int8();
  CHECK_RES(ctx, res);
  cbor_mark_uint(res);
  cbor_set_uint8(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_uint16_callback(void *context, uint16_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int16();
  CHECK_RES(ctx, res);
  cbor_mark_uint(res);
  cbor_set_uint16(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_uint32_callback(void *context, uint32_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int32();
  CHECK_RES(ctx, res);
  cbor_mark_uint(res);
  cbor_set_uint32(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_uint64_callback(void *context, uint64_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int64();
  CHECK_RES(ctx, res);
  cbor_mark_uint(res);
  cbor_set_uint64(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_negint8_callback(void *context, uint8_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int8();
  CHECK_RES(ctx, res);
  cbor_mark_negint(res);
  cbor_set_uint8(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_negint16_callback(void *context, uint16_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int16();
  CHECK_RES(ctx, res);
  cbor_mark_negint(res);
  cbor_set_uint16(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_negint32_callback(void *context, uint32_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int32();
  CHECK_RES(ctx, res);
  cbor_mark_negint(res);
  cbor_set_uint32(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_negint64_callback(void *context, uint64_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_int64();
  CHECK_RES(ctx, res);
  cbor_mark_negint(res);
  cbor_set_uint64(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_byte_string_callback(void *context, cbor_data data,
                                       uint64_t length) {
  struct _cbor_decoder_context *ctx = context;
  CHECK_LENGTH(ctx, length);
  unsigned char *new_handle = _cbor_malloc(length);
  if (new_handle == NULL) {
    ctx->creation_failed = true;
    return;
  }

  memcpy(new_handle, data, length);
  cbor_item_t *new_chunk = cbor_new_definite_bytestring();

  if (new_chunk == NULL) {
    _cbor_free(new_handle);
    ctx->creation_failed = true;
    return;
  }

  cbor_bytestring_set_handle(new_chunk, new_handle, length);

  // If an indef bytestring is on the stack, extend it (if it were closed, it
  // would have been popped). Handle any syntax errors upstream.
  if (ctx->stack->size > 0 && cbor_isa_bytestring(ctx->stack->top->item) &&
      cbor_bytestring_is_indefinite(ctx->stack->top->item)) {
    if (!cbor_bytestring_add_chunk(ctx->stack->top->item, new_chunk)) {
      ctx->creation_failed = true;
    }
    cbor_decref(&new_chunk);
  } else {
    _cbor_builder_append(new_chunk, ctx);
  }
}

void cbor_builder_byte_string_start_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_indefinite_bytestring();
  CHECK_RES(ctx, res);
  PUSH_CTX_STACK(ctx, res, 0);
}

void cbor_builder_string_callback(void *context, cbor_data data,
                                  uint64_t length) {
  struct _cbor_decoder_context *ctx = context;
  CHECK_LENGTH(ctx, length);

  unsigned char *new_handle = _cbor_malloc(length);
  if (new_handle == NULL) {
    ctx->creation_failed = true;
    return;
  }

  memcpy(new_handle, data, length);
  cbor_item_t *new_chunk = cbor_new_definite_string();
  if (new_chunk == NULL) {
    _cbor_free(new_handle);
    ctx->creation_failed = true;
    return;
  }
  cbor_string_set_handle(new_chunk, new_handle, length);

  // If an indef string is on the stack, extend it (if it were closed, it would
  // have been popped). Handle any syntax errors upstream.
  if (ctx->stack->size > 0 && cbor_isa_string(ctx->stack->top->item) &&
      cbor_string_is_indefinite(ctx->stack->top->item)) {
    if (!cbor_string_add_chunk(ctx->stack->top->item, new_chunk)) {
      ctx->creation_failed = true;
    }
    cbor_decref(&new_chunk);
  } else {
    _cbor_builder_append(new_chunk, ctx);
  }
}

void cbor_builder_string_start_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_indefinite_string();
  CHECK_RES(ctx, res);
  PUSH_CTX_STACK(ctx, res, 0);
}

void cbor_builder_array_start_callback(void *context, uint64_t size) {
  struct _cbor_decoder_context *ctx = context;
  CHECK_LENGTH(ctx, size);
  cbor_item_t *res = cbor_new_definite_array(size);
  CHECK_RES(ctx, res);
  if (size > 0) {
    PUSH_CTX_STACK(ctx, res, size);
  } else {
    _cbor_builder_append(res, ctx);
  }
}

void cbor_builder_indef_array_start_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_indefinite_array();
  CHECK_RES(ctx, res);
  PUSH_CTX_STACK(ctx, res, 0);
}

void cbor_builder_indef_map_start_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_indefinite_map();
  CHECK_RES(ctx, res);
  PUSH_CTX_STACK(ctx, res, 0);
}

void cbor_builder_map_start_callback(void *context, uint64_t size) {
  struct _cbor_decoder_context *ctx = context;
  CHECK_LENGTH(ctx, size);
  cbor_item_t *res = cbor_new_definite_map(size);
  CHECK_RES(ctx, res);
  if (size > 0) {
    PUSH_CTX_STACK(ctx, res, size * 2);
  } else {
    _cbor_builder_append(res, ctx);
  }
}

/**
 * Is the (partially constructed) item indefinite?
 */
bool _cbor_is_indefinite(cbor_item_t *item) {
  switch (item->type) {
    case CBOR_TYPE_BYTESTRING:
      return cbor_bytestring_is_indefinite(item);
    case CBOR_TYPE_STRING:
      return cbor_string_is_indefinite(item);
    case CBOR_TYPE_ARRAY:
      return cbor_array_is_indefinite(item);
    case CBOR_TYPE_MAP:
      return cbor_map_is_indefinite(item);
    default:
      // Should never happen since a non-nested item cannot be on top of the
      // stack.
      return false;
  }
}

void cbor_builder_indef_break_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  /* There must be an item to break out of*/
  if (ctx->stack->size > 0) {
    cbor_item_t *item = ctx->stack->top->item;
    if (_cbor_is_indefinite(
            item) && /* Only indefinite items can be terminated by 0xFF */
        /* Special case: we cannot append up if an indefinite map is incomplete
           (we are expecting a value). */
        (item->type != CBOR_TYPE_MAP || ctx->stack->top->subitems % 2 == 0)) {
      _cbor_stack_pop(ctx->stack);
      _cbor_builder_append(item, ctx);
      return;
    }
  }

  ctx->syntax_error = true;
}

void cbor_builder_float2_callback(void *context, float value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_float2();
  CHECK_RES(ctx, res);
  cbor_set_float2(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_float4_callback(void *context, float value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_float4();
  CHECK_RES(ctx, res);
  cbor_set_float4(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_float8_callback(void *context, double value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_float8();
  CHECK_RES(ctx, res);
  cbor_set_float8(res, value);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_null_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_null();
  CHECK_RES(ctx, res);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_undefined_callback(void *context) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_undef();
  CHECK_RES(ctx, res);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_boolean_callback(void *context, bool value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_build_bool(value);
  CHECK_RES(ctx, res);
  _cbor_builder_append(res, ctx);
}

void cbor_builder_tag_callback(void *context, uint64_t value) {
  struct _cbor_decoder_context *ctx = context;
  cbor_item_t *res = cbor_new_tag(value);
  CHECK_RES(ctx, res);
  PUSH_CTX_STACK(ctx, res, 1);
}