#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 "label.h"

static int label_num = 0;
static int pool_num = 0;

static label_t head = NULL;
static label_t pool = NULL;

static label_t pool_alloc(void)
{
  label_t label;

  if (!pool)
    return NULL;

  label = pool;
  pool = pool->next;
  pool_num--;

  return label;
}

static int pool_free(label_t label)
{
  label->next = pool;
  pool = label;
  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_LABEL_NOT_EMPTY;
  return 0;
}

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

static int _free(label_t label)
{
  if ((LABEL_MAXNUM < 0) || (pool_num < LABEL_MAXNUM))
    return pool_free(label);
  memory_free(label);
  return 0;
}
#else
static struct label labels[LABEL_MAXNUM];

static int _init(void)
{
  label_t label;
  int i;

  memset(labels, 0, sizeof(labels));

  for (i = 0; i < LABEL_MAXNUM; i++) {
    label = &labels[i];
    label->next = pool;
    pool = label;
    pool_num++;
  }

  return 0;
}

static int _done(void)
{
  return 0;
}

static int _check(void)
{
  label_t label;
  int n = 0;

  for (label = pool; label && (n < LABEL_MAXNUM); label = label->next)
    n++;

  if (label || (n != LABEL_MAXNUM) || (n != pool_num))
    return NLL_ERRCODE_LABEL_NOT_EMPTY;

  return 0;
}

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

static int _free(label_t label)
{
  return pool_free(label);
}
#endif

int label_check(void)
{
  if (label_num || head)
    return NLL_ERRCODE_LABEL_NOT_EMPTY;

  return _check();
}

static int label_alloc(label_t *labelp)
{
  label_t label;

  if ((label = _alloc()) == NULL)
    return NLL_ERRCODE_LABEL_BUFFER_OVER;
  label_num++;

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

  *labelp = label;

  return 0;
}

static int label_free(label_t label)
{
  label_t next;
  int r;

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

    label_num--;
    if ((r = _free(label)) < 0)
      return r;
  }

  return 0;
}

int label_init(void)
{
  label_num = 0;
  pool_num = 0;
  head = NULL;
  pool = NULL;
  return _init();
}

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

int label_get(const char *name, label_t *labelp)
{
  label_t label;

  for (label = head; label; label = label->next) {
    if (!strcmp(label->name, name)) {
      *labelp = label;
      return 0;
    }
  }

  return NLL_ERRCODE_LABEL_NOT_FOUND;
}

int label_set(const char *name, spot_t spot, label_t *labelp)
{
  label_t label;
  int r;

  if (strlen(name) > LABEL_NAMELEN)
    return NLL_ERRCODE_LABEL_TOO_LONG;

  if (label_get(name, &label) == 0) {
    if (spot && label->spot.line)
      return NLL_ERRCODE_LABEL_DUPLICATE;
  } else {
    if ((r = label_alloc(&label)) < 0)
      return r;

    strcpy(label->name, name);

    label->next = head;
    label->prev = NULL;
    if (head)
      head->prev = label;
    head = label;
  }

  if (spot) {
    label->spot.line    = spot->line;
    label->spot.command = spot->command;
  }

  label->refcount++;

  *labelp = label;

  return 0;
}

int label_clear(label_t label)
{
  label->spot.line    = NULL;
  label->spot.command = NULL;
  return 0;
}

int label_clean(label_t label)
{
  return 0;
}

int label_del(label_t label)
{
  if (--(label->refcount) == 0) {
    if (label->next)
      label->next->prev = label->prev;
    if (label->prev)
      label->prev->next = label->next;
    if (head == label)
      head = label->next;
    label->next = label->prev = NULL;
    label_free(label);
  }

  return 0;
}

int label_dump(FILE *fp, label_t label)
{
  if (!label)
    label = head;

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

  for (; label; label = label->next) {
    nll_wait_output(fp);
    fprintf(fp, ".%s\tRef:%d\n", label->name, label->refcount);
  }

  fflush(fp);

  return 0;
}
