/* Copyright (c) 1993 Stephen F. White */

#include "cool.h"
#include "proto.h"
#include "netio.h"
#include "execute.h"
#include "y.tab.h"

void op_add (void)
{
  Var right, left, ret;

  right = pop ();
  left = pop ();
  if (left.type != right.type && left.type != LIST) {
    var_free (left);
    var_free (right);
    raise (E_TYPE);
  } else if (left.type == NUM) {
    left.v.num = left.v.num + right.v.num;
    push (left);
  } else if (left.type == STR) {
    ret.type = STR;
    if (left.v.str->ref == 1) {
      ret.v.str = string_cat (left.v.str, right.v.str->str);
    } else {
      ret.v.str = string_new (left.v.str->len + right.v.str->len + 1);
      strcpy (ret.v.str->str, left.v.str->str);
      strcpy (ret.v.str->str + left.v.str->len, right.v.str->str);
      ret.v.str->len = left.v.str->len + right.v.str->len;
      var_free (left);
    }
    var_free (right);
    push (ret);
  } else if (left.type == LIST) {
    ret.type = LIST;
    ret.v.list = list_setadd (left.v.list, right);
    push (ret);
  } else {
    var_free (left);
    var_free (right);
    raise (E_TYPE);
  }
}

void op_sub (void)
{
  Var right, left;

  right = pop ();
  left = pop ();
  if (left.type != right.type && left.type != LIST) {
    var_free (left);
    var_free (right);
    raise (E_TYPE);
  } else if (left.type == NUM) {
    left.v.num -= right.v.num;
    push (left);
  } else if (left.type == LIST) {
    left.v.list = list_setremove (left.v.list, right);
    var_free (right);
    push (left);
  } else {
    raise (E_ARGTYPE);
  }
}

void op_mul (void)
{
  Var right, left;

  right = pop ();
  left = pop ();
  if (left.type != NUM || right.type != NUM) {
    var_free (left);
    var_free (right);
    raise (E_TYPE);
  } else {
    left.v.num *= right.v.num;
    push (left);
  }
}

void op_div (void)
{
  Var right, left;

  right = pop ();
  left = pop ();

  if (left.type != NUM || right.type != NUM) {
    var_free (left);
    var_free (right);
    raise (E_TYPE);
  } else if (!right.v.num) {
    raise (E_DIV);
  } else {
    left.v.num /= right.v.num;
    push (left);
  }
}

void op_mod (void)
{
  Var right, left;

  right = pop ();
  left = pop ();

  if (left.type != NUM || right.type != NUM) {
    var_free (left);
    var_free (right);
    raise (E_TYPE);
  } else if (!right.v.num) {
    raise (E_DIV);
  } else {
    left.v.num %= right.v.num;
    push (left);
  }
}

void op_negate (void)
{
  Var arg;

  arg = pop ();
  if (arg.type != NUM) {
    var_free (arg);
    raise (E_TYPE);
  } else {
    arg.v.num = -arg.v.num;
    push (arg);
  }
}

void op_and (void)
{
  Var left;
  Var ret;

  left = pop ();
  if (ISTRUE (left)) {          /* LHS true, evaluate RHS */
    frame.pc++;                 /* skip over breakout goto */
  } else {                      /* LHS false, abort */
    ret.type = NUM;
    ret.v.num = 0;
    push (ret);
    frame.pc = frame.m->code[frame.pc]; /* skip to breakout */
  }
  var_free (left);
}

void op_or (void)
{
  Var left;
  Var ret;

  left = pop ();
  if (!ISTRUE (left)) {         /* LHS false, evaluate RHS */
    frame.pc++;                 /* skip over breakout goto */
  } else {                      /* LHS true, skip to end */
    ret.type = NUM;
    ret.v.num = 1;
    push (ret);
    frame.pc = frame.m->code[frame.pc]; /* skip to breakout */
  }
  var_free (left);
}

void op_not (void)
{
  Var ret, arg;

  arg = pop ();
  ret.type = NUM;
  ret.v.num = !ISTRUE (arg);
  var_free (arg);
  push (ret);
}

#define REL(NAME, OP) \
void NAME(void) \
{ \
    Var     ret, right, left; \
\
    right = pop(); left = pop(); \
    if (left.type != right.type) { \
    raise(E_TYPE); \
    } else if (left.type != STR && left.type != NUM) { \
    raise(E_TYPE); \
    } else { \
    ret.type = NUM; ret.v.num = var_compare(left, right) OP 0; \
    push(ret); \
    } \
    var_free(left);  var_free(right); \
}

REL (op_lt, <)
  REL (op_gt, >)
  REL (op_le, <=)
  REL (op_ge, >=)

void op_eq (void)
{
  Var ret, right, left;

  right = pop ();
  left = pop ();

  ret.type = NUM;
  ret.v.num = var_eq (left, right);
  push (ret);
  var_free (left);
  var_free (right);
}

void op_ne (void)
{
  Var ret, right, left;

  right = pop ();
  left = pop ();
  ret.type = NUM;
  ret.v.num = !var_eq (left, right);
  push (ret);
  var_free (left);
  var_free (right);
}

void op_index (void)
{
  Var ret, i, base;

  i = pop ();
  base = pop ();
  switch (base.type) {
  case STR:
    if (i.type != NUM) {
      raise (E_TYPE);
    } else if (i.v.num < 1 || i.v.num > base.v.str->len) {
      raise (E_RANGE);
    } else {
      ret.type = STR;
      ret.v.str = string_new (2);
      ret.v.str->str[0] = base.v.str->str[i.v.num - 1];
      ret.v.str->str[1] = '\0';
      ret.v.str->len = 1;
      push (ret);
    }
    break;
  case LIST:
    if (i.type != NUM) {
      raise (E_TYPE);
    } else if (i.v.num < 1 || i.v.num > base.v.list->len) {
      raise (E_RANGE);
    } else {
      ret = var_dup (base.v.list->el[i.v.num - 1]);
      push (ret);
    }
    break;
  case MAP:
    if (map_find (base.v.map, i, &ret)) {
      raise (E_MAPNF);
    } else {
      push (var_dup (ret));
    }
    break;
  default:
    raise (E_TYPE);
  }
  var_free (base);
  var_free (i);
}

void op_subset (void)
{
  Var base, left, right, ret;
  int len = 0;

  right = pop ();
  left = pop ();
  base = pop ();

  ret.type = base.type;

  if (base.type == STR) {
    len = base.v.str->len;
  } else if (base.type == LIST) {
    len = base.v.list->len;
  }
  if (left.type != NUM || right.type != NUM) {
    var_free (left);
    var_free (right);
    var_free (base);
    raise (E_TYPE);
    return;
  }
  if (right.v.num < 1) {
    right.v.num = len;
  }
  if (left.v.num > right.v.num
    || left.v.num < 1 || left.v.num > len
    || right.v.num < 1 || right.v.num > len) {
    raise (E_RANGE);
  } else if (base.type == STR) {
    len = right.v.num - left.v.num + 1; /* substring length */
    ret.type = STR;
    ret.v.str = string_new (len);
    strncpy (ret.v.str->str, base.v.str->str + left.v.num - 1, len);
    ret.v.str->str[len] = '\0';
    ret.v.str->len = len;
    push (ret);
  } else if (base.type == LIST) {
    ret.v.list = list_subset (base.v.list, left.v.num, right.v.num);
    push (ret);
  } else {
    raise (E_TYPE);
  }
  var_free (base);
  var_free (left);
  var_free (right);
}

void op_lsubset (void)
{
  Var left, right;

  right = pop ();
  left.type = NUM;
  left.v.num = 1;
  push (left);
  push (right);
  op_subset ();
}

void op_rsubset (void)
{
  Var left, right;

  left = pop ();
  right.type = NUM;
  right.v.num = -1;
  push (left);
  push (right);
  op_subset ();
}

void op_splice(void)
{
    int		i;
    Var		list = pop();

    if (list.type != LIST) {
	raise(E_TYPE);
    } else {
	for (i = 0; i < list.v.list->len; i++) {
	    push(var_dup(list.v.list->el[i]));
	}
    }
    var_free(list);
}

void
op_return(void)
{
  ex_retval = pop ();
  ex_state = STOPPED;
}

void op_pop (void)
{
  Var v;

  v = pop ();
  var_free (v);
}

void op_numpush (void)
{
  Var n;

  n.type = NUM;
  n.v.num = frame.m->code[frame.pc++];
  push (n);
}

void op_strpush (void)
{
  Var s;
  int symno;

  symno = frame.m->code[frame.pc++];
  s.type = STR;
  s.v.str = string_dup (sym_get (frame.on, symno));
  push (s);
}

void op_objpush (void)
{
  Var o;

  o.type = OBJ;
  o.v.obj.id = frame.m->code[frame.pc++];
  o.v.obj.server = frame.m->code[frame.pc++];
  push (o);
}

void op_listpush (void)
{
  Var list;

  list = pop_args(count_args());
  push (list);
}

void op_mappush (void)
{
  int num = frame.m->code[frame.pc++], i;
  Var v;
  Var from, to;

  v.type = MAP;
  v.v.map = map_new (num);
  for (i = 0; i < num; i++) {
    to = pop ();
    from = pop ();
    v.v.map = map_add (v.v.map, from, to);
  }
  push (v);
}

void op_errpush (void)
{
  Var e;

  e.type = ERR;
  e.v.err = frame.m->code[frame.pc++];
  push (e);
}

void op_message (void)
{
  Var args;
  Var dest;
  String *msg;

  args = pop_args(count_args());
  dest = pop ();
  if (dest.type != OBJ) {
    var_free (args);
    var_free (dest);
    frame.pc++;
    raise (E_INVIND);
  } else {
    msg = string_dup (sym_get (frame.on, frame.m->code[frame.pc]));
    frame.pc++;
    send_message_and_block (frame.this, dest.v.obj, msg,
      args.v.list, dest.v.obj);
  }
}

void op_message_expr (void)
{
  Var args;
  Var msg, dest;

  args = pop_args(count_args());
  msg = pop(); dest = pop();

  if (dest.type != OBJ) {
    var_free (args);
    var_free (dest);
    var_free (msg);
    raise (E_INVIND);
  } else if (msg.type != STR) {
    var_free (args);
    var_free (msg);
    raise (E_TYPE);
  } else {
    send_message_and_block (frame.this, dest.v.obj, msg.v.str,
      args.v.list, dest.v.obj);
  }
}

void op_if (void)
{
  Var cond;

  cond = pop ();
  if (ISTRUE (cond)) {
    pushn (frame.m->code[frame.pc + 1]);        /* push end address */
    frame.pc += 2;              /* skip to code */
  } else if (frame.m->code[frame.pc]) { /* if there's an elseif */
    pushn (frame.m->code[frame.pc + 1]);        /* push end address */
    frame.pc = frame.m->code[frame.pc]; /* go to elseif code */
  } else {
    frame.pc = frame.m->code[frame.pc + 1];     /* go to end address */
  }
  var_free (cond);
}

void op_null (void)
{                               /* null function */
}

void op_elseif (void)
{
  Var cond;

  cond = pop ();
  if (ISTRUE (cond)) {
    frame.pc++;
  } else {
    frame.pc = frame.m->code[frame.pc];
  }
  var_free (cond);
}

void op_for (void)
{
  Var idx, list;

  idx = pop ();
  list = pop ();

  if( list.type == MAP)
  {
	list.v.list = map_keys( list.v.map );
	list.type = LIST;
    }
    if( list.type == STR)
    {
	list.v.list = string_list( list.v.str );
	list.type = LIST;
  }
  if (list.type != LIST) {
    var_free (list);
    raise (E_FOR);
  } else if (idx.v.num >= list.v.list->len) {   /* loop is complete */
    var_free (list);
    frame.pc = frame.m->code[frame.pc + 1];     /* skip to end */
  } else {
    var_assign_local (frame.stack, frame.m->code[frame.pc],
      var_dup (list.v.list->el[idx.v.num]));
    idx.v.num++;
    push (list);                /* push list */
    push (idx);                 /* push new index */
    pushpc (frame.pc - 1);      /* push address of FOR statement */
    frame.pc += 2;              /* go to first instruction in loop */
  }
}

void op_forrng (void)
{
  Var v, upper;

  upper = pop ();
  v = pop ();
  if (v.type != NUM || upper.type != NUM) {
    var_free (upper);
    var_free (v);
    raise (E_TYPE);
  } else if (v.v.num > upper.v.num) {
    frame.pc = frame.m->code[frame.pc + 1];
  } else {
    var_assign_local (frame.stack, frame.m->code[frame.pc], v);
    v.v.num++;
    push (v);
    push (upper);
    pushpc (frame.pc - 1);
    frame.pc += 2;
  }
}

void op_pushpc (void)
{
  pushpc (frame.pc - 1);
}

void op_argstart(void)
{
    pushpc(-ARGSTART);
}

void op_while(void)
{
  Var cond;

  cond = pop ();
  if (!ISTRUE (cond)) {
    (void) pop ();              /* take pc off stack */
    frame.pc = frame.m->code[frame.pc];
  } else {
    frame.pc++;                 /* go to first instruction in loop */
  }
  var_free (cond);
}

void op_do (void)
{
  pushpc (frame.m->code[frame.pc++]);
}

void op_dowhile (void)
{
  Var cond;

  cond = pop ();
  if (ISTRUE (cond)) {          /* keep looping */
    frame.pc = frame.m->code[frame.pc];
  } else {                      /* loop is finished */
    (void) pop ();              /* remove sentinel */
    frame.pc += 2;              /* skip to end */
  }
  var_free (cond);
}

void op_break (void)
{
  Var newpc;
  int break_lvl = frame.m->code[frame.pc++];

  while (break_lvl--) {
    do {
      newpc = pop ();
      var_free (newpc);
	} while (newpc.type != PC || newpc.v.num < 0);
  }
  if (frame.m->code[newpc.v.num] == FOR) {
    (void) pop ();              /* pop index */
    newpc = pop ();
    var_free (newpc);           /* pop list */
    frame.pc = frame.m->code[newpc.v.num + 2];
  } else if (frame.m->code[newpc.v.num] == FORRNG) {
    (void) pop ();              /* pop upper range */
    (void) pop ();              /* pop current value */
    frame.pc = frame.m->code[newpc.v.num + 2];
  } else if (frame.m->code[newpc.v.num] == DOWHILE) {
    frame.pc = newpc.v.num + 3;
  } else if (frame.m->code[newpc.v.num] == PUSHPC) {    /* WHILE, actually */
    while (frame.m->code[newpc.v.num] != WHILE) {
      newpc.v.num++;
    }
    frame.pc = frame.m->code[newpc.v.num + 1];
  }
}

void op_continue (void)
{
  Var newpc;
  int break_lvl = frame.m->code[frame.pc++];

  while (break_lvl--) {
    do {
      newpc = pop ();
      var_free (newpc);
	} while (newpc.type != PC || newpc.v.num < 0);
  }
  if (frame.m->code[newpc.v.num] == DOWHILE) {
    pushpc (newpc.v.num);
    frame.pc = frame.m->code[newpc.v.num + 2];
  } else {
    frame.pc = newpc.v.num;     /* all the others push their own PC */
  }
}

void op_asgnlvar (void)
{
  Var v;

  v = pop ();
  var_assign_local (frame.stack, frame.m->code[frame.pc++], v);
}

void op_asgngvar (void)
{
  Var v;
  int varno = frame.m->code[frame.pc++];

  v = pop ();
  raise (var_assign_global (this, sym_get (frame.on, varno), v));
  (void) pop ();
}

static int do_asgnindex (Var var, Var idx, Var expr, Var * ret);

void op_asgnlvarindex (void)
{
  Var var, idx, expr, ret;
  int varno = frame.m->code[frame.pc++];

  expr = pop ();
  idx = pop ();

  var = frame.stack[varno];
  if (!do_asgnindex (var, idx, expr, &ret)) {
    frame.stack[varno] = ret;
  }
}

void op_asgngvarindex (void)
{
  Var var, idx, expr, ret;
  int varno = frame.m->code[frame.pc++];
  Error r;

  expr = pop ();
  idx = pop ();

  r = var_get_global (this, sym_get (frame.on, varno)->str, &var);
  if (r != E_NONE) {
    raise (r);
  } else if (!do_asgnindex (var, idx, expr, &ret)) {
    var = var_dup (var);
    var_assign_global (this, sym_get (frame.on, varno), ret);
  }
}

static int do_asgnindex (Var var, Var idx, Var expr, Var * ret)
{
  ret->type = var.type;
  if (var.type == LIST) {
    if (idx.type != NUM) {
      var_free (idx);
      var_free (expr);
      raise (E_TYPE);
      return -1;
    } else if (idx.v.num <= 0 || idx.v.num > var.v.list->len) {
      var_free (expr);
      raise (E_RANGE);
      return -2;
    } else {
      ret->v.list = list_assign (var.v.list, expr, idx.v.num);
      return 0;
    }
  } else if (var.type == MAP) {
    ret->v.map = map_add (var.v.map, idx, expr);
    return 0;
  } else {
    var_free (expr);
    var_free (idx);
    raise (E_TYPE);
    return -3;
  }
}

void op_getlvar (void)
{
  Var v;

  var_get_local (frame.stack, frame.m->code[frame.pc++], &v);
  push (var_dup (v));
}

void op_getgvar (void)
{
  Var ret;
  Error r;
  int varname;

  varname = frame.m->code[frame.pc++];
  r = var_get_global (this, sym_get (frame.on, varname)->str, &ret);
  if (r != E_NONE) {
    raise (r);
    (void) pop ();              /* what's this for?  i forget */
  } else {
    push (var_dup (ret));
  }
}

void op_getgvarexpr(void)
{
  Var arg, ret;
  Error r;

  arg = pop ();
  if (arg.type != STR) {
    raise (E_ARGTYPE);
  } else {
    r = var_get_global (this, arg.v.str->str, &ret);
    if (r != E_NONE) {
      raise (r);
    } else {
      push (var_dup (ret));
    }
  }
  var_free (arg);
}

void op_getsysvar(void)
{
    int		varno = frame.m->code[frame.pc++];
    String	*varname = sym_get(frame.on, varno);
    Var		ret;
    Object      *sys;

    if (!(sys = retrieve(sys_obj))) {
        raise(E_OBJNF);
    } else if (var_get_global(sys, varname->str, &ret) == E_VARNF) {
        raise(E_VARNF);
    } else {
        push(var_dup(ret));
    }
}

void op_asgngvarexpr(void)
{
  Var arg1, arg2;
  Error r;

  arg2 = pop();  arg1 = pop();
  if (arg1.type != STR) {
    raise (E_ARGTYPE);
  } else if (!valid_ident (arg1.v.str->str)) {
    raise (E_RANGE);
  } else {
    r = var_assign_global (this, arg1.v.str, arg2);
    if (r != E_NONE) {
      raise (r);
    } else {
      push (var_dup (arg2));
    }
  }
  var_free (arg1);
}

void op_stop (void)
{
  Var newpc;

  newpc = pop ();
  if (newpc.type != PC && newpc.type != NUM) {
    writelog ();
    fprintf (stderr, "EXECUTION ERROR:  STOP encountered non-num PC\n");
    raise (E_INTERNAL);
    var_free (newpc);
  } else {
	if (newpc.v.num < 0 && newpc.v.num != -ARGSTART) {
      ex_state = STOPPED;
    } else {
      frame.pc = newpc.v.num;
    }
  }
}

void op_in (void)
{
  Var left, right, r;

  right = pop ();
  left = pop ();
  r.type = NUM;

  if (right.type == LIST) {
    r.v.num = list_ismember (left, right.v.list);
    push (r);
  } else if (right.type == STR) {
    if (left.type != STR) {
      raise (E_TYPE);
    } else {
      r.v.num = str_in (left.v.str->str, right.v.str->str);
      push (r);
    }
  } else {
    raise (E_TYPE);
  }
  var_free (left);
  var_free (right);
}