#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "nlltypes.h"
#include "nlllib.h"
#include "memory.h"
#include "value.h"
#include "array.h"

static int array_num = 0;

#ifndef NLL_MEMORY_STATIC
static int _init(void)
{
  return 0;
}

static int _check(void)
{
  return 0;
}

static array_t _alloc(int number)
{
  array_t array;
  if ((array = memory_alloc(sizeof(*array) * number)) == NULL)
    return NULL;
  array_num += number;
  return array;
}

static int _free(array_t array, int number)
{
  array_num -= number;
  memory_free(array);
  return 0;
}
#else
static struct array arrays[ARRAY_MAXNUM];

static int _init(void)
{
  memset(arrays, 0, sizeof(arrays));
  return 0;
}

static int _check(void)
{
  return 0;
}

static array_t _alloc(int number)
{
  array_t array;

  if (array_num + number > ARRAY_MAXNUM)
    return NULL;

  array = &arrays[array_num];
  array_num += number;

  return array;
}

static int _free(array_t array, int number)
{
  while ((array_num > 0) && !arrays[array_num - 1].value)
    array_num--;

  return 0;
}
#endif

int array_check(void)
{
  if (array_num)
    return NLL_ERRCODE_ARRAY_NOT_EMPTY;

  return _check();
}

int array_init(void)
{
  array_num = 0;
  return _init();
}

int array_get_number(array_t array)
{
  int r;
  integer_t number;

  if ((r = value_get_integer(array[0].value, &number)) < 0)
    return r;

  return number;
}

int array_get_value(array_t array, int index, value_t *valuep)
{
  int r;

  if ((r = array_get_number(array)) < 0)
    return r;
  if ((index < 0) || (index >= r))
    return NLL_ERRCODE_ARRAY_OUT_OF_RANGE;

  if (valuep)
    *valuep = array[1 + index].value;

  return 0;
}

int array_set_value(array_t array, int index, value_t value)
{
  int r;

  if ((r = array_get_number(array)) < 0)
    return r;
  if ((index < 0) || (index >= r))
    return NLL_ERRCODE_ARRAY_OUT_OF_RANGE;

  array[1 + index].value = value;

  return 0;
}

int array_alloc(array_t *arrayp, int number)
{
  int r, i;
  array_t array;
  value_t value = NULL;

  if (number < 0)
    return NLL_ERRCODE_ARRAY_INVALID_PARAMETER;

  if ((array = _alloc(number + 1)) == NULL)
    return NLL_ERRCODE_ARRAY_BUFFER_OVER;

  memset(array, 0, sizeof(*array) * (number + 1));

  if ((r = value_alloc(&array[0].value)) < 0) {
    _free(array, number + 1);
    return r;
  }

  /* Top value is dummy for reference counter */
  if ((r = value_set_integer(array[0].value, number)) < 0)
    goto err;

  for (i = 0; i < number; i++) {
    if ((r = value_alloc(&value)) < 0)
      goto err;
    if ((r = array_set_value(array, i, value)) < 0)
      goto err;
    value = NULL;
  }

  *arrayp = array;

  return 0;

err:
  if (array) {
    array[0].value->refcount++;
    array_free(array, number);
  }
  if (value)
    value_free(value);
  return r;
}

int array_free(array_t array, int number)
{
  int r, i;
  value_t value;

  if (number < 0) {
    if ((r = array_get_number(array)) < 0)
      return r;
    number = r;
  }

  if (--(array[0].value->refcount) > 1)
    return 1;

  for (i = 0; i < number; i++) {
    if ((r = array_get_value(array, i, &value)) < 0)
      return r;
    if (value) {
#ifdef NLL_CLEAR_VALUE
      value_clear(value); /* For link of the loop */
#endif
      value_free(value);
      array_set_value(array, i, NULL);
    }
  }

  if ((r = value_free(array[0].value)) < 0)
    return r;
  array[0].value = NULL;

  if ((r = _free(array, number + 1)) < 0)
    return r;

  return 0;
}

int array_make(value_t value, int *n)
{
  int r, i, num;
  array_t array = NULL;
  value_t v;

  if ((num = *n) < 0)
    goto ret;

  if ((r = array_alloc(&array, num)) < 0)
    goto err;

  if (*(n + 1) < 0)
    goto ret;

  for (i = 0; i < num; i++) {
    if ((r = array_get_value(array, i, &v)) < 0)
      goto err;
    if ((r = array_make(v, n + 1)) < 0)
      goto err;
  }

ret:
  if ((r = value_set_array(value, array, 0)) < 0)
    goto err;
  return 0;

err:
  if (array) {
    array[0].value->refcount++;
    array_free(array, num);
  }
  return r;
}

int array_make_values(value_t value, value_t numbers)
{
  int r, i;
  integer_t num;
  array_t array = NULL;
  value_t v;

  if (!numbers)
    goto ret;

  if ((r = value_get_integer(numbers, &num)) < 0)
    goto err;

  if ((r = array_alloc(&array, num)) < 0)
    goto err;

  if (!numbers->next)
    goto ret;

  for (i = 0; i < num; i++) {
    if ((r = array_get_value(array, i, &v)) < 0)
      goto err;
    if ((r = array_make_values(v, numbers->next)) < 0)
      goto err;
  }

ret:
  if ((r = value_set_array(value, array, 0)) < 0)
    goto err;
  return 0;

err:
  if (array) {
    array[0].value->refcount++;
    array_free(array, num);
  }
  return r;
}

int array_dump(FILE *fp, array_t array)
{
  int i, n;
  value_t value;

  if ((n = array_get_number(array)) < 0)
    n = -1;

  nll_wait_output(fp);
  fprintf(fp, "[%d](", n);

  for (i = 0; i < n; i++) {
    nll_wait_output(fp);
    if (i >= 4) {
      fprintf(fp, "...");
      break;
    }
    fprintf(fp, "[%d]", i);
    if (array_get_value(array, i, &value) < 0)
      ;
    else
      value_dump(fp, value);
  }

  fprintf(fp, ")");

  return 0;
}
