/* Copyright 1989, 1990 by James Aspnes, David Applegate, and Bennet Yee */
/* See the file COPYING for distribution information */
/* Command interpreter */
#include <ctype.h>

#include "config.h"
#include "db.h"
#include "bytecode.h"
#include "globals.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;
}