#include "config.h"

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

#ifdef WIN32
#include <windows.h>
#endif

#ifndef NLL_OUTPUT_NOWAIT
#ifndef WIN32
#include <time.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/select.h>
#else
#include <winsock2.h>
#endif
#endif

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

#include "nlllib.h"
#include "memory.h"

FILE *nll_stdin  = NULL;
FILE *nll_stdout = NULL;

void nll_error_print(const char *filename, int line,
		     int errcode, const char *param)
{
  if (!param)
    param = "(?)";

  switch (errcode) {
  case NLL_ERRCODE_FILE_NOT_FOUND:
    fprintf(nll_stdout, "File not found: %s\n", param);
    break;
  case NLL_ERRCODE_LINE_BUFFER_OVER:
    fprintf(nll_stdout, "Line buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_LINE_NOT_EMPTY:
    fprintf(nll_stdout, "Line not empty: %s\n", param);
    break;
  case NLL_ERRCODE_LINE_TOO_LONG:
    fprintf(nll_stdout, "Too long line: %s\n", param);
    break;
  case NLL_ERRCODE_LINE_NOT_FOUND:
    fprintf(nll_stdout, "Line not found: %s\n", param);
    break;
  case NLL_ERRCODE_VARIABLE_NOT_EMPTY:
    fprintf(nll_stdout, "Variable not empty: %s\n", param);
    break;
  case NLL_ERRCODE_VARIABLE_INVALID_NAME:
    fprintf(nll_stdout, "Invalid variable name: %s\n", param);
    break;
  case NLL_ERRCODE_VARIABLE_TOO_LONG:
    fprintf(nll_stdout, "Too long variable: %s\n", param);
    break;
  case NLL_ERRCODE_VARIABLE_BUFFER_OVER:
    fprintf(nll_stdout, "Variable buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_VARIABLE_NOT_FOUND:
    fprintf(nll_stdout, "Variable not found: %s\n", param);
    break;
  case NLL_ERRCODE_VALUE_NOT_EMPTY:
    fprintf(nll_stdout, "Value not empty: %s\n", param);
    break;
  case NLL_ERRCODE_VALUE_BUFFER_OVER:
    fprintf(nll_stdout, "Value buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_VALUE_INVALID_TYPE:
    fprintf(nll_stdout, "Invalid value type: %s\n", param);
    break;
  case NLL_ERRCODE_VALUE_CONST_VALUE:
    fprintf(nll_stdout, "Constant value: %s\n", param);
    break;
  case NLL_ERRCODE_VALUE_EMPTY_VALUE:
    fprintf(nll_stdout, "Empty value: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_NOT_EMPTY:
    fprintf(nll_stdout, "Formula not empty: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_BUFFER_OVER:
    fprintf(nll_stdout, "Calculation buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_LESS_PARAMETER:
    fprintf(nll_stdout, "Less parameter in operand: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_MUCH_PARAMETER:
    fprintf(nll_stdout, "Much parameter in operand: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_DIV_ZERO:
    fprintf(nll_stdout, "Division by zero: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_INVALID_OPERATOR:
    fprintf(nll_stdout, "Invalid operator: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_INVALID_PARAMETER:
    fprintf(nll_stdout, "Invalid parameter: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_UNSUPPORTED_OPERATOR:
    fprintf(nll_stdout, "Unsupported operator: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_UNKNOWN_FUNCTION:
    fprintf(nll_stdout, "Unknown function: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_NULL_POINTER:
    fprintf(nll_stdout, "Null pointer access: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_INVALID_POINTER:
    fprintf(nll_stdout, "Invalid pointer: %s\n", param);
    break;
  case NLL_ERRCODE_FORMULA_UNSUPPORTED_FUNCTION:
    fprintf(nll_stdout, "Unsupported function: %s\n", param);
    break;
  case NLL_ERRCODE_LABEL_NOT_EMPTY:
    fprintf(nll_stdout, "Label not empty: %s\n", param);
    break;
  case NLL_ERRCODE_LABEL_BUFFER_OVER:
    fprintf(nll_stdout, "Label buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_LABEL_INVALID_NAME:
    fprintf(nll_stdout, "Invalid label name: %s\n", param);
    break;
  case NLL_ERRCODE_LABEL_TOO_LONG:
    fprintf(nll_stdout, "Too long label: %s\n", param);
    break;
  case NLL_ERRCODE_LABEL_NOT_FOUND:
    fprintf(nll_stdout, "Label not found: %s\n", param);
    break;
  case NLL_ERRCODE_LABEL_DUPLICATE:
    fprintf(nll_stdout, "Duplicate label: %s\n", param);
    break;
  case NLL_ERRCODE_POSITION_NOT_EMPTY:
    fprintf(nll_stdout, "Position not empty: %s\n", param);
    break;
  case NLL_ERRCODE_POSITION_STACK_OVER:
    fprintf(nll_stdout, "Position stack overflow: %s\n", param);
    break;
  case NLL_ERRCODE_POSITION_NOT_FOUND:
    fprintf(nll_stdout, "Position not found: %s\n", param);
    break;
  case NLL_ERRCODE_POSITION_DIFFERENT_TYPE:
    fprintf(nll_stdout, "Different position type: %s\n", param);
    break;
  case NLL_ERRCODE_POSITION_DIFFERENT_VARIABLE:
    fprintf(nll_stdout, "Different position variable: %s\n", param);
    break;
  case NLL_ERRCODE_COMMAND_NOT_EMPTY:
    fprintf(nll_stdout, "Command not empty: %s\n", param);
    break;
  case NLL_ERRCODE_COMMAND_BUFFER_OVER:
    fprintf(nll_stdout, "Command buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_COMMAND_TOO_LONG:
    fprintf(nll_stdout, "Too long command: %s\n", param);
    break;
  case NLL_ERRCODE_COMMAND_NOT_FOUND:
    fprintf(nll_stdout, "Command not found: %s\n", param);
    break;
  case NLL_ERRCODE_COMMAND_MUCH_ARGS:
    fprintf(nll_stdout, "Command has much arguments: %s\n", param);
    break;
  case NLL_ERRCODE_COMMAND_INVALID_FORMAT:
    fprintf(nll_stdout, "Command invalid format: %s\n", param);
    break;
  case NLL_ERRCODE_STRING_NOT_EMPTY:
    fprintf(nll_stdout, "String not empty: %s\n", param);
    break;
  case NLL_ERRCODE_STRING_TOO_LONG:
    fprintf(nll_stdout, "Too long string: %s\n", param);
    break;
  case NLL_ERRCODE_STRING_BUFFER_OVER:
    fprintf(nll_stdout, "String buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_ARRAY_NOT_EMPTY:
    fprintf(nll_stdout, "Array not empty: %s\n", param);
    break;
  case NLL_ERRCODE_ARRAY_BUFFER_OVER:
    fprintf(nll_stdout, "Array buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_ARRAY_OUT_OF_RANGE:
    fprintf(nll_stdout, "Out of array range: %s\n", param);
    break;
  case NLL_ERRCODE_ARRAY_INVALID_PARAMETER:
    fprintf(nll_stdout, "Invalid array parameter: %s\n", param);
    break;
  case NLL_ERRCODE_AREA_NOT_EMPTY:
    fprintf(nll_stdout, "Area not empty: %s\n", param);
    break;
  case NLL_ERRCODE_AREA_BUFFER_OVER:
    fprintf(nll_stdout, "Area buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_AREA_OUT_OF_RANGE:
    fprintf(nll_stdout, "Out of area range: %s\n", param);
    break;
  case NLL_ERRCODE_STACK_NOT_EMPTY:
    fprintf(nll_stdout, "Stack not empty: %s\n", param);
    break;
  case NLL_ERRCODE_STACK_STACK_OVER:
    fprintf(nll_stdout, "Stack overflow: %s\n", param);
    break;
  case NLL_ERRCODE_STACK_NOT_FOUND:
    fprintf(nll_stdout, "Stack not found: %s\n", param);
    break;
  case NLL_ERRCODE_MEMORY_NOT_EMPTY:
    fprintf(nll_stdout, "Memory not empty: %s\n", param);
    break;
  case NLL_ERRCODE_MEMORY_BUFFER_OVER:
    fprintf(nll_stdout, "Memory buffer overflow: %s\n", param);
    break;
  case NLL_ERRCODE_MEMORY_INVALID_TYPE:
    fprintf(nll_stdout, "Invalid memory type: %s\n", param);
    break;
  case NLL_ERRCODE_MEMORY_INVALID_PARAMETER:
    fprintf(nll_stdout, "Invalid memory parameter: %s\n", param);
    break;
  case NLL_ERRCODE_NLL_SYNTAX_ERROR:
    fprintf(nll_stdout, "Syntax error: %s\n", param);
    break;
  case NLL_ERRCODE_NLL_SYMBOL_TOO_LONG:
    fprintf(nll_stdout, "Too long symbol name: %s\n", param);
    break;
  case NLL_ERRCODE_NLL_NO_CONTINUE_POINT:
    fprintf(nll_stdout, "No continue point: %s\n", param);
    break;
  case NLL_ERRCODE_NLL_INVALID_COMMAND:
    fprintf(nll_stdout, "Invalid command: %s\n", param);
    break;
  case NLL_ERRCODE_NLL_NO_DATA:
    fprintf(nll_stdout, "No data to read: %s\n", param);
    break;

  case NLL_ERRCODE_UNKNOWN:
  default:
    fprintf(nll_stdout, "Unknown error: (%s:%d): %s\n", filename, line, param);
    break;
  }
}

void nll_error_exit(const char *filename, int line,
		    int errcode, const char *param)
{
  nll_error_print(filename, line, errcode, param);
  exit(1);
}

#ifdef WIN32
static void win_usleep(int usec)
{
  Sleep((usec + 999) / 1000);
}

static void win_sleep(int sec)
{
  int s;
  do {
    s = (sec > 60) ? 60 : sec;
    Sleep(s * 1000);
    sec -= s;
  } while (sec);
}
#define usleep(usec) win_usleep(usec)
#define sleep(sec)   win_sleep(sec)
#endif

void nll_usleep(int usec)
{
  usleep(usec);
}

void nll_sleep(int sec)
{
  sleep(sec);
}

static int nosystem = 0;

int nll_is_nosystem(void)
{
  return nosystem;
}

int nll_nosystem_set(void)
{
  nosystem = 1;
  return 0;
}

static int nosyscall = 0;

int nll_is_nosyscall(void)
{
  return nosyscall;
}

int nll_nosyscall_set(void)
{
  nosyscall = 1;
  return 0;
}

static int nonetwork = 0;

int nll_is_nonetwork(void)
{
  return nonetwork;
}

int nll_nonetwork_set(void)
{
  nonetwork = 1;
  return 0;
}

static int nofixed = 0;

int nll_is_nofixed(void)
{
  return nofixed;
}

int nll_nofixed_set(void)
{
  nofixed = 1;
  return 0;
}

static int istty = 0;

int nll_is_tty(void)
{
  return istty;
}

int nll_tty_clear(void)
{
  istty = 0;
  return 0;
}

int nll_tty_set(void)
{
  istty = 1;
  return 0;
}

static int initialize = 0;

int nll_is_initialize(void)
{
  return initialize;
}

int nll_initialize_clear(void)
{
  initialize = 0;
  return 0;
}

int nll_initialize_set(void)
{
  initialize = 1;
  return 0;
}

static int edit_line = -1;

int nll_edit(void)
{
  return edit_line;
}

int nll_edit_clear(void)
{
  edit_line = -1;
  return 0;
}

int nll_edit_set(int line)
{
  edit_line = line;
  return 0;
}

static int finished = 0;
static int finished_lock = 0;

void nll_finished_clear(void)
{
  finished = 0;
}

void nll_finish(void)
{
  finished++;
}

int nll_is_finished(void)
{
  return finished_lock ? (finished > 4) : (finished > 0);
}

int nll_finish_lock(int locked)
{
  int old;
  old = finished_lock;
  finished_lock = locked;
  return old;
}

int nll_finish_is_locked(void)
{
  return finished_lock;
}

static struct file_entry {
  struct file_entry *next;
  FILE *fp;
} *nll_fps = NULL;

static int nll_file_alloc(FILE *fp)
{
  struct file_entry *f;

  f = memory_alloc(sizeof(*f));
  if (!f)
    return NLL_ERRCODE_MEMORY_BUFFER_OVER;

  f->fp = fp;
  f->next = nll_fps;
  nll_fps = f;

  return 0;
}

int nll_file_stdin(void)
{
  return nll_file_alloc(stdin);
}

int nll_file_open(const char *filename)
{
  int r;
  FILE *fp;

  fp = fopen(filename, "r");
  if (!fp)
    return NLL_ERRCODE_FILE_NOT_FOUND;

  if ((r = nll_file_alloc(fp)) < 0)
    fclose(fp);

  return r;
}

int nll_file_close(void)
{
  struct file_entry *f;

  if (!nll_fps)
    return NLL_ERRCODE_FILE_NOT_FOUND;

  f = nll_fps;
  nll_fps = nll_fps->next;

  if (f->fp != stdin)
    fclose(f->fp);

  memory_free(f);

  return 0;
}

int nll_file_allclose(void)
{
  while (nll_fps)
    nll_file_close();
  return 0;
}

FILE *nll_file_fp(void)
{
  return nll_fps ? nll_fps->fp : NULL;
}

static FILE *_nll_stdin  = NULL;
static FILE *_nll_stdout = NULL;

int nll_stdin_save(FILE *fp)
{
  if (!_nll_stdin) {
    _nll_stdin = nll_stdin;
  } else {
    fclose(nll_stdin);
  }
  nll_stdin = fp;
  return 0;
}

int nll_stdin_restore(void)
{
  if (!_nll_stdin) {
    ;
  } else {
    fclose(nll_stdin);
    nll_stdin = _nll_stdin;
    _nll_stdin = NULL;
  }
  return 0;
}

int nll_stdout_save(FILE *fp)
{
  fflush(nll_stdout);
  if (!_nll_stdout) {
    _nll_stdout = nll_stdout;
  } else {
    fclose(nll_stdout);
  }
  nll_stdout = fp;
  return 0;
}

int nll_stdout_restore(void)
{
  if (!_nll_stdout) {
    ;
  } else {
    fflush(nll_stdout);
    fclose(nll_stdout);
    nll_stdout = _nll_stdout;
    _nll_stdout = NULL;
  }
  return 0;
}

int nll_wait_output(FILE *fp)
{
#ifndef NLL_OUTPUT_NOWAIT
  int r, fd;
  fd_set fds;
  struct timeval t;

  if (!fp)
    fp = nll_stdout;

  while (1) {
    fd = fileno(fp);
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    t.tv_sec = 0;
    t.tv_usec = 100000;

    r = select(fd + 1, NULL, &fds, NULL, &t);

    if (r > 0) {
      if (FD_ISSET(fd, &fds))
	break;
      continue;
    }

    if (r == 0)
      break;
  }
#endif
#ifdef NLL_OUTPUT_WAIT_FORCE
  nll_usleep(NLL_OUTPUT_WAIT_FORCE);
#endif

  return 0;
}

char *nll_fgets(char *str, int size, FILE *fp)
{
  char *p, *e, *last;

  p = str;
  e = NULL;

  while (fgets(p, size - (p - str), fp)) {
    e = p + strlen(p);
    for (last = e; last > p; last--) {
      if (*last && !strchr(" \t\n", *last))
	break;
    }
    if (*last != '\\')
      break;
    *last = '\0';
    p = last;
  }

  if (!e || (e - str) + 1 >= size)
    return NULL;

  return str;
}
