#include "config.h"

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

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

#include "nlltypes.h"
#include "nlllib.h"
#include "memory.h"
#include "string.h"

static int string_num = 0;
static int pool_num = 0;

static string_t head = NULL;
static string_t pool = NULL;

static string_t pool_alloc(void)
{
  string_t string;

  if (!pool)
    return NULL;

  string = pool;
  pool = pool->next;
  pool_num--;

  return string;
}

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

#ifndef NLL_MEMORY_STATIC
static char *buffer = NULL;

static int _init(void)
{
  buffer = memory_alloc(STRING_MAXLEN + 1);
  if (buffer == NULL)
    return NLL_ERRCODE_STRING_BUFFER_OVER;
  return 0;
}

static int _done(void)
{
  while (pool)
    memory_free(pool_alloc());
  if (buffer) {
    memory_free(buffer);
    buffer = NULL;
  }
  return 0;
}

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

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

static int _free(string_t string)
{
  if ((STRING_MAXNUM < 0) || (pool_num < STRING_MAXNUM))
    return pool_free(string);
  memory_free(string);
  return 0;
}
#else
static struct string strings[STRING_MAXNUM];
static char buffers[STRING_MAXNUM][STRING_MAXLEN + 1];
static char buffer[STRING_MAXLEN + 1];

static int _init(void)
{
  string_t string;
  int i;

  memset(strings, 0, sizeof(strings));
  memset(buffers, 0, sizeof(buffers));

  for (i = 0; i < STRING_MAXNUM; i++) {
    string = &strings[i];
    string->next = pool;
    pool = string;
    pool_num++;
  }

  return 0;
}

static int _done(void)
{
  return 0;
}

static int _check(void)
{
  string_t string;
  int n = 0;

  for (string = pool; string && (n < STRING_MAXNUM); string = string->next)
    n++;

  if (string || (n != STRING_MAXNUM) || (n != pool_num))
    return NLL_ERRCODE_STRING_NOT_EMPTY;

  return 0;
}

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

static int _free(string_t string)
{
  return pool_free(string);
}
#endif

int string_check(void)
{
  if (string_num || head)
    return NLL_ERRCODE_STRING_NOT_EMPTY;

  return _check();
}

static int _string_free(string_t string)
{
  int r;

#ifndef NLL_MEMORY_STATIC
  if (string->s)
    memory_free(string->s);
#endif
  string->s = NULL;

  string_num--;
  if ((r = _free(string)) < 0)
    return r;

  return 0;
}

int string_alloc(string_t *stringp)
{
  string_t string;
  int r;

  if ((string = _alloc()) == NULL)
    goto err;
  string_num++;

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

#ifdef NLL_MEMORY_STATIC
  string->size = STRING_MAXLEN + 1;
  string->s = buffers[string - strings];
#else
  string->size = 16;
  string->s = memory_alloc(string->size);
#endif

  if (string->s == NULL)
    goto err;

  memset(string->s, 0, string->size);

  string->next = head;
  head = string;

  *stringp = string;

  return 0;

err:
  if (string) {
    if ((r = _string_free(string)) < 0)
      return r;
  }

  return NLL_ERRCODE_STRING_BUFFER_OVER;
}

int string_free(string_t string)
{
  string_t next;
  int r;

  for (; string; string = next) {
    next = string->next;
    string->next = NULL;
    if ((r = _string_free(string)) < 0)
      return r;
  }

  return 0;
}

int string_del(string_t string)
{
  string_t *p, next;

  for (p = &head; *p;) {
    if (*p == string) {
      next = (*p)->next;
      (*p)->next = NULL;
      string_free(*p);
      *p = next;
      continue;
    }
    p = &((*p)->next);
  }

  return 0;
}

int string_init(void)
{
  string_num = 0;
  pool_num = 0;
  head = NULL;
  pool = NULL;
  return _init();
}

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

int string_set(string_t string, const char *s, int length)
{
  if (length < 0)
    length = strlen(s);

  if (length > STRING_MAXLEN)
    return NLL_ERRCODE_STRING_TOO_LONG;

#ifndef NLL_MEMORY_STATIC
  if (string->size < length + 1) {
    while (string->size < length + 1)
      string->size *= 2;
    if (string->size > STRING_MAXLEN + 1)
      string->size = STRING_MAXLEN + 1;

    memory_free(string->s);
    string->s = memory_alloc(string->size);
    if (string->s == NULL)
      return NLL_ERRCODE_STRING_BUFFER_OVER;

    memset(string->s, 0, string->size);
  }
#endif

  memcpy(string->s, s, length);

  string->length = length;
  string->s[length] = '\0';

  return 0;
}

char *string_buffer(void)
{
  return buffer;
}

int string_buffer_size(void)
{
  return STRING_MAXLEN + 1;
}

int string_dump_string(FILE *fp, string_t string)
{
  char *p, escape, c;
  int i;

  nll_wait_output(fp);
  fprintf(fp, "\"");

  for (p = string->s, i = 0; i < string->length; p++, i++) {
    nll_wait_output(fp);
    if (i >= 64) {
      fprintf(fp, "...");
      break;
    }
    escape = 0;
    switch (*p) {
    case '\t': escape = 1; c = 't'; break;
    case '\n': escape = 1; c = 'n'; break;
    case '\v': escape = 1; c = 'v'; break;
    case '\f': escape = 1; c = 'f'; break;
    case '\r': escape = 1; c = 'r'; break;
    case '\a': escape = 1; c = 'a'; break;
    default: c = *p; c = isprint(c) ? c : '.'; break;
    }
    fprintf(fp, "%s%c", (escape ? "\\" : ""), c);
  }

  fprintf(fp, "\"");

  return 0;
}

int string_dump(FILE *fp, string_t string)
{
  if (!string)
    string = head;

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

  for (; string; string = string->next) {
    string_dump_string(fp, string);
    fprintf(fp, "\n");
  }

  fflush(fp);

  return 0;
}
