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

struct qentry {
  struct qentry *next;
  struct qentry *prev;
  value_t value;
};

typedef struct queueid {
  queueid_index_t index;
  struct qentry entry;
  int num;
} *queueid_t;

static queueid_t queueids[QUEUEID_MAXNUM];
static int queueid_num = 0;

static struct qentry *pool = NULL;

int queueid_init(void)
{
  queueid_num = 0;
  memset(queueids, 0, sizeof(queueids));
  return 0;
}

int queueid_done(void)
{
  queueid_index_t index;
  struct qentry *entry;

  for (index = 0; index < QUEUEID_MAXNUM; index++) {
    if (queueids[index])
      queueid_destroy(index);
  }

  while (pool) {
    entry = pool;
    pool = pool->next;
    memory_free(entry);
  }

  return 0;
}

int queueid_check(void)
{
  queueid_index_t index;

  if (queueid_num)
    return NLL_ERRCODE_MEMORY_NOT_EMPTY;

  for (index = 0; index < QUEUEID_MAXNUM; index++) {
    if (queueids[index])
      return NLL_ERRCODE_MEMORY_NOT_EMPTY;
  }

  if (pool)
    return NLL_ERRCODE_MEMORY_NOT_EMPTY;

  return 0;
}

static queueid_t _alloc(void)
{
  queueid_t queueid;
  if ((queueid = memory_alloc(sizeof(*queueid))) == NULL)
    return NULL;
  return queueid;
}

static int _free(queueid_t queueid)
{
  memory_free(queueid);
  return 0;
}

static queueid_t queueid_get(queueid_index_t index)
{
  if ((index < 0) || (index >= QUEUEID_MAXNUM))
    return NULL;

  return queueids[index];
}

static int queueid_is_empty(queueid_t queueid)
{
  return (queueid->entry.next == &queueid->entry) ? 1 : 0;
}

static int qentry_free(struct qentry *entry)
{
  if (entry->value)
    value_free(entry->value);
  entry->next = pool;
  pool = entry;
  return 0;
}

static struct qentry *qentry_alloc(value_t value)
{
  struct qentry *entry;

  if (pool) {
    entry = pool;
    pool = pool->next;
  } else {
    if ((entry = memory_alloc(sizeof(*entry))) == NULL)
      goto err;
  }
  memset(entry, 0, sizeof(*entry));

  if (value_alloc(&entry->value) < 0)
    goto err;

  if (value_copy_value(entry->value, value) < 0)
    goto err;

  return entry;

err:
  if (entry)
    qentry_free(entry);
  return NULL;
}

int queueid_number(queueid_index_t index)
{
  queueid_t queueid;

  queueid = queueid_get(index);
  if (!queueid)
    return -1;

  return queueid->num;
}

static int qentry_insert_next(struct qentry *prev, value_t value)
{
  struct qentry *entry;

  if ((entry = qentry_alloc(value)) == NULL)
    return -1;

  prev->next->prev = entry;
  entry->next = prev->next;
  prev->next = entry;
  entry->prev = prev;

  return 0;
}

static int qentry_delete(struct qentry *entry, value_t value)
{
  if (value) {
    if (value_copy_value(value, entry->value) < 0)
      return -1;
  }

  entry->next->prev = entry->prev;
  entry->prev->next = entry->next;

  qentry_free(entry);

  return 0;
}

int queueid_headin(queueid_index_t index, value_t value)
{
  queueid_t queueid;

  queueid = queueid_get(index);
  if (!queueid)
    return -1;

  if (qentry_insert_next(&queueid->entry, value) < 0)
    return -1;
  queueid->num++;

  return 0;
}

int queueid_tailin(queueid_index_t index, value_t value)
{
  queueid_t queueid;

  queueid = queueid_get(index);
  if (!queueid)
    return -1;

  if (qentry_insert_next(queueid->entry.prev, value) < 0)
    return -1;
  queueid->num++;

  return 0;
}

int queueid_headout(queueid_index_t index, value_t value)
{
  queueid_t queueid;

  queueid = queueid_get(index);
  if (!queueid)
    return -1;

  if (queueid_is_empty(queueid)) {
    if (value_set_null(value) < 0)
      return -1;
  } else {
    if (qentry_delete(queueid->entry.next, value) < 0)
      return -1;
    queueid->num--;
  }

  return 0;
}

int queueid_tailout(queueid_index_t index, value_t value)
{
  queueid_t queueid;

  queueid = queueid_get(index);
  if (!queueid)
    return -1;

  if (queueid_is_empty(queueid)) {
    if (value_set_null(value) < 0)
      return -1;
  } else {
    if (qentry_delete(queueid->entry.prev, value) < 0)
      return -1;
    queueid->num--;
  }

  return 0;
}

static int queueid_entry_init(queueid_t queueid)
{
  queueid->entry.next = &queueid->entry;
  queueid->entry.prev = &queueid->entry;
  queueid->num = 0;
  return 0;
}

static int queueid_entry_done(queueid_t queueid)
{
  while (!queueid_is_empty(queueid)) {
    if (qentry_delete(queueid->entry.next, NULL) < 0)
      return -1;
    queueid->num--;
  }
  return 0;
}

int queueid_destroy(queueid_index_t index)
{
  queueid_t queueid;
  int r;

  queueid = queueid_get(index);
  if (!queueid)
    return -1;

  queueids[queueid->index] = NULL;

  if (queueid_entry_done(queueid) < 0)
    return -1;
  memset(queueid, 0, sizeof(*queueid));

  queueid_num--;
  if ((r = _free(queueid)) < 0)
    return r;

  return 0;
}

queueid_index_t queueid_create(void)
{
  queueid_index_t index;
  queueid_t queueid = NULL;

  for (index = 0; index < QUEUEID_MAXNUM; index++) {
    if (!queueids[index])
      break;
  }
  if (index == QUEUEID_MAXNUM)
    return -1;

  if ((queueid = _alloc()) == NULL)
    return -1;
  queueid_num++;

  memset(queueid, 0, sizeof(*queueid));
  queueid->index = index;
  if (queueid_entry_init(queueid) < 0)
    return -1;

  queueids[index] = queueid;

  return queueid->index;
}
