/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/match.c,v 1.8 90/09/18 08:00:31 rearl Exp $ */

/*
 * $Log:	match.c,v $
 * Revision 1.8  90/09/18  08:00:31  rearl
 * Changed upper/lowercase lookup.
 * 
 * Revision 1.7  90/09/16  04:42:29  rearl
 * Preparation code added for disk-based MUCK.
 * 
 * Revision 1.6  90/09/05  02:33:24  rearl
 * Fixed room exit matching so everything in the room is checked before
 * checking parent rooms.
 * 
 * Revision 1.5  90/08/27  03:31:25  rearl
 * Added environment support.
 * 
 * Revision 1.4  90/08/11  04:05:55  rearl
 * *** empty log message ***
 * 
 * Revision 1.3  90/08/05  03:19:41  rearl
 * Redid matching routines.
 * 
 * Revision 1.2  90/07/29  17:40:53  rearl
 * match_rmatch (for MUF programs) checks exits/actions on
 * rooms/players/objects.
 * 
 * Revision 1.1  90/07/19  23:03:54  casie
 * Initial revision
 * 
 *
 */

#include "copyright.h"
#include "config.h"

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

#include "db.h"
#include "params.h"
#include "match.h"

#define DOWNCASE(x) (lowercase[x])

char match_args[BUFSIZ]; /* remaining text */

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

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

static dbref choose_thing(dbref thing1, dbref thing2, struct match_data *md)
{
  int has1;
  int has2;
  int preferred = md -> preferred_type;
  
  if(thing1 == NOTHING) {
    return thing2;
  } else if(thing2 == NOTHING) {
    return thing1;
  }
  
  if(preferred != NOTYPE) {
    if(Typeof(thing1) == preferred) {
      if(Typeof(thing2) != preferred) {
	return thing1;
      }
    } else if(Typeof(thing2) == preferred) {
      return thing2;
    }
  }
  
  if(md -> check_keys) {
    has1 = could_doit(md -> match_who, thing1);
    has2 = could_doit(md -> 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(struct match_data *md)
{
  dbref match;
  const char *p;
  
  if(*(md -> match_name) == LOOKUP_TOKEN
     && payfor(md -> match_who, LOOKUP_COST)) {
    for(p = (md -> match_name) + 1; isspace(*p); p++);
    if((match = lookup_player(p)) != NOTHING) {
      md -> exact_match = match;
    }
  }
}

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

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

void match_absolute(struct match_data *md)
{
  dbref match;
  
  if((match = absolute_name(md)) != NOTHING) {
    md -> exact_match = match;
  }
}

void match_me(struct match_data *md)
{
  if(!string_compare(md -> match_name, "me")) {
    md -> exact_match = md -> match_who;
  }
}

void match_here(struct match_data *md)
{
  if(!string_compare(md -> match_name, "here")
     && DBFETCH(md -> match_who)->location != NOTHING) {
    md -> exact_match = DBFETCH(md -> match_who)->location;
  }
}

void match_home(struct match_data *md) {
  if (!string_compare(md -> match_name, "home"))
    md -> exact_match = HOME;
}


static void match_list(dbref first, struct match_data *md)
{
  dbref absolute;
  
  absolute = absolute_name(md);
  if(!controls(md -> match_who, absolute)) absolute = NOTHING;
  
  DOLIST(first, first) {
    if(first == absolute) {
      md -> exact_match = first;
      return;
    } else if(!string_compare(NAME(first), md -> match_name)) {
      /* if there are multiple exact matches, randomly choose one */
      md -> exact_match = choose_thing(md -> exact_match, first, md);
    } else if(string_match(NAME(first), md -> match_name)) {
      if(md->last_match != first) { /* 4 people inside themselves. */
	md -> last_match = first;
	(md -> match_count)++;
      }
    }
  }
}

void match_possession(struct match_data *md)
{
  match_list(DBFETCH(md -> match_who)->contents, md);
}

void match_neighbor(struct match_data *md)
{
  dbref loc;
  
  if((loc = DBFETCH(md -> match_who)->location) != NOTHING) {
    match_list(DBFETCH(loc)->contents, md);
  }
}

/*
 * 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, struct match_data *md)
{
  dbref exit, absolute;
  const char *exitname, *p;
  int i,exitprog;
  
  if (first == NOTHING) return;		/* Easy fail match */
  if ((DBFETCH(md -> match_who)->location) == NOTHING) return;
  
  absolute = absolute_name(md);		/* parse #nnn entries */
  if (!controls(md -> match_who, absolute)) absolute = NOTHING;

  DOLIST(exit, first) {
    if((md->exact_match==NOTHING)) {
      if (exit == absolute) {
	md -> exact_match = exit;
	continue;
      }
      exitprog = 0;
      if (DBFETCH(exit)->sp.exit.dest)
	for (i=0;i<DBFETCH(exit)->sp.exit.ndest;i++)
	  if (Typeof((DBFETCH(exit)->sp.exit.dest)[i]) == TYPE_PROGRAM)
	    exitprog = 1;
      exitname = NAME(exit);
      while (*exitname) {			/* for all exit aliases */
	for (p = md -> match_name;		/* check out 1 alias */
	     *p
	     && DOWNCASE(*p) == DOWNCASE(*exitname)
	     && *exitname != EXIT_DELIMITER;
	     p++, exitname++);
	/* did we get a match on this alias? */
	if (*p == '\0' || (*p == ' ' && exitprog)) {
	  /* make sure there's nothing afterwards */
	  while (isspace(*exitname)) exitname++;
	  if (*exitname == '\0' || *exitname == EXIT_DELIMITER) {
	    /* we got a match on this alias */
	    if (strlen(md -> match_name)-strlen(p) > md -> longest_match) {
	      md -> exact_match = exit;
	      md -> longest_match = strlen(md -> match_name)-strlen(p);
	      if (*p == ' ')
		strcpy(match_args, p+1);
	      else
		*match_args = '\0';
	    }
	    else if (strlen(md -> match_name)-strlen(p) == md -> longest_match) {
	      md -> exact_match =
		choose_thing(md -> exact_match, exit, md);
	      if (md -> exact_match == exit)
		if (*p == ' ')
		  strcpy(match_args, p+1);
		else
		  *match_args = '\0';
	    }
	    goto next_exit;
	  }
	}
	/* we didn't get it, go on to next alias */
	while (*exitname && *exitname++ != EXIT_DELIMITER);
	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(dbref loc, struct match_data *md)
{
  if (Typeof(loc) != TYPE_ROOM) return;
  if (DBFETCH(loc)->exits != NOTHING)
    match_exits(DBFETCH(loc)->exits, md);
}

/*
 * match_invobj_actions
 * matches actions attached to objects in inventory
 */
void match_invobj_actions(struct match_data *md)
{
  dbref thing;
  
  if (DBFETCH(md -> match_who)->contents == NOTHING)
    return;
  DOLIST(thing, DBFETCH(md -> match_who)->contents) {
    if (Typeof(thing) == TYPE_THING
	&& DBFETCH(thing)->exits != NOTHING) {
      match_exits(DBFETCH(thing)->exits, md);
    }
  }
}

/*
 * match_roomobj_actions
 * matches actions attached to objects in the room
 */
void match_roomobj_actions(struct match_data *md)
{
  dbref thing, loc;
  
  if ((loc = DBFETCH(md -> match_who)->location) == NOTHING) return;
  if (DBFETCH(loc)->contents == NOTHING) return;
  DOLIST(thing, DBFETCH(loc)->contents) {
    if (Typeof(thing) == TYPE_THING
	&& DBFETCH(thing)->exits != NOTHING) {
      match_exits(DBFETCH(thing)->exits, md);
    }
  }
}

/*
 * match_player_actions
 * matches actions attached to player
 */
void match_player_actions(struct match_data *md)
{
  if (DBFETCH(md -> match_who)->exits == NOTHING) return;
  match_exits(DBFETCH(md -> match_who)->exits, md);
}

/*
 * 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(struct match_data *md)
{
  dbref loc;
  int x=0;
  strcpy(match_args, "\0");
  if ((loc = DBFETCH(md -> match_who)->location) != NOTHING)
    match_room_exits(loc, md);
  if (md -> exact_match == NOTHING)
    match_invobj_actions(md);
  if (md -> exact_match == NOTHING)
    match_roomobj_actions(md);
  if (md -> exact_match == NOTHING)
    match_player_actions(md);
  if(Typeof(loc)!=TYPE_ROOM)
    loc=DBFETCH(GLOBAL_ENVIRONMENT)->contents; /* Eewww.. */
  while ((loc = DBFETCH(loc)->location) != NOTHING) {
    if(x++>30)
      loc=GLOBAL_ENVIRONMENT; /* a loop.. get out of it. */
    match_room_exits(loc, md);
  }
}

void match_everything(struct match_data *md)
{
  match_all_exits(md);
  match_neighbor(md);
  match_possession(md);
  match_me(md);
  match_here(md);
  /*  if(Wizard(md -> match_who)||(Cont(md->match_who))) {*/
  match_absolute(md);
  match_player(md);
  /*  }*/
}

dbref match_result(struct match_data *md)
{
  if(md -> exact_match != NOTHING) {
    return (md -> exact_match);
  } else {
    switch(md -> match_count) {
    case 0:
      return NOTHING;
    case 1:
      return (md -> last_match);
    default:
      return AMBIGUOUS;
    }
  }
}

/* use this if you don't care about ambiguity */
dbref last_match_result(struct match_data *md)
{
  if(md -> exact_match != NOTHING) {
    return (md -> exact_match);
  } else {
    return (md -> last_match);
  }
}

dbref noisy_match_result(struct match_data *md)
{
  dbref match;
  
  switch(match = match_result(md)) {
  case NOTHING:
    notify(md -> match_who, NOMATCH_MESSAGE);
    return NOTHING;
  case AMBIGUOUS:
    notify(md -> match_who, AMBIGUOUS_MESSAGE);
    return NOTHING;
  default:
    return match;
  }
}

void match_rmatch(dbref arg1, struct match_data *md)
{
  if (arg1 == NOTHING) return;
  switch (Typeof(arg1)) {
  case TYPE_PLAYER:
    match_list(DBFETCH(arg1)->contents, md);
    match_exits(DBFETCH(arg1)->exits, md);
    break;
  case TYPE_ROOM:
    match_list(DBFETCH(arg1)->contents, md);
    match_exits(DBFETCH(arg1)->exits, md);
    break;
  case TYPE_THING:
    match_exits(DBFETCH(arg1)->exits, md);
    match_list(DBFETCH(arg1)->contents,md);
    break;
  }
}