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

void
op_shutdown(void)
{
    if (!is_wizard(frame.this)) {
	raise(E_PERM);
    } else {
	frame.pc++;			/* ignore nargs */
	server_running = 0;
	pushn(0);
    }
}

void
op_dump(void)
{
    frame.pc++;			/* ignore nargs */
    if (!is_wizard(frame.this)) {
	raise(E_PERM);
    } else {
	cache_sync();
	pushn(0);
    }
}

void
op_writelog(void)
{
    Var		arg;

    arg = pop();
    frame.pc++;			/* ignore nargs */
    if (frame.this.id != SYS_OBJ && !is_wizard(frame.this)) {
	raise(E_PERM);
    } else if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else {
	writelog();
	fprintf(stderr, "%s\n", arg.v.str->str);
	pushn(0);
    }
    var_free(arg);
}

void
op_boot(void)
{
    frame.pc++;			/* ignore nargs */
    boot(frame.this.id);
    pushn(0);
}

void
op_raise(void)
{
    Var		 e;
    List	*raise_args;

    frame.pc++;			/* ignore nargs */
    e = pop();
    if (e.type != ERR) {
	var_free(e);
	raise(E_ARGTYPE);
	(void) pop();
    } else {
	ex_state = RAISED;
	raise_args = make_raise_args(e.v.err);
	raise_args->el[1].v.str = add_traceback_header(raise_args->el[1].v.str,
				    e.v.err);
	send_raise(raise_args);
    }
}

void
op_pass(void)
{
    int		 nargs;
    Objid	 parent;
    Var		 args;

    nargs = frame.m->code[frame.pc++];
    parent.server = 0;
    parent.id = frame.m->code[frame.pc++];
    args = pop_args(nargs);
    send_message_and_block(frame.this, frame.this,
	string_dup(sym_get(frame.on, frame.m->name)), args.v.list, parent);
}

void
op_hasparent(void)
{
    Var		parent;
    frame.pc++;		/* skip nargs */

    parent = pop();

    if (parent.type != OBJ) {
	var_free(parent);
	raise(E_ARGTYPE);
    } else {
	pushn(hasparent(this, parent.v.obj));
    }
}

void
op_objsize(void)
{
    frame.pc++;		/* skip nargs */

    pushn(size_object(this));
}

void
op_spew_method(void)
{
    Var		 name, ret;
    Method	*m;

    frame.pc++;		/* skip nargs */
    name = pop();
    if (name.type != STR) {
	var_free(name);
	raise(E_ARGTYPE);
    } else {
	if ((m = find_method(this, name.v.str->str))) {
	    ret.type = STR;
	    ret.v.str = decode_method(this, m, 2);
	    push(ret);
	} else {
	    raise(E_METHODNF);
	}
    }
    var_free(name);
}

void
op_list_method(void)
{
    int		nargs = frame.m->code[frame.pc++];
    Var		name, lineno, brackets, indent, ret;
    Method	*m;

    lineno.type = brackets.type = indent.type = NUM;
    lineno.v.num = 1;  brackets.v.num = 0;  indent.v.num = 2;

    switch (nargs) {
      case 4:
	indent = pop();
      case 3:
	brackets = pop();
      case 2:
	lineno = pop();
      case 1:
	name = pop();
    }
    
    if (name.type != STR || indent.type != NUM || brackets.type != NUM
     || lineno.type != NUM) {
	var_free(name);  var_free(indent);  var_free(brackets);
	var_free(lineno);
	raise(E_ARGTYPE);
    } else {
	if ((m = find_method(this, name.v.str->str))) {
	    ret.type = STR;
	    ret.v.str = string_new(0);
	    ret.v.str = decompile_method(ret.v.str, this, m, lineno.v.num,
					 brackets.v.num, indent.v.num, 0, 0, 0);
	    push(ret);
	} else {
	    raise(E_METHODNF);
	}
    }
    var_free(name);
}

void
op_decompile(void)
{
    Var		ret;

    frame.pc++;		/* skip nargs */
    ret.type = STR;
    ret.v.str = decompile_object(this);
    push(ret);
}

void
op_find_method(void)
{
    Var		arg, ret;
    Object	*where;

    frame.pc++;		/* skip nargs */
    arg = pop();
    if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else {
	(void) find_method_recursive(this, arg.v.str->str, &where);
	ret.type = OBJ;
	if (where) {
	    ret.v.obj = where->id;
	} else {
	    ret.v.obj.server = 0;
	    ret.v.obj.id = NOTHING;
	}
	push (ret);
    } /* if */
    var_free(arg);
}
void
op_this(void)
{
    Var		v;

    v.type = OBJ; v.v.obj = frame.this;
    push(v);
}

void
op_player(void)
{
    Var		v;

    v.type = OBJ; v.v.obj = frame.player;
    push(v);
}

void
op_caller(void)
{
    Var		v;

    v.type = OBJ; v.v.obj = frame.caller;
    push(v);
}

void
op_args(void)
{
    Var		v;

    v.type = LIST; v.v.list = list_dup(frame.args);
    push(v);
}

void
op_setplayer(void)
{
    Var		newplayer;

    newplayer = pop();
    if (newplayer.type != OBJ) {
	raise(E_ARGTYPE);
    } else {
	frame.player = newplayer.v.obj;
    }
    var_free(newplayer);
}

static void
do_programming(Playerid id, int what1, GENPTR what2)
{
    GENPTR	*progwhat = MALLOC(GENPTR, 2);

    progwhat[0] = (GENPTR) what1;
    progwhat[1] = what2;
    if (start_programming(id, progwhat)) {
	tell(id, "Couldn't enter programming mode -- can't open temp file.");
    } else {
	tell(id, "Entering programming mode.  Use \".\" to end input.");
    }
}

void
op_program(void)
{
    Var		obj, method;

    switch(frame.m->code[frame.pc++]) {
      case 0:	/* program some objects */
	do_programming(frame.this.id, -1, 0);
	pushn(0);
	break;
      case 1:	/* invalid */
	method = pop(); var_free(method);
	raise(E_RANGE);
	break;
      case 2:	/* program a single method */
	method = pop();
	obj = pop();
	if (obj.type != OBJ || method.type != STR) {
	    raise(E_ARGTYPE);
	} else if (!valid(obj.v.obj)) {
	    raise(E_OBJNF);
	} else if (!can_program(frame.this, obj.v.obj)) {
	    raise(E_PERM);
	} else if (!valid_ident(method.v.str->str)) {
	    raise(E_METHODNF);	/* not quite the right error, but hey */
	} else {
	    do_programming(frame.this.id, obj.v.obj.id,
			   (GENPTR) string_dup(method.v.str));
	    pushn(0);
	}
	var_free(obj);  var_free(method);
	break;
    }
}

static Playerid		 progr;
static const char	*mem_code;
static int		 code_pos, mem_eof;
static int		 mem_getc(void);
static void		 mem_ungetc(int c);
static void		 mem_perror(const char *s);

static int
mem_getc(void)
{
    if (mem_code[code_pos]) {
	return mem_code[code_pos++];
    } else {
	mem_eof = 1;
	return EOF;
    }
}

static void
mem_ungetc(int c)
{
    if (code_pos > 0 && !mem_eof) {
	code_pos--;
    }
}

static void
mem_perror(const char *s)
{
    tell(progr, s);
}

void
op_compile(void)
{
    Var		obj, method, pcode;
    int		nargs = frame.m->code[frame.pc++];
    Object	*o;

    pcode = pop();
    if (pcode.type != STR) {
	var_free(pcode);
	while (--nargs) {
	    method = pop();
	    var_free(method);
	}
	raise(E_ARGTYPE);
	return;
    }
    mem_code = pcode.v.str->str;
    code_pos = 0;
    mem_eof = 0;
    progr = frame.this.id;
    switch(nargs) {
      case 1:	/* program some objects */
	pushn(compile(progr, mem_getc, mem_ungetc, mem_perror, 0, 0, 0, 0, 0));
	break;
      case 2:	/* invalid */
	method = pop();  var_free(method);
	raise(E_RANGE);
	break;
      case 3:	/* program a single method */
	method = pop();
	obj = pop();
	if (obj.type != OBJ || method.type != STR) {
	    raise(E_ARGTYPE);
	} else if (!(o = retrieve(obj.v.obj))) {
	    raise(E_OBJNF);
	} else if (!can_program(frame.this, obj.v.obj)) {
	    raise(E_PERM);
	} else if (!valid_ident(method.v.str->str)) {
	    raise(E_METHODNF);	/* not quite the right error, but hey */
	} else {
	    pushn(compile(progr, mem_getc, mem_ungetc, mem_perror, 1, o,
			   method.v.str, 0, 0));
	    
	}
	var_free(obj);  var_free(method);
	this = retrieve(frame.this);	/* in case we recompiled "this" */
	break;
    }
    var_free(pcode);
}

void
op_verb(void)
{
    Var		 verb, method, prep;
    int		 verbno, methodno, prepno;

    frame.pc++;				/* ignore nargs */
    method = pop();
    prep = pop();
    verb = pop();
    if (verb.type != STR || prep.type != STR || method.type != STR) {
	var_free(verb);  var_free(prep);  var_free(method);
	raise(E_ARGTYPE);
    } else {
	verbno = sym_add(this, verb.v.str);
	if (prep.v.str->str[0]) {
	    prepno = sym_add(this, prep.v.str);
	} else {
	    string_free(prep.v.str);
	    prepno = -1;
	}
	methodno = sym_add(this, method.v.str);
	verb_add(this, verbno, prepno, methodno);
	cache_put(this, frame.this.id);
	pushn(0);
    }
}

void
op_rmverb(void)
{
    Var		 verb;

    frame.pc++;
    verb = pop();
    if (verb.type != STR) {
	raise(E_ARGTYPE);
    } else {
	pushn(verb_rm(this, verb.v.str->str));
	cache_put(this, frame.this.id);
    }
    var_free(verb);
} 

void
op_rmmethod(void)
{
    Var		method;

    frame.pc++;
    method = pop();
    if (method.type != STR) {
	raise(E_ARGTYPE);
    } else {
	raise(rm_method(this, method.v.str->str));
	cache_put(this, frame.this.id);
    }
    var_free(method);
}

void
op_rmvar(void)
{
    Var		varname;

    frame.pc++;
    varname = pop();
    if (varname.type != STR) {
	raise(E_ARGTYPE);
    } else {
	raise(var_rm_global(this, varname.v.str->str));
	cache_put(this, frame.this.id);
    }
    var_free(varname);
}

void
op_listinsert(void)
{
    int		nargs = frame.m->code[frame.pc++];
    Var		list, value, pos;

    pos.type = NUM;
    pos.v.num = -1;
    if (nargs > 2) {
	pos = pop();
    }
    value = pop();
    list = pop();
    if (list.type != LIST || pos.type != NUM) {
	var_free(pos);  var_free(list);  var_free(value);
	raise(E_ARGTYPE);
    } else if (pos.v.num > list.v.list->len) {
	raise(E_RANGE);
    } else {
	list.v.list = list_insert(list.v.list, value, pos.v.num);
	push(list);
    }
}

void
op_listappend(void)
{
    int		nargs = frame.m->code[frame.pc++];
    Var		list, value, pos;

    pos.type = NUM;
    pos.v.num = -1;
    if (nargs > 2) {
	pos = pop();
    }
    value = pop();
    list = pop();
    if (list.type != LIST || pos.type != NUM) {
	var_free(pos);  var_free(list);  var_free(value);
	raise(E_ARGTYPE);
    } else if (pos.v.num > list.v.list->len) {
	raise(E_RANGE);
    } else {
	list.v.list = list_append(list.v.list, value, pos.v.num);
	push(list);
    }
}

void
op_listdelete(void)
{
    Var		pos;	/* position to delete */
    Var		list;	/* list to delete it from */

    pos = pop();
    list = pop();
    frame.pc++;		/* skip nargs */
    if (list.type != LIST || pos.type != NUM) {
	var_free(pos);  var_free(list);
	raise(E_ARGTYPE);
    } else if (pos.v.num < 1 || pos.v.num > list.v.list->len) {
	var_free(list);
	raise(E_RANGE);
    } else {
	list.v.list = list_delete(list.v.list, pos.v.num);
	push(list);
    }
}

void
op_listassign(void)
{
    Var		pos;		/* position to assign */
    Var		value;		/* value to assign it */
    Var		list;		/* list to modify */

    pos = pop();
    value = pop();
    list = pop();
    frame.pc++;		/* skip nargs */
    if (list.type != LIST || pos.type != NUM) {
	var_free(pos);  var_free(list);  var_free(value);
	raise(E_ARGTYPE);
    } else if (pos.v.num < 1 || pos.v.num > list.v.list->len) {
	var_free(list);	 var_free(value);
	raise(E_RANGE);
    } else {
	list.v.list = list_assign(list.v.list, value, pos.v.num);
	push(list);
    }
}

void
op_strsub(void)
{
    int		nargs;
    Var		source, what, with, caseflag, ret;

    caseflag.type = NUM;  caseflag.v.num = 0;
    nargs = frame.m->code[frame.pc++];
    if (nargs > 3) {
	caseflag = pop();
    }
    with = pop();  what = pop();  source = pop();
    if (source.type != STR || what.type != STR || with.type != STR) {
	raise(E_ARGTYPE);
    } else {
	ret.type = STR;
	ret.v.str = strsub(source.v.str->str, what.v.str->str, with.v.str->str,
			   ISTRUE(caseflag));
	push(ret);
    }
    var_free(source);  var_free(what);  var_free(with);  var_free(caseflag);
}

void
op_psub(void)
{
    Var		source, ret;

    source = pop();
    frame.pc++;		/* skip nargs */
    if (source.type != STR) {
	raise(E_ARGTYPE);
    } else {
	ret.type = STR;
	ret.v.str = psub(source.v.str->str);
	push(ret);
    }
    var_free(source);
}

void
op_servers(void)
{
    Var			servlist;

    servlist.type = LIST;
    servlist.v.list = list_dup(server_list);

    frame.pc++;		/* skip nargs */
    push(servlist);
}

void
op_ps(void)
{
    Var		v;

    frame.pc++;		/* skip nargs */
    v.type = LIST;  v.v.list = ps();
    push(v);
} /* op_ps() */

void
op_kill(void)
{
    Var		pid;
    int		r;

    frame.pc++;		/* skip nargs */

    pid = pop();
    if (pid.type != NUM) {
	var_free(pid);
	raise(E_ARGTYPE);
    } else {
	r = cmkill(pid.v.num, frame.caller);
        switch (r) {
	  case 0:
	  case -1:
	    pushn(r);
	    break;
	  case -2:
	    raise(E_PERM);
	    break;
	}
    } /* if */
} /* op_kill() */