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

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

static void do_match (int full);
static void base_echo( char newline );

/*********
 * base_echo
 * By Robin Powell
 *
 * Does the echo work, taking an argument for whether to send
 * a newline or not
 ***********/
void base_echo( char newline )
{
  Var		str, id;
  int		fd = -1;

  if (nargs > 1) {
    id = pop();
	if (id.type != NUM) {
	    var_free(id);  var_free(pop());
	    raise(E_ARGTYPE);
	    return;
	} else {
	    fd = id.v.num;
	}
  }
  str = pop();
  if (str.type != STR) {
    raise (E_ARGTYPE);
  }
  else
  {
	str.v.str = string_output( str.v.str );
	if (fd >= 0) {
	    tell_fd(fd, str.v.str->str, newline);
	    pushn(0);
  } else {
    tell(frame.this.id, str.v.str->str, newline);
    pushn (0);
  }
    }
    var_free(str);
}


void op_echo(void)
{
    base_echo( 1 );
}


/***********
 * op_echon
 * By Robin Powell
 *
 * Echo without the trailing \r\n
 ***********/

void
op_echon(void)
{
    base_echo( 0 );
}

#define LINELEN 256

void op_echo_file (void)
{
  Var		 str, id;
  FILE *f;
  char line[LINELEN], *newline;
  char filename[MAX_PATH_LEN];
  SOCKET fd = INVALID_SOCKET;

  if (nargs > 1) {
    id = pop();
    if (id.type != NUM) {
      var_free(id);  var_free(pop());
      raise(E_ARGTYPE);
      return;
    } else {
      fd = id.v.num;
    }
  }
  str = pop();
  if (str.type != STR) {
    raise (E_ARGTYPE);
  } else if (str_in("..", str.v.str->str)  /* no ..'s allowed */
	    || str.v.str->str[0] == '/') {   /* can't start with a / either */
    raise (E_FILE);
  } else {
	sprintf(filename, "%s/%s", RUNDIR, str.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';
		if (fd != INVALID_SOCKET) {
		    tell_fd(fd, line, 1);
		} else {
		    tell(frame.this.id, line, 1);
		}
      }
      fclose (f);
      pushn (0);
    }
  }
    var_free(str);
}

void op_connect(void)
{
    Var		hostname, port;
    int	r;

    port = pop();  hostname = pop();

    if (hostname.type != STR || port.type != NUM) {
	raise(E_ARGTYPE);
    } else {
		r = net_open_connect(frame.this.id, hostname.v.str->str, (short) port.v.num);
    	if (r < 0 || r > -5) {
		    switch (r) {
		      case -1:			/* address lookup failed */
  				raise(E_SERVERNF);
		      case -2:			/* address lookup timed out */
				raise(E_TIMEOUT);
		      case -3:			/* connect() failed */
		        raise(E_SERVERNF);
		      case -4:			/* connection in progress */
		        pushn(-1);
		        break;
		      default:
		        raise(E_SERVERNF);
		    }
		} else {
		    pushn(r);
		}
    }
    var_free(hostname);  var_free(port);
}

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

  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)
{
  destroy (frame.this);
  disconnect(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;

  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)
{
  pushn ((long) time ((time_t *) 0));
}

void op_ctime (void)
{
  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)
{
  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[RAND () % strlen (saltstuff)];
    salt[1] = saltstuff[RAND () % 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;

  s.type = STR;
  s.v.str = string_cpy (check_malloc ());
  push (s);
}

void op_cache_stats (void)
{
  Var s;

  s.type = STR;
  s.v.str = string_cpy (cache_stats ());
  push (s);
}

void op_explode (void)
{
  Var str, tokv, ret;
    int		nwords;
    char        *sep;
    char	space[2]=" ";

  sep = space;
  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 = malloc( strlen( tokv.v.str->str ) + 1 );
      strcpy( sep, tokv.v.str->str );
      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);
  if( sep != space )
	free( sep );
}

void op_random (void)
{
  Var arg;

  arg = pop ();
  if (arg.type != NUM) {
    raise (E_ARGTYPE);
  } else if (arg.v.num <= 0) {
    raise (E_RANGE);
  } else {
    pushn (RAND () % arg.v.num + 1);
  }
}

void op_setadd (void)
{
  Var new, list;

    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;

    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;

  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 (!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;

  name = pop ();
  if (name.type != STR) {
    raise (E_ARGTYPE);
  } else {
    for (l = this->locks; l; l = l->next) {
      if (!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;
  int i;

  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->on->ref++;
	e->m->ref++;
    e->blocked_on = BL_TIMER;
    gettimeofday (&e->timeout_at, 0);
    e->timeout_at.tv_sec += delay.v.num;
	e->timeout_at.tv_usec = 0;
	if (delay.v.num >= sleep_to_refresh) {
	    e->ticks = 0;
	    e->age = 0;
	}
	e->sp--;
    event_add (e);
    ex_state = BLOCKED;
  }
}

void op_typeof (void)
{
  Var arg;

  arg = pop ();
  pushn (arg.type);
}

void op_lengthof (void)
{
  Var arg;

  arg = pop ();
  switch (arg.type) {
  case STR:
    pushn (arg.v.str->len);
    break;
  case LIST:
    pushn (arg.v.list->len);
    break;
  case MAP:
    pushn (arg.v.map->num);
    break;
  default:
    raise (E_ARGTYPE);
    break;
  }
  var_free (arg);
}

void op_serverof (void)
{
  Var arg;

  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;

  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;

  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;

  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;

  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		 prepad = 0;

  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;

  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;

  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:
  case MAP:
    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;

  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:
  case MAP:
    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;

  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:
  case MAP:
    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;
  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);
}

void op_strcmp(void)
{
    Var		s1, s2;

    s2 = pop();  s1 = pop();
    if (s1.type != STR || s2.type != STR) {
	raise(E_ARGTYPE);
    } else {
	pushn(strcmp(s1.v.str->str, s2.v.str->str));
    }
    var_free(s1);  var_free(s2);
}

void op_tolower(void)
{
    Var		arg, ret;
    int		i;

    arg = pop();
    if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else {
	if (arg.v.str->ref == 1) {
	    ret = arg;
	    arg.v.str->ref++;
	} else {
	    ret.type = STR;
	    ret.v.str = string_cpy(arg.v.str->str);
	}
	for (i = 0; i < arg.v.str->len; i++) {
	    ret.v.str->str[i] = tolower((int)arg.v.str->str[i]);
	}
	push(ret);
    }
    var_free(arg);
}

void
op_toupper(void)
{
    Var		arg, ret;
    int		i;

    arg = pop();
    if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else {
	if (arg.v.str->ref == 1) {
	    ret = arg;
	    arg.v.str->ref++;
	} else {
	    ret.type = STR;
	    ret.v.str = string_cpy(arg.v.str->str);
	}
	for (i = 0; i < arg.v.str->len; i++) {
	    ret.v.str->str[i] = toupper((int)arg.v.str->str[i]);
	}
	push(ret);
    }
    var_free(arg);
}

void
op_f_index(void)
{
    Var		s1, s2, case_matters;

    case_matters.type = NUM;  case_matters.v.num = 0;
    if (nargs > 2) {
	case_matters = pop();
    }
    s2 = pop();  s1 = pop();
    if (s1.type != STR || s2.type != STR || case_matters.type != NUM) {
	raise(E_ARGTYPE);
    } else {
	pushn(strindex(s1.v.str->str, s2.v.str->str, case_matters.v.num));
    }
    var_free(s1);  var_free(s2);  var_free(case_matters);
}


void
op_rindex(void)
{
    Var		s1, s2, case_matters;

    case_matters.type = NUM;  case_matters.v.num = 0;
    if (nargs > 2) {
	case_matters = pop();
    }
    s2 = pop();  s1 = pop();
    if (s1.type != STR || s2.type != STR || case_matters.type != NUM) {
	raise(E_ARGTYPE);
    } else {
	pushn(strrindex(s1.v.str->str, s2.v.str->str, case_matters.v.num));
    }
    var_free(s1);  var_free(s2);  var_free(case_matters);
}