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

static int buffer_size = 0;

static area_t head;
static area_t tail;

#ifndef NLL_MEMORY_STATIC
static int _init(void)
{
  static struct area _head;
  head = &_head;
  return 0;
}

static int _check(void)
{
  return 0;
}

static area_t _alloc(int size)
{
  area_t area;
  if ((area = memory_alloc(sizeof(*area) + size)) == NULL)
    return NULL;
  return area;
}

static int _free(area_t area)
{
  memory_free(area);
  return 0;
}
#else
static char buffer[AREA_SIZE];

static int _init(void)
{
  memset(buffer, 0, sizeof(buffer));
  head = (area_t)buffer;
  return 0;
}

static int _check(void)
{
  return 0;
}

static area_t _alloc(int size)
{
  area_t area;

  area = (area_t)((char *)(tail + 1) + tail->area_size);

  if ((char *)(area + 1) + size > &buffer[AREA_SIZE])
    return NULL;

  return area;
}

static int _free(area_t area)
{
  return 0;
}
#endif

int area_check(void)
{
  if (buffer_size)
    return NLL_ERRCODE_AREA_NOT_EMPTY;

  if (head != tail)
    return NLL_ERRCODE_AREA_NOT_EMPTY;

  if (head->next || tail->prev)
    return NLL_ERRCODE_AREA_NOT_EMPTY;

  return _check();
}

int area_alloc(area_t *areap, int size)
{
  area_t area;
  int area_size;

  if (size < 0)
    return NLL_ERRCODE_MEMORY_INVALID_PARAMETER;

  area_size = ((size + 15) / 16) * 16;

  if ((area = _alloc(area_size)) == NULL)
    return NLL_ERRCODE_AREA_BUFFER_OVER;
  buffer_size += area_size;

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

  memset(area + 1, 0, area_size);
  area->size = size;
  area->area_size = area_size;

  area->next = tail->next;
  area->prev = tail;
  tail->next = area;

  tail = area;

  *areap = area;

  return 0;
}

int area_free(area_t area)
{
  int r;

  if (--(area->refcount) != 0)
    return 1;

  area->prev->next = area->next;
  if (area->next)
    area->next->prev = area->prev;
  if (area == tail)
    tail = area->prev;

  buffer_size -= area->area_size;
  if ((r = _free(area)) < 0)
    return r;

  return 0;
}

int area_init(void)
{
  int r;

  buffer_size = 0;

  if ((r = _init()) < 0)
    return r;

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

  head->prev      = NULL;
  head->next      = NULL;
  head->size      = 0;
  head->area_size = 0;

  tail = head;

  return 0;
}

int area_get_size(area_t area)
{
  return area->size;
}

char *area_get_buffer(area_t area)
{
  return (char *)(area + 1);
}

int area_copy_area(area_t area, int offset, area_t _area, int _offset, int size)
{
  char *p, *_p;

  if ((offset < 0) || (offset + size > area->size))
    return NLL_ERRCODE_AREA_OUT_OF_RANGE;
  if ((_offset < 0) || (_offset + size > _area->size))
    return NLL_ERRCODE_AREA_OUT_OF_RANGE;

  p = (char *)(area + 1) + offset;
  _p = (char *)(_area + 1) + _offset;

  memcpy(p, _p, size);

  return 0;
}

int area_read(area_t area, int offset, char *buffer, int size)
{
  char *p;

  if ((offset < 0) || (offset + size > area->size))
    return NLL_ERRCODE_AREA_OUT_OF_RANGE;

  p = (char *)(area + 1) + offset;

  memcpy(buffer, p, size);

  return 0;
}

int area_write(area_t area, int offset, char *buffer, int size)
{
  char *p;

  if ((offset < 0) || (offset + size > area->size))
    return NLL_ERRCODE_AREA_OUT_OF_RANGE;

  p = (char *)(area + 1) + offset;

  memcpy(p, buffer, size);

  return 0;
}

union val {
  char top;
  integer_t integer;
  unsigned char c;
  unsigned short s;
  unsigned int i;
  unsigned long l;
};

int area_read_integer(area_t area, int offset, integer_t *integerp, int size)
{
  int r;
  union val val;
  integer_t integer;

  if ((size != sizeof(val.c)) && (size != sizeof(val.s)) &&
      (size != sizeof(val.i)) && (size != sizeof(val.l)))
    size = sizeof(val.integer);

  memset(&val, 0, sizeof(val));

  if ((r = area_read(area, offset, &val.top, size)) < 0)
    return r;

  switch (size) {
  case sizeof(val.c): integer = val.c;       break;
  case sizeof(val.s): integer = val.s;       break;
  case sizeof(val.i): integer = val.i;       break;
  default:            integer = val.integer; break;
  }

  *integerp = integer;

  return 0;
}

int area_write_integer(area_t area, int offset, integer_t integer, int size)
{
  int r;
  union val val;

  if ((size != sizeof(val.c)) && (size != sizeof(val.s)) &&
      (size != sizeof(val.i)) && (size != sizeof(val.l)))
    size = sizeof(val.integer);

  memset(&val, 0, sizeof(val));

  switch (size) {
  case sizeof(val.c): val.c       = integer; break;
  case sizeof(val.s): val.s       = integer; break;
  case sizeof(val.i): val.i       = integer; break;
  default:            val.integer = integer; break;
  }

  if ((r = area_write(area, offset, &val.top, size)) < 0)
    return r;

  return 0;
}

int area_dump(FILE *fp, area_t area)
{
  char *p, c;
  int i;

  nll_wait_output(fp);
  fprintf(fp, "(%d)[", area->size);

  p = (char *)(area + 1);
  for (i = 0; i < area->size; p++, i++) {
    nll_wait_output(fp);
    if (i >= 64) {
      fprintf(fp, "...");
      break;
    }
    c = *p;
    c = isprint(c) ? c : '.';
    fprintf(fp, "%c", c);
  }

  fprintf(fp, "]");

  return 0;
}
