/* Copyright 1989, 1990 by James Aspnes, David Applegate, and Bennet Yee */
/* See the file COPYING for distribution information */
/* Command interpreter */
#include "os.h"
#include "config.h"
#include "db.h"
#include "bytecode.h"
#include "globals.h"
#include "externs.h"
#include "interface.h"

/* for before and after handlers */
void do_action (datum actor, datum thing, datum verb)
{
  datum action;

  if ((action = lookup_action (thing, verb)) != NOTHING) {
    /* got it */
    PUSH_GLOBALS {
      you = actor;
      me = thing;
      text = NOTHING;
      run_action (action);
    }
    POP_GLOBALS;
  }
}

/* runs action verb on obj with you = actor */
/* mt and t hold string versions of mtext and text */
/* returns nonzero if successful */
static int try_action (datum actor, datum verb, datum obj,
  const char *mt, const char *t)
{
  datum action;
  datum location;

  if ((action = lookup_action (obj, verb)) != NOTHING) {
    /* got it */
    location = safe_get (actor, location);

    do_action (actor, location, BEFORE_ACTION);
    do_action (actor, actor, BEFORE_ACTION);
    PUSH_GLOBALS {
      you = actor;
      me = obj;
      mtext = intern (mt);
      text = intern (t);
      run_action (action);
    }
    POP_GLOBALS;
    do_action (actor, actor, AFTER_ACTION);
    do_action (actor, location, AFTER_ACTION);
    return 1;
  } else {
    return 0;
  }
}

static datum special_match (datum actor, datum location, const char *string)
{
  datum x;

  if (!strcmp (string, ME_STRING)) {
    return actor;
  } else if (!strcmp (string, HERE_STRING)) {
    return location;
  } else if (*string == NUMERIC_NAME_TOKEN
    && (x = atol (string + 1)) != NOTHING && controls (actor, x)) {
    return x;
  } else {
    return NOTHING;
  }
}

static void parse_program (datum actor, const char *command)
{
  byte *code;

  if (!flag_set (actor, F_PROGRAMMER)) {
    notify (actor, "You are not permitted to run programs.");
    return;
  } else if ((code = compile (command)) == 0) {
    notify (actor, compile_error);
  } else {
    PUSH_GLOBALS {
      me = you = actor;
      mtext = text = NOTHING;
      run_code (code);
      free ((void *) code);
    } POP_GLOBALS;
  }
  return;
}

/* tries performing a match on vtext, itext, and otext */
/* actor sets you */
/* location is actor's location */
/* actor_contents and location_contents are the contents sets */
/* vtext is the verb */
/* otext is the object being acted on */
/* itext is the unparsed text */
/* returns nonzero if successful */
static int try_match (datum actor,
  datum location,
  set actor_contents,
  set loc_contents, const char *vtext, const char *otext, const char *itext)
{
  datum verb;                   /* interned vtext */
  datum objname;                /* interned otext */
  datum obj;                    /* matched object */

  if ((verb = intern_soft (vtext)) != NOTHING) {
    if ((obj = special_match (actor, location, otext)) != NOTHING) {
      if (try_action (actor, verb, obj, otext, itext))
        return 1;
    } else if ((objname = intern_soft (otext)) != NOTHING) {
      SET_FOREACH_MATCH (loc_contents, objname, obj) {
        if (try_action (actor, verb, obj, otext, itext))
          return 1;
      }
      END_SET_FOREACH;
      SET_FOREACH_MATCH (actor_contents, objname, obj) {
        if (try_action (actor, verb, obj, otext, itext))
          return 1;
      }
      END_SET_FOREACH;
    }
  }

  /* we lost */
  return 0;
}


/*** parse_command ***/
/* Parses a command line and sends out the appropriate methods. */

/* Tries the following templates, in order: */
/* Verb (on room) => verb handler on room */
/* Verb (on actor) => verb handler on actor */
/* Object => _invoke handler on matched object */
/* Verb Object => verb handler on object */
/* Verb Object1 Prep Object2 =>
      verb<prep on obj1
   or verb>prep on obj2
   or verb^prep on room
   or verb^prep on actor */
/* Verb text => verb handler on room */
/* Verb text => verb handler on actor */
/* ??? => _default on room */
/* ??? => _default on actor */

/* when matching an object, the room's contents are always checked first */

void parse_command (datum actor, const char *safe_command)
{
  char cbuf[MAX_STRLEN + 1];    /* mutable copy of safe_command */

  datum verb;                   /* interned verb */

  char vbuf[MAX_STRLEN + 1];    /* buffer for compound verb */
  char *vp;                     /* pointer to left/right location in vbuf */

  int c;                        /* saved character */

  char *command;                /* start of command */
  char *args;                   /* start of verb arguments */
  char *obj1end;                /* end of first obj */

  char *pstart;                 /* start of preposition */
  char *pend;                   /* end of preposition */

  char *obj2start;              /* start of second object */

  datum obj;                    /* matched object */
  datum location;               /* location of actor */

  set loc_contents;             /* contents lists */
  set actor_contents;

  extern set get_contents (datum);


  /* verify that actor exists and can run commands */
  if (!flag_set (actor, F_PLAYER))
    return;

  /* eat leading whitespace */
  while (*safe_command && isspace (*safe_command))
    safe_command++;

  /* look for special command */
  if (*safe_command == RUN_CODE_COMMAND) {
    parse_program (actor, safe_command + 1);
    return;
  }

  /* get actor's location */
  /* if actor isn't anywhere, lose */
  if ((location = safe_get (actor, location)) == NOTHING)
    goto parse_failed;

  /* check for illegal character */
  /* this excludes calling _before, _after, _tick, etc. */
  if (!isalnum (*safe_command)) {
    goto parse_failed;
  }

  /* everything ok, do the parse */
  strip_whitespace (safe_command, cbuf);
  command = cbuf;

  /* check for room command or actor command */
  if ((verb = intern_soft (command)) != NOTHING) {
    if (try_action (actor, verb, location, 0, 0))
      return;
    if (try_action (actor, verb, actor, 0, 0))
      return;
  }

  /* get location and actor  contents lists */
  loc_contents = get_contents (location);
  actor_contents = get_contents (actor);

  /* rebuild the name lists -- this forces aliases to be interned */
  set_build_name_list (loc_contents);
  set_build_name_list (actor_contents);

  /* check for object invocation */
  /* reintern verb in case it's there now but wasn't before list builds */
  if ((verb = intern_soft (command)) != NOTHING) {
    SET_FOREACH_MATCH (loc_contents, verb, obj) {
      if (try_action (actor, INVOKE_ACTION, obj, command, 0))
        return;
    }
    END_SET_FOREACH;
    SET_FOREACH_MATCH (actor_contents, verb, obj) {
      if (try_action (actor, INVOKE_ACTION, obj, command, 0))
        return;
    }
    END_SET_FOREACH;
  }

  /* must be V O or V O P O */
  /* try V O first */

  /* get the verb word */
  for (args = command; *args && !isspace (*args); args++);
  if (*args != '\0')
    *args++ = '\0';

  /* check for no objects */
  if (!*args)
    goto parse_failed;

  /* try V O */
  if (try_match (actor, location, actor_contents, loc_contents,
      command, args, 0))
    return;

  /* try V O P O */
  /* don't try to read this cruft! */

  /* copy V into vbuf */
  strcpy (vbuf, command);
  vp = vbuf + strlen (vbuf);

  /* walk obj1end across args */
  obj1end = args;
  for (;;) {
    /* add a word to obj1 */
    while (*obj1end && !isspace (*obj1end))
      obj1end++;
    if (*obj1end == '\0')
      break;                    /* no room for P O2 */

    /* find start of prep */
    for (pstart = obj1end; *pstart && isspace (*pstart); pstart++);
    if (*pstart == '\0')
      break;                    /* no prep */

    /* find end of prep */
    for (pend = pstart; *pend && !isspace (*pend); pend++);
    if (*pend == '\0')
      break;                    /* no room for O2 */

    /* find start of obj2 */
    for (obj2start = pend; *obj2start && isspace (*obj2start); obj2start++);
    if (*obj2start == '\0')
      break;                    /* no obj2 */

    /* else all the pointers work */
    /* mark obj1end */
    c = *obj1end;
    *obj1end = '\0';

    /* grab the verb */
    strncpy (vp + 1, pstart, pend - pstart);
    vp[1 + pend - pstart] = '\0';

    /* try verb>prep */
    vp[0] = RIGHT_ACTION;
    if (try_match (actor, location, actor_contents, loc_contents,
        vbuf, obj2start, args))
      return;

    /* try verb<prep */
    vp[0] = LEFT_ACTION;
    if (try_match (actor, location, actor_contents, loc_contents,
        vbuf, args, obj2start))
      return;

    /* try verb^prep --- double-text action */
    vp[0] = DOUBLE_ACTION;
    if ((verb = intern_soft (vbuf)) != NOTHING) {
      if (try_action (actor, verb, location, args, obj2start))
        return;
      if (try_action (actor, verb, actor, args, obj2start))
        return;
    }

    /* didn't work this time, undo '\0' at obj1end and
       move obj1end to start of next word */
    *obj1end = c;
    obj1end = pstart;
  }

  /* try V text */
  if ((verb = intern_soft (command)) != NOTHING) {
    if (try_action (actor, verb, location, 0, args))
      return;
    if (try_action (actor, verb, actor, 0, args))
      return;
  }

parse_failed:
  /* can't do it */
  /* try defaults */
  if (try_action (actor, DEFAULT_ACTION, location, 0, safe_command))
    return;
  if (try_action (actor, DEFAULT_ACTION, actor, 0, safe_command))
    return;

  /* we lose completely */
  notify (actor, DEFAULT_HUH_MESSAGE);
  return;
}