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

#include <stdio.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/types.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"

static void	do_match(int full);

void
op_echo(void)
{
    Var		arg;

    frame.pc++;			/* ignore nargs */
    arg = pop();
    if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else {
	tell(frame.this.id, arg.v.str->str);
	pushn(0);
    }
    var_free(arg);
}

#define LINELEN	256

void
op_echo_file(void)
{
    Var		 arg;
    FILE	*f;
    char	 line[LINELEN], *newline;
    char	 filename[MAX_PATH_LEN];

    frame.pc++;			/* ignore nargs */
    arg = pop();
    if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else if (str_in("..", arg.v.str->str)  /* no ..'s allowed */
	    || arg.v.str->str[0] == '/') {   /* can't start with a / either */
	raise(E_FILE);
    } else {
	sprintf(filename, "%s/%s", RUNDIR, arg.v.str->str);
        if (!(f = fopen(filename, "r"))) {
	    raise(E_FILE);
	} else {
	    while (fgets(line, LINELEN, f)) {
		if ((newline = index(line, '\n'))) *newline = '\0';
		if ((newline = index(line, '\r'))) *newline = '\0';
		tell(frame.this.id, line);
	    }
	    fclose(f);
	    pushn(0);
	}
    }
    var_free(arg);
}

void
op_clone(void)
{
    Var		oid;
    Object *o = clone(frame.this);

    frame.pc++;	/* ignore nargs */
    oid.type = OBJ;
    if (o) {
	oid.v.obj = o->id;
    } else {
	oid.v.obj.id = -1;
	oid.v.obj.server = 0;
    }
    push(oid);
}

void
op_destroy(void)
{
    frame.pc++;	/* ignore nargs */
    destroy(frame.this);
    boot(frame.this.id);		/* just in case it's a player */
    this = 0;		/* can't do anything with this object anymore */
    pushn(0);
    ex_state = STOPPED;
}

void
op_chparents(void)
{
    Var		newparents;
    Error	r;

    frame.pc++;	/* ignore nargs */
    newparents = pop();
    if (newparents.type != LIST) {
	var_free(newparents);
	raise(E_ARGTYPE);
    } else if ((r = check_parents(frame.caller, this, newparents.v.list))
		== E_NONE) {
	list_free(this->parents);
	this->parents = newparents.v.list;
	cache_put(this, frame.this.id);
	pushn(0);
    } else {
	var_free(newparents);
	raise(r);
    }
}

extern time_t	time( time_t * );

void
op_time(void)
{
    frame.pc++;	/* ignore nargs */
    pushn( (long) time( (time_t *) 0));
}

void
op_ctime(void)
{
    int		nargs = frame.m->code[frame.pc++];
    Var		arg, ret;
    long	t;

    if (nargs == 0) {
	t = time( (time_t *) 0);
    } else {
	arg = pop();
	if (arg.type != NUM) {
	    raise(E_ARGTYPE);
	    return;
	}
	t = arg.v.num;
    }
    ret.type = STR;
    ret.v.str = string_cpy(ctime((time_t *) &t));
    ret.v.str->str[--ret.v.str->len] = '\0';	/* nuke the newline */
    push(ret);
}

void
op_crypt(void)
{
    int		nargs = frame.m->code[frame.pc++];
    Var		a1, a2, r;
    char	salt[3];
    static	char saltstuff[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";

    if (nargs > 1) {
	a2 = pop();
	if (a2.type != STR) {
	    var_free(a2);
	    a2 = pop(); var_free(a2);
	    raise(E_ARGTYPE);
	    return;
	} else if (a2.v.str->len != 2) {
	    var_free(a2);
	    a2 = pop(); var_free(a2);
	    raise(E_RANGE);
	    return;
	}
	salt[0] = a2.v.str->str[0];
	salt[1] = a2.v.str->str[1];
	var_free(a2);
    } else {
	salt[0] = saltstuff[random() % strlen(saltstuff)];
	salt[1] = saltstuff[random() % strlen(saltstuff)];
    }
    salt[2] = '\0';
    a1 = pop();
    if (a1.type != STR) {
	var_free(a1);
	raise(E_ARGTYPE);
	return;
    }
    r.type = STR;
    r.v.str = string_cpy(crypt(a1.v.str->str, salt));
    var_free(a1);
    push(r);
}

void
op_checkmem(void)
{
    Var		s;

    frame.pc++;		/* ignore nargs */
    s.type = STR;
    s.v.str = string_cpy(check_malloc());
    push(s);
}

void
op_cache_stats(void)
{
    Var		s;

    frame.pc++;		/* ignore nargs */
    s.type = STR;
    s.v.str = string_cpy(cache_stats());
    push(s);
}

void
op_explode(void)
{
    Var		str, tokv, ret;
    int		nwords, nargs;
    char        sep = ' ';

    nargs = frame.m->code[frame.pc++];
    sep = ' ';
    if (nargs > 1) {
	tokv = pop();
	if (tokv.type != STR) {
	    var_free(tokv);  str = pop(); var_free(str);
	    raise(E_ARGTYPE);  return;
	} else if (!tokv.v.str->str[0]) {
	    var_free(tokv);  str = pop(); var_free(str);
	    raise(E_RANGE);  return;
	} else {
	    sep = tokv.v.str->str[0];  var_free(tokv);
	}
    }
    str = pop();
    if (str.type != STR) {
	raise(E_ARGTYPE);
    } else {
	nwords = count_words(str.v.str->str, sep);
	ret.type = LIST;
	ret.v.list = explode(str.v.str->str, sep, nwords);
	push(ret);
    }
    var_free(str);
}

void
op_random(void)
{
    Var		arg;

    frame.pc++;			/* skip nargs */
    arg = pop();
    if (arg.type != NUM) {
	raise(E_ARGTYPE);
    } else if (arg.v.num <= 0) {
	raise(E_RANGE);
    } else {
	pushn(random() % arg.v.num + 1);
    }
}

void
op_setadd(void)
{
    Var		new, list;

    frame.pc++;	/* ignore nargs */
    new = pop(); list = pop();
    if (list.type != LIST) {
	var_free(list); var_free(new);
	raise(E_ARGTYPE);
    } else {
	list.v.list = list_setadd(list.v.list, new);
	push(list);
    }
}

void
op_setremove(void)
{
    Var		what, list;

    frame.pc++;	/* ignore nargs */
    what = pop(); list = pop();
    if (list.type != LIST) {
	var_free(list);
	raise(E_ARGTYPE);
    } else {
	list.v.list = list_setremove(list.v.list, what);
	push(list);
    }
    var_free(what);
}

void
op_lock(void)
{
    Var		 name;
    Event	*e;
    Lock	*l, *new;

    frame.pc++;					/* ignore nargs */
    name = pop();
    if (name.type != STR) {
	raise(E_ARGTYPE);
    } else {
	pushn(0);		/* lock() always returns 0 */
	for (l = this->locks; l; l = l->next) {
	    if (!cool_strcasecmp(l->name->str, name.v.str->str)) {
		  /* found lock */
		e = MALLOC(Event, 1);
		*e = frame;
		e->blocked_on = BL_LOCK;
		e->lock = string_dup(name.v.str);
		gettimeofday(&e->timeout_at, 0);
		e->timeout_at.tv_sec += LOCK_TIMEOUT;
		e->msg = 0;
		event_add(e);
		ex_state = BLOCKED;
		break;
	    }
	}
	new = MALLOC(Lock, 1);
	new->name = string_dup(name.v.str);
	new->added_by = frame.msgid; 
	new->next = 0;
	if (!this->locks) {
	    this->locks = new;
	} else {
	    for (l = this->locks; l->next; l = l->next)
		;
	    l->next = new;
	}
    }
    var_free(name);
}

void
op_unlock(void)
{
    Var		name;
    Lock       *l, *prev = 0;

    frame.pc++;					/* ignore nargs */
    name = pop();
    if (name.type != STR) {
	raise(E_ARGTYPE);
    } else {
	for (l = this->locks; l; l = l->next) {
	    if (!cool_strcasecmp(l->name->str, name.v.str->str)
	      && l->added_by == frame.msgid) {	/* found lock */
		if (prev) {					/* remove it */
		    prev->next = l->next;
		} else {
		    this->locks = l->next;
		}
		string_free(l->name);
		FREE(l);
		pushn(1);
		break;
	    }
	    prev = l;
	}
	if (!l) {
	    pushn(0);
	}
    }
    var_free(name);
}

void
op_at(void)
{
    int		i, endat = frame.m->code[frame.pc++];
    Var		at_time;

    at_time = pop();
    if (at_time.type != NUM) {
	raise(E_ARGTYPE);
	pop();			/* at is a statement, has no value */
    } else {
	Event *e = MALLOC(Event, 1);

	*e = frame;
	e->blocked_on = BL_TIMER;
	e->timeout_at.tv_sec = at_time.v.num;
	e->timeout_at.tv_usec = 0;
	e->on->ref++;
	e->m->ref++;
	e->args = list_dup(frame.args);
	e->msg = 0;
	for (i = 0; i < frame.nvars ;i++) {
	    e->stack[i] = var_dup(e->stack[i]);
	}
	e->sp = e->nvars;
	e->stack[e->sp].type = NUM;
	e->stack[e->sp].v.num = -1;		/* add a new terminator */
	e->sp++;
	event_add(e);
    }
    frame.pc = endat;
}

void
op_sleep(void)
{
    Var		delay;

    delay.type = NUM;  delay.v.num = 0;
    if (frame.m->code[frame.pc++]) {
	delay = pop();
    }
    if (delay.type != NUM) {
	raise(E_ARGTYPE);
    } else {
	Event *e = MALLOC(Event, 1);

	pushn(0);			/* sleep() always returns 0 */
	*e = frame;
	e->blocked_on = BL_TIMER;
	gettimeofday(&e->timeout_at, 0);
	e->timeout_at.tv_sec += delay.v.num;
	event_add(e);
	ex_state = BLOCKED;
    }
}

void
op_typeof(void)
{
    Var		arg;

    frame.pc++;		/* ignore nargs */
    arg = pop();
    pushn(arg.type);
}

void
op_lengthof(void)
{
    Var		arg;

    frame.pc++;		/* ignore nargs */
    arg = pop();
    switch (arg.type) {
      case STR:
	pushn(arg.v.str->len);
	break;
      case LIST:
	pushn(arg.v.list->len);
	break;
      default:
	raise(E_ARGTYPE);
	break;
    }
    var_free(arg);
}

void
op_serverof(void)
{
    Var		arg;

    frame.pc++;		/* ignore nargs */
    arg = pop();
    if (arg.type != OBJ) {
	raise(E_ARGTYPE);
    } else {
	pushn(arg.v.obj.server);
    }
    var_free(arg);
}

void
op_servername(void)
{
    Var		arg, ret;

    frame.pc++;		/* skip nargs */
    arg = pop();
    if (arg.type != OBJ) {
	var_free(arg);
	raise(E_ARGTYPE);
    } else {
	ret.type = STR;
	ret.v.str = string_cpy(serv_id2name(arg.v.obj.server));
	push(ret);
    }
}

void
op_verbs(void)
{
    Verbdef	*v;
    int		 i, nverbs = 0;
    Var		 ret;
    List	 *info;

    frame.pc++;			/* ignore nargs */
    for (v = this->verbs; v; v = v->next) {
	nverbs++;
    }
    ret.type = LIST;
    ret.v.list = list_new(nverbs);
    for (v = this->verbs, i = 0; v; v = v->next, i++) {
	info = list_new(3);
	info->el[0].type = info->el[1].type = info->el[2].type = STR;
	info->el[0].v.str = sym_get(this, v->verb);
	if(v->prep >= 0) {
	    info->el[1].v.str = sym_get(this, v->prep);
	} else {
	    info->el[1].v.str = sym_sys(BLANK);
	}
	info->el[2].v.str = sym_get(this, v->method);
	info->el[0].v.str->ref++;
	info->el[1].v.str->ref++;
	info->el[2].v.str->ref++;
	ret.v.list->el[i].type = LIST;
	ret.v.list->el[i].v.list = info;
    }
    push(ret);
}

void
op_vars(void)
{
    Vardef	*v;
    Var		 ret;
    int		 i, hval;

    frame.pc++;			/* ignore nargs */
    if (this->vars) {
	ret.type = LIST;
	ret.v.list = list_new(this->vars->num);
	i = 0;
	for (hval = 0; hval < this->vars->size; hval++) {
	    for (v = this->vars->table[hval]; v; v = v->next) {
		ret.v.list->el[i].type = STR;
		ret.v.list->el[i].v.str = sym_get(this, v->name);
		ret.v.list->el[i].v.str->ref++;
		i++;
	    }
	}
    } else {
	ret.type = LIST;
	ret.v.list = list_dup(empty_list);
    }
    push(ret);
}

void
op_methods(void)
{
    Method	*m;
    Var		 ret;
    int		 i, hval;

    frame.pc++;			/* ignore nargs */
    if (this->methods) {
	ret.type = LIST;
	ret.v.list = list_new(this->methods->num);
	i = 0;
	for (hval = 0; hval < this->methods->size; hval++) {
	    for (m = (Method *) this->methods->table[hval]; m;
		 m = m->next) {
		ret.v.list->el[i].type = STR;
		ret.v.list->el[i].v.str = sym_get(this, m->name);
		ret.v.list->el[i].v.str->ref++;
		i++;
	    }
	}
    } else {
	ret.type = LIST;
	ret.v.list = list_dup(empty_list);
    }
    push(ret);
}

void
op_parents(void)
{
    Var		 ret;

    ret.type = LIST;
    ret.v.list = list_dup(this->parents);
    push(ret);
}

void
op_pad(void)
{
    Var		 what, len, with, ret;
    char	 tok = ' ';
    int		 nargs, prepad = 0;

    nargs = frame.m->code[frame.pc++];
    if (nargs > 2) {
	with = pop();
	if (with.type != STR) {
	    var_free(with); raise(E_ARGTYPE); return;
	} else if (with.v.str->len < 1) {
	    var_free(with); raise(E_RANGE); return;
	} else {
	    tok = with.v.str->str[0];
	}
    }
    len = pop();  what = pop();
    if (len.type != NUM || what.type != STR) {
	raise(E_ARGTYPE);
    } else {
	ret.type = STR;
	if (what.v.str->ref == 1) {			/* if just one ref, */
	    ret.v.str = string_dup(what.v.str);		/* use this str */
	} else {
	    ret.v.str = string_cpy(what.v.str->str);	/* make a copy */
	}
	if (len.v.num < 0) {
	    len.v.num = -len.v.num;
	    prepad = 1;
	}
	if (len.v.num < what.v.str->len) {
	    ret.v.str->str[len.v.num] = '\0';		/* truncate */
	} else if (prepad) {				/* pad */
	    ret.v.str = string_prepad(ret.v.str, len.v.num - what.v.str->len, tok);
	} else {
	    ret.v.str = string_pad(ret.v.str, len.v.num - what.v.str->len, tok);
	}
	ret.v.str->len = len.v.num;
	push(ret);
    }
    var_free(len);  var_free(what);
}

void
op_tostr(void)
{
    Var		 what, ret;

    frame.pc++;			/* skip nargs */
    what = pop();
    ret.type = STR;
    ret.v.str = string_new(0);
    ret.v.str = var_tostring(ret.v.str, what, 1);
    push (ret);
    var_free(what);
}

void
op_tonum(void)
{
    Var		 arg, ret;

    frame.pc++;				/* ignore nargs */
    ret.type = NUM;
    arg = pop();
    switch (arg.type) {
      case STR:
	ret.v.num = atoi(arg.v.str->str);
	break;
      case NUM:
	ret = arg;
	break;
      case OBJ:
	ret.v.num = arg.v.obj.id;
	break;
      case LIST:  case PC:
	raise(E_ARGTYPE);
	var_free(arg);
	return;
      case ERR:
	ret.v.num = (int) arg.v.err;
	break;
    }
    var_free(arg);
    push(ret);
}

void
op_toobj(void)
{
    Var		 arg, ret;

    frame.pc++;				/* ignore nargs */
    ret.type = OBJ;
    arg = pop();
    switch (arg.type) {
      case STR:
	if (!parse_obj(arg.v.str->str, &ret.v.obj)) {
	    ret.v.obj.id = -1;
	    ret.v.obj.server = 0;
	}
	var_free(arg);
	break;
      case NUM:
	ret.v.obj.server = 0;
	ret.v.obj.id = arg.v.num;
	break;
      case OBJ:
	ret = arg;
	break;
      case LIST:  case PC:
	var_free(arg);
	raise(E_ARGTYPE);
	return;
      case ERR:
	ret.v.obj.server = 0;
	ret.v.obj.id = (int) arg.v.err;
	break;
    }
    push (ret);
}

void
op_toerr(void)
{
    Var		 arg, ret;

    frame.pc++;				/* ignore nargs */
    ret.type = ERR;
    ret.v.err = E_NONE;
    arg = pop();
    switch (arg.type) {
      case STR:
	if (!parse_err(arg.v.str->str, &ret.v.err)) {
	    ret.v.err = -1;
	}
	break;
      case NUM:
	ret.v.err = arg.v.num;
	break;
      case OBJ:
	ret.v.err = arg.v.obj.id;
	break;
      case LIST:  case PC:
	raise(E_ARGTYPE);
	var_free(arg);
	return;
      case ERR:
	ret = arg;
	break;
    }
    var_free(arg);
    push (ret);
}

static void
do_match(int full)
{
    Var		template, str, marker;
    int		nargs = frame.m->code[frame.pc++];
    char	tok;

    if (nargs == 3) {
	marker = pop();
	if (marker.type != STR || !(tok = marker.v.str->str[0])) {
	    var_free(marker);
	    str = pop();  template = pop();
	    var_free(str); var_free(template);	/* free other 2 args */
	    raise(E_ARGTYPE);
	    return;
	}
	var_free(marker);
    } else {
	tok = ' ';			/* default separator is a space */
    }
    str = pop();  template = pop();	/* get 2 args */

    if (template.type != STR || str.type != STR) {
	raise(E_ARGTYPE);
    } else if (full) {
	pushn(match_full(template.v.str->str, str.v.str->str, tok));
    } else {
	pushn(match(template.v.str->str, str.v.str->str, tok));
    }
    var_free(template);  var_free(str);
}

void
op_match(void)
{
    do_match(0);
}

void
op_match_full(void)
{
    do_match(1);
}