#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 "command.h"
#include "line.h"
#include "position.h"
#include "nll.h"

static int line_num = 0;
static int pool_num = 0;
static int list_num = 0;

static line_t head = NULL;
static line_t tail = NULL;
static line_t pool = NULL;

static line_t pool_alloc(void)
{
  line_t line;

  if (!pool)
    return NULL;

  line = pool;
  pool = pool->next;
  pool_num--;

  return line;
}

static int pool_free(line_t line)
{
  line->next = pool;
  pool = line;
  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_LINE_NOT_EMPTY;
  return 0;
}

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

static int _free(line_t line)
{
  if ((LINE_MAXNUM < 0) || (pool_num < LINE_MAXNUM))
    return pool_free(line);
  memory_free(line);
  return 0;
}
#else
static struct line lines[LINE_MAXNUM];

static int _init(void)
{
  line_t line;
  int i;

  memset(lines, 0, sizeof(lines));

  for (i = 0; i < LINE_MAXNUM; i++) {
    line = &lines[i];
    line->next = pool;
    pool = line;
    pool_num++;
  }

  return 0;
}

static int _done(void)
{
  return 0;
}

static int _check(void)
{
  line_t line;
  int n = 0;

  for (line = pool; line && (n < LINE_MAXNUM); line = line->next)
    n++;

  if (line || (n != LINE_MAXNUM) || (n != pool_num))
    return NLL_ERRCODE_LINE_NOT_EMPTY;

  return 0;
}

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

static int _free(line_t line)
{
  return pool_free(line);
}
#endif

int line_check(void)
{
  if (line_num || list_num || head || tail)
    return NLL_ERRCODE_LINE_NOT_EMPTY;

  return _check();
}

int line_alloc(line_t *linep)
{
  line_t line;

  if ((line = _alloc()) == NULL)
    return NLL_ERRCODE_LINE_BUFFER_OVER;
  line_num++;

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

  *linep = line;

  return 0;
}

int line_free(line_t line)
{
  int r;
  position_t position;
  struct spot s;

  command_free(line->commands);

  if ((r = nll_clear_line(line)) < 0) return r;

  s.line    = line;
  s.command = NULL;
  if ((r = position_get(&s, &position)) < 0) return r;

  if (position) {
    if ((r = position_reset(position)) < 0) return r;
  }

  line_num--;
  if ((r = _free(line)) < 0)
    return r;

  return 0;
}

int line_clear(void)
{
  int r;
  line_t line;

  while (head) {
    line = head;
    head = line->next;
    if (!head)
      tail = NULL;
    list_num--;
    line->next = NULL;
    line->prev = NULL;
    if ((r = line_free(line)) < 0)
      return r;
  }

  return 0;
}

int line_clean(void)
{
  line_t line;
  int r;

  for (line = head; line; line = line->next) {
    if ((r = command_clean(line->commands)) < 0)
      return r;
  }

  return 0;
}

int line_init(void)
{
  line_num = 0;
  pool_num = 0;
  list_num = 0;
  head = NULL;
  tail = NULL;
  pool = NULL;
  return _init();
}

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

line_t line_head(void)
{
  return head;
}

line_t line_tail(void)
{
  return tail;
}

int line_list_num(void)
{
  return list_num;
}

int line_is_exist(line_t line)
{
  line_t ln;

  for (ln = head; ln; ln = ln->next) {
    if (ln == line)
      return 1;
  }

  return 0;
}

int line_get(int number, line_t *linep)
{
  line_t line;
  int n = 0;

  for (line = head; line; line = line->next) {
    n++;
    if (number == n) {
      *linep = line;
      return 0;
    }
  }

  *linep = NULL;
  return 0;
}

int line_set(int number, int number2, char *buffer, line_t *linep, int save, int move)
{
  line_t line = NULL, *p = NULL, *p2 = NULL;
  int r, len = 0, n;

  if (buffer)
    len = strlen(buffer);

  if (len > LINE_MAXLEN)
    return NLL_ERRCODE_LINE_TOO_LONG;

  if (move) {
    n = 0;
    if (number2 > 0) {
      for (p2 = &head; *p2 && (number2 - 1 > n); p2 = &((*p2)->next))
	n++;
    } else {
      for (p2 = &tail; *p2 && (number2 < n); p2 = &((*p2)->prev))
	n--;
      p2 = *p2 ? &((*p2)->next) : &head;
    }
  }

  if (save) {
    n = 0;
    if (number > 0) {
      for (p = &head; *p && (number - 1 > n); p = &((*p)->next))
	n++;
    } else {
      if ((len == 0) || p2)
	number--; /* delete or move */
      for (p = &tail; *p && (number < n); p = &((*p)->prev))
	n--;
      p = *p ? &((*p)->next) : &head;
    }
  }

  if (!p) { /* direct mode */
    if (len == 0) { /* empty line */
      if (linep)
	*linep = NULL;
      return 0;
    }
  } else { /* program mode */
    if ((len == 0) && !p2) { /* delete line */
      line = *p;
      if (*p) {
	*p = (*p)->next;
	if (*p)
	  (*p)->prev = line->prev;
	else
	  tail = line->prev;
	list_num--;
	line->next = NULL;
	line->prev = NULL;
	r = line_free(line);
	if (r < 0) return r;
      }
      if (linep)
	*linep = NULL;
      return 0;
    }

    if (p2) { /* move line */
      line = *p;
      if (*p) {
	if (p2 == &((*p)->next))
	  p2 = p;
	*p = (*p)->next;
	if (*p)
	  (*p)->prev = line->prev;
	else
	  tail = line->prev;
	list_num--;
	line->next = NULL;
	line->prev = NULL;
	if (len == 0) { /* no changed */
	  buffer = NULL;
	} else { /* changed */
	  r = line_free(line);
	  if (r < 0) return r;
	  line = NULL;
	}
      }
      if (!line && (len == 0)) {
	if (linep)
	  *linep = NULL;
	return 0;
      }
      p = p2;
    }
  }

  if (!line) {
    if ((r = line_alloc(&line)) < 0)
      return r;
  }

  if (buffer) {
    memcpy(line->buffer, buffer, len);
    line->buffer[len] = '\0';
  }

  if (p) { /* program mode */
    line->next = *p;
    if (*p) {
      line->prev = (*p)->prev;
      (*p)->prev = line;
    } else {
      line->prev = tail;
      tail = line;
    }
    list_num++;
    *p = line;
  }

  if (linep)
    *linep = buffer ? line : NULL;

  return 0;
}

int line_print(FILE *fp, int start_number, int end_number)
{
  line_t line;
  int number = 0;

  for (line = head; line; line = line->next) {
    number++;
    if ((start_number >= 0) && (number < start_number))
      continue;
    if ((end_number >= 0) && (number > end_number))
      continue;
    nll_wait_output(fp);
    fprintf(fp, "%d%s\n", number, line->buffer);
  }

  fflush(fp);

  return 0;
}

int line_dump(FILE *fp, line_t line)
{
  char *p;

  if (!line)
    line = head;

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

  for (; line; line = line->next) {
    nll_wait_output(fp);
    for (p = line->buffer; isspace(*p); p++)
      ;
    fprintf(fp, "%s\n", p);
    command_dump(fp, line->commands);
  }

  fflush(fp);

  return 0;
}
