/* $Header: match.c,v 1.1 90/04/14 14:56:46 lachesis Exp $
 * $Log:	match.c,v $
 * Revision 1.1  90/04/14  14:56:46  lachesis
 * Initial revision
 * 
 */
#include "copyright.h"

/* Routines for parsing arguments */
#include <ctype.h>

#include "db.h"
#include "config.h"
#include "match.h"

#define DOWNCASE(x) (isupper(x) ? tolower(x) : (x))

static dbref exact_match = NOTHING;	/* holds result of exact match */
static int check_keys = 0;	/* if non-zero, check for keys */
static dbref last_match = NOTHING;	/* holds result of last match */
static int match_count;		/* holds total number of inexact matches */
static dbref match_who;	/* player who is being matched around */
static const char *match_name;	/* name to match */
static int preferred_type = NOTYPE; /* preferred type */

void init_match(dbref player, const char *name, int type)
{
    exact_match = last_match = NOTHING;
    match_count = 0;
    match_who = player;
    match_name = name;
    check_keys = 0;
    preferred_type = type;
}

void init_match_check_keys(dbref player, const char *name, int type)
{
    init_match(player, name, type);
    check_keys = 1;
}

static dbref choose_thing(dbref thing1, dbref thing2)
{
    int has1;
    int has2;

    if(thing1 == NOTHING) {
	return thing2;
    } else if(thing2 == NOTHING) {
	return thing1;
    }

    if(preferred_type != NOTYPE) {
	if(Typeof(thing1) == preferred_type) {
	    if(Typeof(thing2) != preferred_type) {
		return thing1;
	    }
	} else if(Typeof(thing2) == preferred_type) {
	    return thing2;
	}
    }

    if(check_keys) {
	has1 = could_doit(match_who, thing1);
	has2 = could_doit(match_who, thing2);

	if(has1 && !has2) {
	    return thing1;
	} else if (has2 && !has1) {
	    return thing2;
	}
	/* else fall through */
    }

    return (random() % 2 ? thing1 : thing2);
}

void match_player(void)
{
    dbref match;
    const char *p;

    if(*match_name == LOOKUP_TOKEN
       && payfor(match_who, LOOKUP_COST)) {
	for(p = match_name + 1; isspace(*p); p++)
	  /*EMPTY*/
	  ;
	if((match = lookup_player(p)) != NOTHING) {
	    exact_match = match;
	}
    }
}

/* returns nnn if name = #nnn, else NOTHING */
static dbref absolute_name(void)
{
    dbref match;

    if(*match_name == NUMBER_TOKEN) {
	match = parse_dbref(match_name+1);
	if(match < 0 || match >= db_top) {
	    return NOTHING;
	} else {
	    return match;
	}
    } else {
	return NOTHING;
    }
}

void match_absolute(void)
{
    dbref match;

    if((match = absolute_name()) != NOTHING) {
	exact_match = match;
    }
}

void match_me(void)
{
    if(!string_compare(match_name, "me")) {
	exact_match = match_who;
    }
}

void match_home(void)
{
  if (!string_compare(match_name, "home")) {
    exact_match = HOME;
  }
}

void match_here(void)
{
    if(!string_compare(match_name, "here")
       && db[match_who].location != NOTHING) {
	exact_match = db[match_who].location;
    }
}

static void match_list(dbref first)
{
    dbref absolute;

    absolute = absolute_name();
    if(!controls(match_who, absolute)) absolute = NOTHING;

    DOLIST(first, first) {
	if(first == absolute) {
	    exact_match = first;
	    return;
	} else if(!string_compare(db[first].name, match_name)) {
	    /* if there are multiple exact matches, randomly choose one */
	    exact_match = choose_thing(exact_match, first);
	} else if (string_match(db[first].name, match_name) != NULL) {
	    last_match = first;
	    match_count++;
	}
    }
}
    
void match_possession(void)
{
    match_list(db[match_who].contents);
}

void match_neighbor(void)
{
    dbref loc;

    if((loc = db[match_who].location) != NOTHING) {
	match_list(db[loc].contents);
    }
}

/*
 * match_exits matches a list of exits, starting with 'first'.
 * It is will match exits of players, rooms, or things.
 */
void match_exits(dbref first)
{
    dbref exit, absolute;
    const char *exitname, *p;

    if (first == NOTHING) return;		/* Easy fail match */
    if ((db[match_who].location) == NOTHING) return;

    absolute = absolute_name();			/* parse #nnn entries */
    if (!controls(match_who, absolute)) absolute = NOTHING;

    DOLIST(exit, first) {
	if (exit == absolute) {
	    exact_match = exit;
	    continue;
	}
	exitname = db[exit].name;
	while (*exitname) {			/* for all exit aliases */
	    for (p = match_name;		/* check out 1 alias */
		 *p
		 && DOWNCASE(*p) == DOWNCASE(*exitname)
		 && *exitname != EXIT_DELIMITER;
		 p++, exitname++)
	      /*EMPTY*/
	      ;
	    /* did we get a match on this alias? */
	    if (*p == '\0') {
		/* make sure there's nothing afterwards */
		while (isspace(*exitname)) exitname++;
		if(*exitname == '\0' || *exitname == EXIT_DELIMITER) {
		    /* we got a match on this alias */
		    exact_match = choose_thing(exact_match, exit);
		    goto next_exit;
		}
	    }
	    /* we didn't get it, go on to next alias */
	    while (*exitname && *exitname++ != EXIT_DELIMITER)
	      /*EMPTY*/
	      ;
	    while (isspace(*exitname)) exitname++;
	} /* end of while alias string matches */
  next_exit:
    ;
    }
}

/*
 * match_room_exits
 * Matches exits and actions attached to player's current room.
 * Formerly 'match_exit'.
 */
void match_room_exits(void)
{
    dbref  loc;

    if((loc = db[match_who].location) == NOTHING) return;
    if(Typeof(loc) != TYPE_ROOM) return;
    if(db[loc].sp.room.exits != NOTHING) {
	match_exits(db[loc].sp.room.exits);
    }
}

/*
 * match_invobj_actions
 * matches actions attached to objects in inventory
 */
void match_invobj_actions(void)
{
    dbref thing;

    if (db[match_who].contents == NOTHING)
	return;
    DOLIST(thing, db[match_who].contents) {
	if (Typeof(thing) == TYPE_THING
	    && db[thing].sp.thing.actions != NOTHING) {
	    match_exits(db[thing].sp.thing.actions);
	}
    }
}

/*
 * match_roomobj_actions
 * matches actions attached to objects in the room
 */
void match_roomobj_actions(void)
{
    dbref thing, loc;

    if ((loc = db[match_who].location) == NOTHING) return;
    if (db[loc].contents == NOTHING) return;
    DOLIST(thing, db[loc].contents) {
	if (Typeof(thing) == TYPE_THING
	    && db[thing].sp.thing.actions != NOTHING) {
	    match_exits(db[thing].sp.thing.actions);
	}
    }
}

/*
 * match_player_actions
 * matches actions attached to player
 */
void match_player_actions(void)
{
    if (db[match_who].sp.player.actions == NOTHING) return;
    match_exits(db[match_who].sp.player.actions);
}

/*
 * match_all_exits
 * Matches actions on player, objects in room, objects in inventory,
 * and room actions/exits (in reverse order of priority order).
 */
void match_all_exits(void)
{
    match_roomobj_actions();
    match_invobj_actions();
    match_player_actions();
    match_room_exits();
}

void match_everything(void)
{
    match_all_exits();
    match_neighbor();
    match_possession();
    match_me();
    match_here();
    if(Wizard(match_who)) {
	match_absolute();
	match_player();
    }
}

dbref match_result(void)
{
    if(exact_match != NOTHING) {
	return exact_match;
    } else {
	switch(match_count) {
	  case 0:
	    return NOTHING;
	  case 1:
	    return last_match;
	  default:
	    return AMBIGUOUS;
	}
    }
}
	   
/* use this if you don't care about ambiguity */
dbref last_match_result(void)
{
    if(exact_match != NOTHING) {
	return exact_match;
    } else {
	return last_match;
    }
}

dbref noisy_match_result(void)
{
    dbref match;

    switch(match = match_result()) {
      case NOTHING:
	notify(match_who, NOMATCH_MESSAGE);
	return NOTHING;
      case AMBIGUOUS:
	notify(match_who, AMBIGUOUS_MESSAGE);
	return NOTHING;
      default:
	return match;
    }
}