#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 "formula.h"
#include "label.h"
#include "value.h"
#include "variable.h"
#include "command.h"

static int command_num = 0;
static int pool_num = 0;

static command_t pool = NULL;

static command_t pool_alloc(void)
{
  command_t command;

  if (!pool)
    return NULL;

  command = pool;
  pool = pool->next;
  pool_num--;

  return command;
}

static int pool_free(command_t command)
{
  command->next = pool;
  pool = command;
  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_COMMAND_NOT_EMPTY;
  return 0;
}

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

static int _free(command_t command)
{
  if ((COMMAND_MAXNUM < 0) || (pool_num < COMMAND_MAXNUM))
    return pool_free(command);
  memory_free(command);
  return 0;
}
#else
static struct command commands[COMMAND_MAXNUM];

static int _init(void)
{
  command_t command;
  int i;

  memset(commands, 0, sizeof(commands));

  for (i = 0; i < COMMAND_MAXNUM; i++) {
    command = &commands[i];
    command->next = pool;
    pool = command;
    pool_num++;
  }

  return 0;
}

static int _done(void)
{
  return 0;
}

static int _check(void)
{
  command_t command;
  int n = 0;

  for (command = pool; command && (n < COMMAND_MAXNUM); command = command->next)
    n++;

  if (command || (n != COMMAND_MAXNUM) || (n != pool_num))
    return NLL_ERRCODE_COMMAND_NOT_EMPTY;

  return 0;
}

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

static int _free(command_t command)
{
  return pool_free(command);
}
#endif

static const struct command_ope opecodes[] = {
  { NULL      , NULL , COMMAND_NONE     },
  { "#"       , NULL , COMMAND_COMMENT  },
  { "RUN"     , "RN.", COMMAND_RUN      },
  { "EDIT"    , "ET.", COMMAND_EDIT     },
  { "END"     , NULL , COMMAND_END      },
  { "EXIT"    , NULL , COMMAND_EXIT     },
  { "QUIT"    , "QT.", COMMAND_EXIT     },
  { "BREAK"   , "BK.", COMMAND_BREAK    },
  { "STEP"    , "SE.", COMMAND_STEP     },
  { "STOP"    , "SP.", COMMAND_STOP     },
  { "SKIP"    , NULL , COMMAND_SKIP     },
  { "CONTINUE", "CN.", COMMAND_CONTINUE },
  { "ARGS"    , "AG.", COMMAND_ARGS     },
  { "LET"     , "LT.", COMMAND_LET      },
  { "PRINT"   , "PT.", COMMAND_PRINT    },
  { "GOTO"    , "GT.", COMMAND_GOTO     },
  { "GONEXT"  , "GN.", COMMAND_GONEXT   },
  { "GOSUB"   , "GS.", COMMAND_GOSUB    },
  { "RETURN"  , "RT.", COMMAND_RETURN   },
  { "IF"      , NULL , COMMAND_IF       },
  { "ELSE"    , "ES.", COMMAND_ELSE     },
  { "ENDIF"   , "EI.", COMMAND_ENDIF    },
  { "LOOPEND" , "LE.", COMMAND_LOOPEND  },
  { "LOOP"    , "LP.", COMMAND_LOOP     },
  { "FOR"     , "FR.", COMMAND_FOR      },
  { "NEXT"    , "NX.", COMMAND_NEXT     },
  { "DIM"     , "DM.", COMMAND_DIM      },
  { "MEMORY"  , "MM.", COMMAND_MEMORY   },
  { "PUSH"    , "PS.", COMMAND_PUSH     },
  { "POP"     , "PP.", COMMAND_POP      },
  { "DFROM"   , "FM.", COMMAND_DFROM    },
  { "DREAD"   , "RD.", COMMAND_DREAD    },
  { "DATA"    , "DT.", COMMAND_DATA     },
  { "DISCARD" , "DC.", COMMAND_DISCARD  },
  { "NEW"     , "NW.", COMMAND_NEW      },
  { "LIST"    , "LS.", COMMAND_LIST     },
  { "SAVE"    , "SV.", COMMAND_SAVE     },
  { "LOAD"    , "LD.", COMMAND_LOAD     },
  { "STDIN"   , "SI.", COMMAND_STDIN    },
  { "STDOUT"  , "SO.", COMMAND_STDOUT   },
  { "EVAL"    , "EV.", COMMAND_EVAL     },
  { "EVALF"   , "EF.", COMMAND_EVALF    },
  { "WAIT"    , "WT.", COMMAND_WAIT     },
  { "DUMP"    , "DP.", COMMAND_DUMP     },
  { NULL      , NULL , COMMAND_LAST     }
};

int command_check(void)
{
  if (command_num)
    return NLL_ERRCODE_COMMAND_NOT_EMPTY;

  return _check();
}

static int namecmp(const char *s0, const char *s1, int len)
{
  int i;
  for (i = 0; i < len; i++) {
    if (toupper(*(s0++)) != toupper(*(s1++)))
      return -1;
  }
  return 0;
}

int command_search(char *p, char **endp, command_ope_t *opep)
{
  command_ope_t ope;
  int i, len;

  for (ope = opecodes; ope->type != COMMAND_LAST; ope++) {
    if (ope->name) {
      len = strlen(ope->name);
      if (!namecmp(ope->name, p, len)) {
#ifdef NLL_COMMAND_NEED_SPACE
	if (p[len] && (ope->type != COMMAND_COMMENT)) {
	  if (isalnum(p[len]) || (p[len] == '_'))
	    continue;
#ifdef NLL_MULTIBYTE_NAME
	  else if (p[len] & 0x80)
	    continue;
#endif
	}
#endif
	goto found;
      }
    }
  }

  for (ope = opecodes; ope->type != COMMAND_LAST; ope++) {
    if (ope->other_name) {
      len = strlen(ope->other_name);
      if (!namecmp(ope->other_name, p, len))
	goto found;
    }
  }

  for (ope = opecodes; ope->type != COMMAND_LAST; ope++) {
    if (ope->name) {
      len = strlen(ope->name);
      for (i = 1; i < len; i++) {
	if (!namecmp(ope->name, p, i) && (p[i] == '.')) {
	  len = i + 1;
	  goto found;
	}
      }
    }
  }

  return NLL_ERRCODE_COMMAND_NOT_FOUND;

found:
  *opep = ope;
  for (i = 0; i < len; i++, p++)
    *p = toupper(*p);
  if (endp)
    *endp = p;
  return 0;
}

int command_list(FILE *fp)
{
  command_ope_t ope;

  for (ope = opecodes; ope->type != COMMAND_LAST; ope++) {
    nll_wait_output(fp);
    if (ope->name) {
      fprintf(fp, "%s", ope->name);
      if (ope->other_name)
	fprintf(fp, "\t(%s)", ope->other_name);
      fprintf(fp, "\n");
    }
  }

  fflush(fp);

  return 0;
}

int command_get(command_type_t type, command_ope_t *opep)
{
  command_ope_t ope;

  for (ope = opecodes; ope->type != COMMAND_LAST; ope++) {
    if (ope->type == type) {
      *opep = ope;
      return 0;
    }
  }

  return NLL_ERRCODE_COMMAND_NOT_FOUND;
}

int command_alloc(command_t *commandp)
{
  command_t command;

  if ((command = _alloc()) == NULL)
    return NLL_ERRCODE_COMMAND_BUFFER_OVER;
  command_num++;

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

  *commandp = command;

  return 0;
}

int command_free(command_t command)
{
  command_t next;
  int r;

  for (; command; command = next) {
    next = command->next;

    command_clear(command);

    command_num--;
    if ((r = _free(command)) < 0)
      return r;
  }

  return 0;
}

int command_set_label(command_t command, const char *name, spot_t spot)
{
  int r;

  if ((r = variable_get(name, &command->label.variable)) < 0)
    return r;
  if (command->label.variable->value->flags & (VALUE_FLAG_CONST|VALUE_FLAG_RDONLY))
    return NLL_ERRCODE_LABEL_DUPLICATE;

  if ((r = label_set(command->label.variable->name, spot,
		     &command->label.label)) < 0)
    return r;

  /* Clear a value of the variable for a case of VALUE_FLAG_STATIC. */
  if ((r = value_clear(command->label.variable->value)) < 0)
    return r;

  if ((r = value_set_label(command->label.variable->value,
			   command->label.label)) < 0)
    return r;

  command->label.variable->value->flags |= VALUE_FLAG_CONST;

  return 0;
}

int command_clear_label(command_t command)
{
  int r;

  if (command->label.args) {
    if ((r = formula_free(&command->label.args)) < 0)
      return r;
    command->label.args = NULL;
  }

  if (command->label.variable) {
    /* Clear a value of the variable for a case referred from another place. */
    command->label.variable->value->flags &= ~VALUE_FLAG_CONST;
    if ((r = value_clear(command->label.variable->value)) < 0)
      return r;
    if ((r = variable_del(command->label.variable)) < 0)
      return r;
    command->label.variable = NULL;
  }

  if (command->label.label) {
    if ((r = label_clear(command->label.label)) < 0)
      return r;
    if ((r = label_del(command->label.label)) < 0)
      return r;
    command->label.label = NULL;
  }

  return 0;
}

int command_clear(command_t command)
{
  command_arg_t arg;
  int i;

  command_clear_label(command);

  if (command->prefix) {
    formula_free(&command->prefix);
    command->prefix = NULL;
  }

  for (i = 0; i < COMMAND_ARG_MAXNUM; i++) {
    arg = &command->args[i];
    switch (arg->type) {
    case COMMAND_ARG_LABEL:
      if (arg->arg.label.label)
	label_del(arg->arg.label.label);
      break;
    case COMMAND_ARG_VARIABLE:
      if (arg->arg.variable.variable)
	variable_del(arg->arg.variable.variable);
      break;
    case COMMAND_ARG_FORMULA:
      if (arg->arg.formula.element)
	formula_free(&arg->arg.formula.element);
      break;
    case COMMAND_ARG_WORD:
    case COMMAND_ARG_UNKNOWN:
    default:
      break;
    }
  }

  memset(command->args, 0, sizeof(command->args));

  command->ope = NULL;

  return 0;
}

int command_clean(command_t command)
{
  command_arg_t arg;
  int r, i;

  for (; command; command = command->next) {
    if (command->label.label) {
      if ((r = label_clean(command->label.label)) < 0)
	return r;
    }
    if (command->label.variable) {
      if ((r = value_clear(command->label.variable->value)) < 0)
	return r;
    }
    if (command->label.args) {
      if ((r = formula_clean(command->label.args)) < 0)
	return r;
    }

    if (command->prefix) {
      if ((r = formula_clean(command->prefix)) < 0)
	return r;
    }

    for (i = 0; i < COMMAND_ARG_MAXNUM; i++) {
      arg = &command->args[i];
      switch (arg->type) {
      case COMMAND_ARG_LABEL:
	if (arg->arg.label.label) {
	  if ((r = label_clean(arg->arg.label.label)) < 0)
	    return r;
	}
	break;
      case COMMAND_ARG_VARIABLE:
	if (arg->arg.variable.variable) {
	  if ((r = value_clear(arg->arg.variable.variable->value)) < 0)
	    return r;
	}
	break;
      case COMMAND_ARG_FORMULA:
	if (arg->arg.formula.element) {
	  if ((r = formula_clean(arg->arg.formula.element)) < 0)
	    return r;
	}
	break;
      case COMMAND_ARG_WORD:
      case COMMAND_ARG_UNKNOWN:
      default:
	break;
      }
    }
  }

  return 0;
}

int command_init(void)
{
  command_num = 0;
  pool_num = 0;
  pool = NULL;
  return _init();
}

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

int command_dump(FILE *fp, command_t command)
{
  int i;
  command_arg_t arg;
  char *s;

  for (; command; command = command->next) {
    nll_wait_output(fp);
    fprintf(fp, "\t");
    if (command->errcode) {
      fprintf(fp, "ERROR\t");
      NLL_ERRPRINT(command->errcode, command->line);
      continue;
    }
    if (command->label.label) {
      fprintf(fp, ".");
      if (command->label.variable)
	fprintf(fp, "%s", command->label.variable->name);
      if (command->label.args)
	fprintf(fp, " ");
      formula_dump(fp, command->label.args);
      fprintf(fp, ":");
    }
    if (command->prefix) {
      if (command->label.label)
	fprintf(fp, "\t");
      formula_dump(fp, command->prefix);
    }
    fprintf(fp, "\t");
    if (command->ope) {
      fprintf(fp, "%s", command->ope->name ? command->ope->name : "NONE");
    } else {
      fprintf(fp, "NULL");
    }
    fprintf(fp, "\tArgs:");
    for (i = 0; i < COMMAND_ARG_MAXNUM; i++) {
      arg = &command->args[i];
      s = "";
      switch (arg->type) {
      case COMMAND_ARG_VARIABLE: if (arg->arg.variable.variable) s = " V"; break;
      case COMMAND_ARG_WORD:     if (arg->arg.word.word[0])      s = " W"; break;
      case COMMAND_ARG_LABEL:
	if (arg->arg.label.label) {
	  fprintf(fp, " L:");
	}
	break;

      case COMMAND_ARG_FORMULA:
	if (arg->arg.formula.element) {
	  fprintf(fp, " F:");
	  formula_dump(fp, arg->arg.formula.element);
	}
	break;

      case COMMAND_ARG_UNKNOWN:
      default:
	break;
      }
      fprintf(fp, "%s", s);
    }
    fprintf(fp, "\n");
  }

  fflush(fp);

  return 0;
}
