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

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

/*
 * execute.c
 *
 * This file contains execution global parameters, and is the interface
 * to the finite state machine executor.  The interface is through
 * call_method(), resume_method_*() or call_verb().
 *
 * It also contains functions useful for the opcodes of the state machine,
 * including push(), pop(), pushn(), raise() and send_message_and_block().
 */

/*
 * The actual execution globals
 */

Event frame;                    /* contains pc, sp, local vars, etc. */
Object *this;
enum state ex_state;            /* current execution state */
Var ex_retval;                  /* return value of current method */
int		 nargs;		/* argument count to built-in function */

/*
 * Prototypes for functions local to this file
 */

static void execute (void);
static Error call_verb_recursive (Object * o, int msgid, int age, int ticks,
  Objid player, Objid from, Objid to, const char *verb, const char *argstr);
static void free_frame (Event * e);
static void clear_locks (Object * o, int msgid);
static String *add_traceback (String * str);

static void
do_method (Object * o, Method * m, int msgid, int age, int ticks,
  Objid player, Objid from, Objid to, const char *message, List * args)
{
  frame.sp = 0;
  frame.pc = 0;
  frame.nvars = var_count_local (m);
  var_init_local (o, m, frame.stack);
  frame.sp += frame.nvars;
  frame.msgid = msgid;
  frame.age = age;
  frame.ticks = ticks;
  frame.player = player;
  frame.caller = from;
  frame.this = to;
  frame.on = o;
  frame.on->ref++;
  frame.args = list_dup (args);
  frame.m = m;
  frame.m->ref++;
  frame.last_opcode = -1;
  pushn (-1);                   /* the terminator */
  ex_state = RUNNING;
  execute ();
}

Error
call_method (int msgid, int age, int ticks, Objid player, Objid from,
  Objid to, const char *message, List * args, Objid on)
{
  Method *m;
  Object *o = retrieve (on);

  if (!o) {
    return E_OBJNF;
  } else if ((m = find_method_recursive (o, message, &o))) {
    do_method (o, m, msgid, age, ticks, player, from, to, message, args);
    return E_NONE;
  } else {                      /* not found */
    return E_METHODNF;
  }
}

/*
 * call_verb() - Call a verb on the local object
 *
 * Trashes "cmd", but does not free it
 *
 */

Error
call_verb (int msgid, int age, int ticks, Objid player, Objid from, Objid to,
  List * args)
{
  char *cmd, *start, *end, *argstr;
  Error r;

  if (args->len != 1) {
    return E_NARGS;
  } else if (args->el[0].type != STR) {
    return E_ARGTYPE;
  }
/*
 * parse out verb (first word)
 */

    cmd = str_dup(args->el[0].v.str->str);
    start = cmd;
    while (isspace((int)*start)) {
    start++;
  }
  if (!*start) {
    FREE (cmd);
    return E_VERBNF;
  }
  end = start;
  while (*end && !isspace ((int)*end))
    end++;
  if (*end) {
    argstr = end + 1;
    *end = '\0';
  } else {
    argstr = end;
  }
  r = call_verb_recursive (retrieve (to), msgid, age, ticks, player, from, to,
    start, argstr);
  FREE (cmd);
  return r;
}

/*
 * call_verb_recursive()
 *
 * Attempts to find a verb on an object, or any of its ancestors.
 * If one is found, the associated method is called, again starting
 * at the base object.
 *
 * Return values:  E_VERBNF indicates no verb was found;
 *         E_NONE indicates a verb was found.
 */

static Error
call_verb_recursive (Object * o, int msgid, int age, int ticks, Objid player,
  Objid from, Objid to, const char *verb, const char *argstr)
{
  Verbdef *v;
  const char *dobj = 0, *prep = 0, *iobj = 0;
  int dobjlen = 0, preplen = 0;
  List *args, *parents;
  int i;
  Error r;

  if (!o) {
    return E_VERBNF;            /* not a valid object, therefore no verb found */
  }
  for (v = o->verbs; v; v = v->next) {  /* for all verbs */
    if (verb_match (sym_get (o, v->verb)->str, verb)) {
      if (v->prep == -1) {
        dobj = argstr;
        dobjlen = strlen (argstr);
      } else if (!prep_match (sym_get (o, v->prep)->str, argstr,
          &dobj, &prep, &iobj, &dobjlen, &preplen)) {
        continue;               /* prep doesn't match; keep looking */
      }
      args = list_new (4);
      args->el[0].type = STR;
      args->el[0].v.str = string_cpy (verb);
      args->el[1].type = args->el[2].type = args->el[3].type = STR;
      if( prep )
      {
      args->el[1].v.str = string_ncpy (dobj, dobjlen);
      } else {
		args->el[1].v.str = string_fixquote( 
        string_ncpy(dobj, dobjlen) );
	  }
	  args->el[2].v.str = prep ? string_ncpy(prep, preplen) : sym_sys(BLANK);
	  if( iobj )
	  {
		args->el[3].v.str = string_fixquote(
			string_cpy(iobj));
	  } else {
		args->el[3].v.str = sym_sys(BLANK);
	  }
      r = call_method (msgid, age, ticks, player, from, to,
        sym_get (o, v->method)->str, args, to);
      list_free (args);
      return r;
      /* found a match, quit */
    }
  }
  parents = list_dup (o->parents);      /* just in case o gets swapped out */
  for (i = 0; i < parents->len; i++) {
    r = call_verb_recursive (retrieve (parents->el[i].v.obj), msgid, age,
      ticks, player, from, to, verb, argstr);
    if (r != E_VERBNF) {
      list_free (parents);
      return r;                 /* found a match on ancestor, quit */
    }
  }
  list_free (parents);
  return E_VERBNF;              /* no match found */
}

static void execute (void)
{
  Op_entry *op;                 /* index into opcode table */

  ex_retval.type = NUM;
  ex_retval.v.num = 0;
  this = retrieve (frame.this);
  if (!this) {
    ex_state = STOPPED;
  }
  while (ex_state == RUNNING) {
    if ((op = opcodes[frame.m->code[frame.pc]])) {
      frame.last_opcode = frame.m->code[frame.pc];
      frame.pc++;
      if (op->builtin) {
		nargs = count_args();
      if ((op->builtin->maxargs >= 0 && nargs > op->builtin->maxargs)
		 || (nargs < op->builtin->minargs)) {
		    raise(E_NARGS);
	  } else {
	    (op->func)();
	  }
	  } else {
      (op->func) ();
      }
      frame.ticks++;
      if (frame.ticks >= max_ticks) {
        raise (E_TICKS);
      }
    } else {
      writelog ();
      fprintf (stderr, "execute(): unknown opcode %d\n",
        frame.m->code[frame.pc]);
      raise (E_INTERNAL);
      break;
    }
  }
  if (ex_state != BLOCKED) {
    clear_locks (this, frame.msgid);
    free_frame (&frame);
  }
  if (ex_state == STOPPED) {
    List *args;

    if (ex_retval.type == NUM && ex_retval.v.num == 0) {
      args = list_dup (zero);
    } else {
      args = list_new (1);
      args->el[0] = ex_retval;
    }

    (void) send_message (frame.msgid, frame.age, frame.ticks, frame.player,
      frame.this, frame.caller, sym_sys (RETURN), args, 0, frame.caller);
  }
}

String *add_traceback_header (String * str, Error e)
{
  str = string_cat (str, "ERROR:  ");
  str = string_cat (str, err_id2desc (e));
  str = string_cat (str, "\r\nraised in ");
  str = add_traceback (str);
  return str;
}

static String *add_traceback (String * str)
{
  int lineno;

  /* calcluate the offending line # using decompile_method() */
  (void) decompile_method ((String *) 0, frame.on, frame.m, 0, 0, 0, 0,
    frame.pc, &lineno);

  str = string_cat (str, "method \"");
  str = string_cat (str, sym_get (frame.on, frame.m->name)->str);
  str = string_cat (str, "\" on ");
  str = string_catobj (str, frame.on->id, 1);
  str = string_cat (str, ", line ");
  str = string_catnum (str, lineno);
#if 0
  if ((opc = opcodes[frame.last_opcode]->name)) {
    str = string_cat (str, ", last opcode ");
    str = string_cat (str, opc);
  }
  str = string_cat (str, ", PC = ");
  str = string_catnum (str, frame.pc);
#endif
  return str;
}

List *make_raise_args (Error e)
{
  List *r = list_new (2);

  r->el[0].type = ERR;
  r->el[0].v.err = e;
  r->el[1].type = STR;
  r->el[1].v.str = string_new (0);
  return r;
}

void send_raise (List * raise_args)
{
  (void) send_message (frame.msgid, frame.age, frame.ticks, frame.player,
    frame.this, frame.caller, sym_sys (RAISE), raise_args, 0, frame.caller);
}

void raise (Error e)
{
  Var ev;
  List *raise_args;

  ev.type = ERR;
  ev.v.err = e;
  if (e != E_STACKOVR) {
    push (ev);
  }
  if (e == E_NONE) {
    return;
  }
  switch (frame.m->ehandler[e]) {
  case EH_DEFAULT:
    raise_args = make_raise_args (e);
    raise_args->el[1].v.str = add_traceback_header (raise_args->el[1].v.str,
      e);
    send_raise (raise_args);
    ex_state = RAISED;
    break;
  case EH_IGNORE:
  case EH_CATCH:               /* catch is unimplemented, does an ignore for now */
    break;
  }
}

/*
 * send_message_and_block()
 *
 * Sends a message and queues up an event for the message's reply.
 * As with send_message(), "msg" and "args" are eaten.
 */

void
send_message_and_block (Objid from, Objid to, String * msg, List * args,
  Objid on)
{
  Event *e = MALLOC (Event, 1);
  Error r;
  Timeval now;

  *e = frame;
  e->msg = 0;
  gettimeofday (&now, 0);
  e->timeout_at = now;
  e->timeout_at.tv_sec += MSG_TIMEOUT;
  e->retry_interval = MSG_RETRY;
  e->retry_at = timer_addmsec (now, e->retry_interval);
  r = send_message (-1, frame.age + 1, frame.ticks, frame.player, from,
    to, msg, args, e, on);
  if (r == E_NONE) {
    ex_state = BLOCKED;
  } else {
    FREE (e);
    raise (r);
  }
}

void resume_method_return (Event * e, Var retval)
{
  frame = *e;                   /* restore execution parameters */
  ex_state = RUNNING;           /* start the beast */
  retval = var_dup (retval);
  push (retval);                /* push return value */
  execute ();
}

void resume_method_raise (Event * e, List * args)
{
  frame = *e;                   /* restore execution parameters */
  switch (e->m->ehandler[args->el[0].v.err]) {
  case EH_DEFAULT:
    if (args->el[1].v.str->str[0]) {
      args->el[1].v.str = string_cat (args->el[1].v.str, "\r\ncalled from ");
      args->el[1].v.str = add_traceback (args->el[1].v.str);
    } else {
      args->el[1].v.str = add_traceback_header (args->el[1].v.str,
        args->el[0].v.err);
    }
    send_raise (list_dup (args));
    ex_state = RAISED;
    break;
  case EH_IGNORE:
  case EH_CATCH:
    push (args->el[0]);
    ex_state = RUNNING;
    break;
  }
  execute ();
}

void resume_method_halt (Event * e)
{
  frame = *e;
  ex_state = HALTED;
  execute ();
}

void resume_method (Event * e)
{
  frame = *e;
  ex_state = RUNNING;
  execute ();
}

static void free_frame (Event * e)
{
  Var v;
  while (e->sp) {
    v = pop ();
    var_free (v);
  }
  list_free (e->args);
  free_method (e->on, e->m);
  free_object (e->on);
}

static void clear_locks (Object * o, int msgid)
{
  Lock *l, *prev = 0, *next;

  if (!o)
    return;

  for (l = o->locks; l; l = next) {
    next = l->next;
    if (l->added_by == msgid) {
      if (prev) {
        prev->next = l->next;
      } else {
        o->locks = l->next;
      }
      string_free (l->name);
      FREE (l);
    } else {
      prev = l;
    }
  }
}

void push (Var v)
{
  if (frame.sp >= STACK_SIZE) {
    raise (E_STACKOVR);
  } else {
    frame.stack[frame.sp++] = v;
  }
}

Var pop (void)
{
  if (frame.sp <= 0) {
    raise (E_STACKUND);
    return zero->el[0];
  } else {
    return frame.stack[--frame.sp];
  }
}

void pushpc (int i)
{
  Var n;

  n.type = PC;
  n.v.num = i;
  push (n);
}

void pushn (long i)
{
  Var n;

  n.type = NUM;
  n.v.num = i;
  push (n);
}

Var pop_args (int num)
{
  Var r;

  r.type = LIST;
  r.v.list = list_new (num);
  for (; num; num--) {
    r.v.list->el[num - 1] = pop ();
  }
  return r;
}

/* count_args()
 *
 * Rifle through the stack looking for an ARGSTART.  Shift all the
 * arguments on the stack down by one so that the argmarker is 
 * written over.  Return the number of arguments.
 */

int count_args(void)
{
    int		i;		/* argcount: index from current top of stack */
    Var		sval, pval;	/* value in sp - i position */

    pval.type = NUM;  pval.v.num = 0;

    for (i = 0; i < frame.sp; i++) {
	sval = frame.stack[frame.sp - i - 1];
	frame.stack[frame.sp - i - 1] = pval;
	if (sval.type == PC && sval.v.num == -ARGSTART) {
	    frame.sp--;
	    return i;
	}
	pval = sval;
    }
    raise(E_STACKUND);
    return -1;
}

/*
 * psub() - do % substitutions from variables
 *
 * `s' is the string to be substituted on.  Returns a String containing
 * the result:  Each %foo is replaced by variable 'foo'.
 *
 * NOTE:  psub() is in here (execute.c) because it needs access to the
 *    stack for local vars.
 */

static String *dosub (String * r, const char *vname, int vnamelen);

String *psub (const char *s)
{
  String *r = string_new (0);
  int state = 0;
  const char *vname = s;

  while (*s) {
    switch (state) {
    case 0:                    /* waiting for a % */
      if (*s == '%') {
        state = 1;
      } else {
        r = string_catc (r, *s);
      }
      break;
    case 1:                    /* got %, waiting for a varname */
      if (*s == '%') {          /* got %%, */
        r = string_catc (r, '%');       /* append % */
        state = 0;
      } else {
        vname = s;              /* got varname start */
        state = 2;
      }
      break;
    case 2:
      if (*s == '%') {          /* got %foo%, do substitution */
        r = dosub (r, vname, s - vname);
        state = 0;
      } else if (!isalnum ((int)*s)) {       /* got %foo, do substitution */
        r = dosub (r, vname, s - vname);
        r = string_catc (r, *s);        /* append terminating char */
        state = 0;
      }
      break;
    }
    s++;
  }
  if (state == 2) {
    r = dosub (r, vname, s - vname);
  }
  return r;
}

static String *dosub (String * r, const char *vname, int vnamelen)
{
  int varno;
  char *start;

  if (var_find_local_by_name (frame.on, frame.m, vname, vnamelen, &varno)) {
    start = r->str + r->len;
    r = var_tostring (r, frame.stack[varno], 0);
    if (isupper ((int)vname[0]) && islower ((int)*start)) {       /* if caps sub, */
      *start = toupper ((int)*start);        /* capitalize result */
    }
  }
  return r;
}

Timeval timer_sub (Timeval t1, Timeval t2)
{
  t1.tv_sec -= t2.tv_sec;
  t1.tv_usec -= t2.tv_usec;
  if (t1.tv_usec < 0) {
    t1.tv_usec += 1000000;
    t1.tv_sec--;
  }
  return t1;
}

Timeval timer_addmsec (Timeval t, int msec)
{
  t.tv_sec += msec / 1000;
  t.tv_usec += (msec % 1000) * 1000;
  if (t.tv_usec > 1000000) {
    t.tv_usec -= 1000000;
    t.tv_sec++;
  }
  return t;
}