#include "config.h"

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

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

#include "nlltypes.h"
#include "nlllib.h"
#include "formula.h"
#include "command.h"
#include "line.h"
#include "label.h"
#include "value.h"
#include "string.h"
#include "variable.h"
#include "position.h"
#include "array.h"
#include "area.h"
#include "stack.h"
#include "memory.h"
#include "readline.h"
#include "fileid.h"
#include "queueid.h"
#include "key.h"
#include "framebuf.h"
#include "mouse.h"
#include "graphic.h"
#include "audio.h"
#include "sdl.h"
#include "image.h"
#include "sound.h"
#include "nll.h"
#include "nllmain.h"

int nll_init(FILE *in, FILE *out)
{
  nll_stdin  = in;
  nll_stdout = out;

  nll_clear_line(NULL);

  memory_init();

  variable_init();
  value_init();
  string_init();
  position_init();
  label_init();
  formula_init();
  command_init();
  line_init();
  array_init();
  area_init();
  stack_init();
  fileid_init();
  queueid_init();
#ifdef USE_FRAMEBUF
  framebuf_init();
#endif
#ifdef USE_SDL
  sdl_init();
#endif
#ifdef USE_GRAPHIC
  image_init();
  graphic_init();
#endif
#ifdef USE_AUDIO
  sound_init();
  audio_init();
#endif
  return 0;
}

int nll_new(void)
{
  nll_clear_line(NULL);

  position_clean();
  stack_clean();
  line_clear();
  variable_clean();

  return 0;
}

int nll_done(void)
{
  int r;

  fileid_done();
  queueid_done();
#ifdef USE_FRAMEBUF
  framebuf_done();
#endif
#ifdef USE_AUDIO
  audio_done();
  sound_done();
#endif
#ifdef USE_GRAPHIC
  graphic_done();
  image_done();
#endif
#ifdef USE_SDL
  sdl_done();
#endif

  nll_clear_line(NULL);

#ifndef NLL_CLEAR_VALUE
  variable_clear(); /* For link of the loop */
#endif

  position_clear();
  stack_clear();
  line_clear();
  variable_clean();

  nll_file_allclose();

  if ((r = variable_done()) < 0) return r;
  if ((r = value_done()   ) < 0) return r;
  if ((r = string_done()  ) < 0) return r;
  if ((r = label_done()   ) < 0) return r;
  if ((r = formula_done() ) < 0) return r;
  if ((r = command_done() ) < 0) return r;
  if ((r = line_done()    ) < 0) return r;

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

  if ((r = fileid_check()  ) < 0) return r;
  if ((r = queueid_check() ) < 0) return r;
#ifdef USE_AUDIO
  if ((r = audio_check()   ) < 0) return r;
  if ((r = sound_check()   ) < 0) return r;
#endif
#ifdef USE_GRAPHIC
  if ((r = graphic_check() ) < 0) return r;
  if ((r = image_check()   ) < 0) return r;
#endif
  if ((r = variable_check()) < 0) return r;
  if ((r = value_check()   ) < 0) return r;
  if ((r = string_check()  ) < 0) return r;
  if ((r = position_check()) < 0) return r;
  if ((r = label_check()   ) < 0) return r;
  if ((r = formula_check() ) < 0) return r;
  if ((r = array_check()   ) < 0) return r;
  if ((r = command_check() ) < 0) return r;
  if ((r = line_check()    ) < 0) return r;
  if ((r = area_check()    ) < 0) return r;
  if ((r = stack_check()   ) < 0) return r;

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

  nll_stdin_restore();
  nll_stdout_restore();

  nll_stdin  = NULL;
  nll_stdout = NULL;

  return 0;
}

static void truncate_line(char *buffer)
{
  int len;
  if (buffer) {
    len = strlen(buffer);
    while (len > 0) {
      if (!isspace(buffer[len - 1]))
	break;
      len--;
    }
    buffer[len] = '\0';
  }
}

static variable_t variable_argc = NULL, variable_argv = NULL;

int nll_make_argc(int argc)
{
  int r;

  if (!variable_argc) {
    if ((r = variable_get("ARGC", &variable_argc)) < 0)
      goto err;
  }

  variable_argc->value->flags &= ~VALUE_FLAG_CONST;
  if ((r = value_set_integer(variable_argc->value, argc)) < 0)
    goto err;
  variable_argc->value->flags |= VALUE_FLAG_CONST;

  return 0;

err:
  nll_del_argc();

  return r;
}

int nll_make_argv(int argc, char *argv[])
{
  int r, i;
  array_t array = NULL;
  value_t value;

  if (!variable_argv) {
    if ((r = variable_get("ARGV", &variable_argv)) < 0)
      goto err;
  }

  if ((r = array_alloc(&array, argc)) < 0)
    goto err;

  if (argv) {
    for (i = 0; i < argc; i++) {
      if ((r = array_get_value(array, i, &value)) < 0)
	goto err;
      if ((r = value_set_string(value, argv[i], -1)) < 0)
	goto err;
    }
  }

  variable_argv->value->flags &= ~VALUE_FLAG_CONST;
  if ((r = value_set_array(variable_argv->value, array, 0)) < 0)
    goto err;
  array = NULL;
  variable_argv->value->flags |= VALUE_FLAG_CONST;

  return 0;

err:
  if (array)
    array_free(array, -1);
  nll_del_argv();

  return r;
}

int nll_set_arg(int index, value_t arg)
{
  int r;
  array_t array;
  value_t value;

  if (!variable_argv)
    return NLL_ERRCODE_VARIABLE_NOT_FOUND;

  if ((r = value_get_array(variable_argv->value, &array, NULL)) < 0)
    return r;
  if ((r = array_get_value(array, index, &value)) < 0)
    return r;
  if ((r = value_copy_value(value, arg)) < 0)
    return r;

  return 0;
}

int nll_del_argc(void)
{
  int r = 0;

  if (variable_argc) {
    r = variable_del(variable_argc);
    variable_argc = NULL;
  }

  return r;
}

int nll_del_argv(void)
{
  int r = 0;

  if (variable_argv) {
    r = variable_del(variable_argv);
    variable_argv = NULL;
  }

  return r;
}

int nll_main(int argc, char *argv[], int quit, int auto_run, int auto_number, char *command)
{
  char *linebuf, *readlinebuf, *top, *p, *head;
  int finished = 0, r, number, number2, move, retcode = 0, direct_mode;
  line_t line;
  FILE *fp = NULL;
  char buffer[LINE_MAXLEN + 1 + 8]; /* +8 is for auto/line number space */

  nll_make_argc(argc);
  if (argc > 0)
    nll_make_argv(argc, argv);

  auto_number = (auto_number ? 1 : 0);

  while (!finished) {
    fp = nll_file_fp();
    if (!fp)
      auto_number = 0;

    linebuf = NULL;
    readlinebuf = NULL;

    if (fp) {
      linebuf = buffer + auto_number;
      if (!nll_fgets(linebuf, sizeof(buffer) - auto_number, fp)) {
	nll_file_close();
	continue;
      }
      truncate_line(linebuf);
    } else if (command) {
      linebuf = command;
      command = NULL;
    } else if (auto_run) {
      linebuf = "RUN";
      auto_run = 0;
    } else if (quit) {
      finished = 1;
      continue;
    } else if (nll_is_tty()) {
      nll_finished_clear();
      readlinebuf = nll_readline("nll> ");
      if (nll_is_finished()) { /* break by Ctrl+C */
	if (!readlinebuf)
	  write(1, "\n", 1); /* for libedit */
	linebuf = "";
      } else if (!readlinebuf) { /* exit by Ctrl+D */
	finished = 1;
	continue;
      } else { /* normal input */
	linebuf = readlinebuf;
	truncate_line(linebuf);
      }
    } else {
      finished = 1;
      continue;
    }

    p = linebuf;
    while (isspace(*p))
      p++;

    if (*p == '$') {
      direct_mode = 1;
      p++;
      while (isspace(*p))
	p++;
      top = p;
    } else {
      number = strtol(p, &top, 10);
      direct_mode = (!auto_number && (top == p)) ? 1 : 0;
    }

    if (direct_mode) {
      if ((r = nll_line_edit(-1, -1, top, &line, 0, 0)) < 0) {
	NLL_ERRPRINT(r, top);
      } else if (line) {
	nll_initialize_clear();
	nll_edit_clear();
	r = nll_parse(line);
	if (readlinebuf)
	  nll_add_history(nll_line_buffer(line));
	if (r >= 0) {
	  if ((r = nll_start(line, NULL, &retcode)) < 0)
	    finished = 1;
	}
	if ((r = nll_line_free(line)) < 0)
	  NLL_ERRPRINT(r, top);
	if (nll_is_initialize()) {
	  if ((r = nll_new()) < 0)
	    NLL_ERRPRINT(r, top);
	}
	if ((number = nll_edit()) >= 0) {
	  if ((r = line_get(number, &line)) >= 0) {
	    if (!line)
	      line = line_tail();
	    if (line)
	      nll_edit_line(number, line);
	  }
	}
      }
    } else { /* program mode */
      head = p;
      move = 0;
      number2 = -1;
      if (top == p) {
	number = 0;
	*(--top) = *p ? ' ' : '#';
      } else {
	p = top;
	while (isspace(*p))
	  p++;
	if ((*p == '-') || isdigit(*p)) {
	  move = 1;
	  number2 = strtol(p, &top, 10);
	}
      }
      if ((r = nll_line_edit(number, number2, top, &line, 1, move)) < 0) {
	NLL_ERRPRINT(r, top);
      } else {
	if (line)
	  nll_parse(line);
	if (readlinebuf) {
	  if (line)
	    strcpy(top, nll_line_buffer(line));
	  nll_add_history(head);
	}
      }
    }

    if (readlinebuf)
      free(readlinebuf);
  }

  nll_del_argv();
  nll_del_argc();

  return retcode;
}
