#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 "constval.h"
#include "constval-ext.h"
#include "function.h"
#include "variable.h"

static int variable_num = 0;
static int pool_num = 0;

static variable_t head = NULL;
static variable_t pool = NULL;

static variable_t pool_alloc(void)
{
  variable_t variable;

  if (!pool)
    return NULL;

  variable = pool;
  pool = pool->next;
  pool_num--;

  return variable;
}

static int pool_free(variable_t variable)
{
  variable->next = pool;
  pool = variable;
  pool_num++;
  return 0;
}

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

static int _done(void)
{
  while (pool)
    memory_free(pool_alloc());
  return 0;
}

static int _check(void)
{
  if (pool)
    return NLL_ERRCODE_VARIABLE_NOT_EMPTY;
  return 0;
}

static variable_t _alloc(void)
{
  variable_t variable;
  variable = pool_alloc();
  if (!variable)
    variable = memory_alloc(sizeof(*variable));
  return variable;
}

static int _free(variable_t variable)
{
  if ((VARIABLE_MAXNUM < 0) || (pool_num < VARIABLE_MAXNUM))
    return pool_free(variable);
  memory_free(variable);
  return 0;
}
#else
static struct variable variables[VARIABLE_MAXNUM];

static int _init(void)
{
  variable_t variable;
  int i;

  memset(variables, 0, sizeof(variables));

  for (i = 0; i < VARIABLE_MAXNUM; i++) {
    variable = &variables[i];
    variable->next = pool;
    pool = variable;
    pool_num++;
  }

  return 0;
}

static int _done(void)
{
  return 0;
}

static int _check(void)
{
  variable_t variable;
  int n = 0;

  for (variable = pool; variable && (n < VARIABLE_MAXNUM); variable = variable->next)
    n++;

  if (variable || (n != VARIABLE_MAXNUM) || (n != pool_num))
    return NLL_ERRCODE_VARIABLE_NOT_EMPTY;

  return 0;
}

static variable_t _alloc(void)
{
  return pool_alloc();
}

static int _free(variable_t variable)
{
  return pool_free(variable);
}
#endif

int variable_check(void)
{
  if (variable_num || head)
    return NLL_ERRCODE_VARIABLE_NOT_EMPTY;

  return _check();
}

static int variable_free(variable_t variable)
{
  variable_t next;
  int r;

  for (; variable; variable = next) {
    next = variable->next;

    if (variable->value) {
#ifdef NLL_CLEAR_VALUE
      value_clear(variable->value); /* For link of the loop */
#endif
      value_free(variable->value);
    }

    variable_num--;
    if ((r = _free(variable)) < 0)
      return r;
  }

  return 0;
}

static int variable_alloc(variable_t *variablep)
{
  variable_t variable;
  int r;

  if ((variable = _alloc()) == NULL)
    return NLL_ERRCODE_VARIABLE_BUFFER_OVER;
  variable_num++;

  memset(variable, 0, sizeof(*variable));

  if ((r = value_alloc(&variable->value)) < 0) {
    variable_free(variable);
    return r;
  }

  *variablep = variable;

  return 0;
}

int variable_del(variable_t variable)
{
  if ((--(variable->refcount) == 0) &&
      (variable->value->type == VALUE_TYPE_UNDEFINED)) {
    if (variable->next)
      variable->next->prev = variable->prev;
    if (variable->prev)
      variable->prev->next = variable->next;
    if (head == variable)
      head = variable->next;
    variable->next = variable->prev = NULL;
    variable_free(variable);
  }

  return 0;
}

int variable_clean(void)
{
  variable_t variable, next;

  for (variable = head; variable; variable = next) {
    next = variable->next;
    if (variable->refcount == 0) {
      if (variable->next)
	variable->next->prev = variable->prev;
      if (variable->prev)
	variable->prev->next = variable->next;
      if (head == variable)
	head = variable->next;
      variable->next = variable->prev = NULL;
      variable_free(variable);
    }
  }

  return 0;
}

int variable_clear(void)
{
  variable_t variable;
  int r;

  for (variable = head; variable; variable = variable->next) {
    if ((r = value_clear(variable->value)) < 0)
      return r;
  }

  return 0;
}

int variable_new(variable_t variable)
{
  int r;

  if (variable->value) {
    if (variable->value->flags & VALUE_FLAG_CONST)
      return NLL_ERRCODE_VALUE_CONST_VALUE;
    if (variable->value->flags & VALUE_FLAG_RDONLY)
      return 0;
#ifdef NLL_CLEAR_VALUE
    value_clear(variable->value);
#endif
    value_free(variable->value);
    variable->value = NULL;
  }

  if ((r = value_alloc(&variable->value)) < 0)
    return r;

  return 0;
}

int variable_init(void)
{
  variable_num = 0;
  pool_num = 0;
  head = NULL;
  pool = NULL;
  return _init();
}

int variable_done(void)
{
  return _done();
}

struct const_variables {
  const struct const_variable *var;
#ifdef NLL_FLOATING_POINT
  const double *floating;
#endif
};

static int variable_const(struct const_variables *vars, variable_t variable)
{
  int r;
  const struct const_variable *p;
#ifdef NLL_FLOATING_POINT
  const double *dp;
#endif

#ifdef NLL_FLOATING_POINT
  dp = vars->floating;
#endif

  for (p = vars->var; p->name; p++) {
    if (!strcmp(variable->name, p->name)) {
      switch (p->type) {
      case VALUE_TYPE_UNDEFINED:
	if ((r = value_clear(variable->value)) < 0)
	  return r;
	break;
      case VALUE_TYPE_NULL:
	if ((r = value_set_null(variable->value)) < 0)
	  return r;
	break;
      case VALUE_TYPE_INTEGER:
	if ((r = value_set_integer(variable->value, p->integer)) < 0)
	  return r;
	break;
      case VALUE_TYPE_STRING:
	if ((r = value_set_string(variable->value, p->string, -1)) < 0)
	  return r;
	break;
#ifdef NLL_FLOATING_POINT
      case VALUE_TYPE_FLOAT:
	if (!dp)
	  return NLL_ERRCODE_VALUE_INVALID_TYPE;
	if ((r = value_set_float(variable->value, *dp)) < 0)
	  return r;
	break;
#endif
      case VALUE_TYPE_ARRAY:
	if ((r = value_set_array(variable->value, p->pointer, 0)) < 0)
	  return r;
	break;
      case VALUE_TYPE_AREA:
	if ((r = value_set_area(variable->value, p->pointer)) < 0)
	  return r;
	break;
      case VALUE_TYPE_POINTER:
	if ((r = value_set_pointer(variable->value, p->pointer, 0)) < 0)
	  return r;
	break;
      case VALUE_TYPE_FUNCTION:
	if ((r = value_set_function(variable->value, p->pointer)) < 0)
	  return r;
	break;
      case VALUE_TYPE_LABEL:
	if ((r = value_set_label(variable->value, p->pointer)) < 0)
	  return r;
	break;
      default:
	return NLL_ERRCODE_VALUE_INVALID_TYPE;
      }
      variable->value->flags = p->flags;
      return 1;
    }
#ifdef NLL_FLOATING_POINT
    if (dp && (p->type == VALUE_TYPE_FLOAT))
      dp++;
#endif
  }

  return 0;
}

int variable_get(const char *name, variable_t *variablep)
{
  variable_t variable;
  function_t function;
  struct const_variables vars;
  int r;

  for (variable = head; variable; variable = variable->next) {
    if (variable->name[0] && !strcmp(variable->name, name))
      break;
  }

  if (!variable) {
    if (strlen(name) > VARIABLE_NAMELEN)
      return NLL_ERRCODE_VARIABLE_TOO_LONG;

    if ((r = variable_alloc(&variable)) < 0)
      return r;

    strcpy(variable->name, name);

    vars.var = const_variables;
#ifdef NLL_FLOATING_POINT
    vars.floating = const_variables_floating;
#endif
    r = variable_const(&vars, variable);

#ifdef NLL_EXTEND_CONSTVAL
    if (!r) {
      vars.var = const_variables_ext;
#ifdef NLL_FLOATING_POINT
      vars.floating = NULL;
#endif
      r = variable_const(&vars, variable);
    }
#endif

    if (r < 0) {
      variable_free(variable);
      return r;
    }

    if ((r = function_search(name, &function)) < 0) {
      /* not function */
    } else { /* function */
      if ((r = value_set_function(variable->value, function)) < 0)
	return r;
      variable->value->flags |= VALUE_FLAG_CONST;
    }

    variable->next = head;
    variable->prev = NULL;
    if (head)
      head->prev = variable;
    head = variable;

    r = 1;
  } else {
    r = 0;
  }

  variable->refcount++;

  *variablep = variable;

  return r;
}

int variable_dump(FILE *fp, variable_t variable)
{
  if (!variable)
    variable = head;

  nll_wait_output(fp);
  fprintf(fp, "\n-- Variable dump --\n");

  for (; variable; variable = variable->next) {
    nll_wait_output(fp);
    fprintf(fp, "%s:\tRef:%d\tValue:", variable->name, variable->refcount);
    if (variable->value->flags & VALUE_FLAG_CONST)
      fprintf(fp, "(C)");
    if (variable->value->flags & VALUE_FLAG_RDONLY)
      fprintf(fp, "(R)");
    if (variable->value->flags & VALUE_FLAG_STATIC)
      fprintf(fp, "(S)");
    value_dump(fp, variable->value);
    fprintf(fp, "\n");
  }

  fflush(fp);

  return 0;
}

static int const_list(FILE *fp, struct const_variables *vars)
{
  const struct const_variable *p;
#ifdef NLL_FLOATING_POINT
  const double *dp;
#endif

#ifdef NLL_FLOATING_POINT
  dp = vars->floating;
#endif

  for (p = vars->var; p->name; p++) {
    nll_wait_output(fp);
    fprintf(fp, "%s", p->name);
    switch (p->type) {
    case VALUE_TYPE_INTEGER:
      fprintf(fp, "\t%ld(0x%lx)", p->integer, p->integer);
      break;
    case VALUE_TYPE_STRING:
      if (p->string)
	fprintf(fp, "\t\"%s\"", p->string);
      break;
#ifdef NLL_FLOATING_POINT
    case VALUE_TYPE_FLOAT:
      if (dp)
	fprintf(fp, "\t%f(FLOAT)", *dp);
      break;
#endif
    default:
      fprintf(fp, "\t(%s)", value_typename_string(p->type));
      break;
    }
    fprintf(fp, "\n");
#ifdef NLL_FLOATING_POINT
    if (dp && (p->type == VALUE_TYPE_FLOAT))
      dp++;
#endif
  }

  fflush(fp);

  return 0;
}

int variable_const_list(FILE *fp)
{
  struct const_variables vars;

  vars.var = const_variables;
#ifdef NLL_FLOATING_POINT
  vars.floating = const_variables_floating;
#endif
  const_list(fp, &vars);

#ifdef NLL_EXTEND_CONSTVAL
  vars.var = const_variables_ext;
#ifdef NLL_FLOATING_POINT
  vars.floating = NULL;
#endif
  const_list(fp, &vars);
#endif

  return 0;
}
