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

#include <stdio.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/time.h>

#include "config.h"
#include "cool.h"
#include "proto.h"
#include "sys_proto.h"
#include "netio.h"
#include "execute.h"
#include "servers.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 */

/*
 * 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)
 */

    start = cmd = str_dup(args->el[0].v.str->str);
    while (isspace(*start)) {
	start++;
    }
    if (!*start) {
	FREE(cmd);
	return E_VERBNF;
    }
    end = start;
    while (*end && !isspace(*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;
	    args->el[1].v.str = string_ncpy(dobj, dobjlen);
	    args->el[2].v.str = prep ? string_ncpy(prep, preplen) : sym_sys(BLANK);
	    args->el[3].v.str = iobj ? string_cpy(iobj) : 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;
    if (frame.on->id.id == SYS_OBJ) {	/* methods on SYS_OBJ */
	frame.ticks = 0;		/* always get a fresh supply of ticks */
    }
    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++;
	    (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;
}

/*
 * 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;

    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(*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(vname[0]) && islower(*start)) {	/* if caps sub, */
	    *start = toupper(*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;
}