pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file match.c
 *
 * \brief Matching of object names.
 *
 * These are the PennMUSH name-matching routines, fully re-entrant.
 *  match_result(who,name,type,flags) - return match, AMBIGUOUS, or NOTHING
 *  noisy_match_result(who,name,type,flags) - return match or NOTHING,
 *      and notify player on failures
 *  last_match_result(who,name,type,flags) - return match or NOTHING,
 *      and return the last match found in ambiguous situations
 *
 * who = dbref of player to match for
 * name = string to match on
 * type = preferred type of match (TYPE_THING, etc.) or NOTYPE
 * flags = a set of bits indicating what kind of matching to do
 *
 * flags are defined in match.h, but here they are for reference:
 * MAT_CHECK_KEYS       - check locks when matching
 * MAT_GLOBAL           - match in master room
 * MAT_REMOTES          - match things not nearby
 * MAT_NEAR             - match things nearby
 * MAT_CONTROL          - do a control check after matching
 * MAT_ME               - match "me"
 * MAT_HERE             - match "here"
 * MAT_ABSOLUTE         - match "#dbref"
 * MAT_PLAYER           - match a player's name
 * MAT_NEIGHBOR         - match something in the same room
 * MAT_POSSESSION       - match something I'm carrying
 * MAT_EXIT             - match an exit
 * MAT_CARRIED_EXIT     - match a carried exit (rare)
 * MAT_CONTAINER        - match a container I'm in
 * MAT_REMOTE_CONTENTS  - match the contents of a remote location
 * MAT_ENGLISH          - match natural english 'my 2nd flower'
 * MAT_EVERYTHING       - me,here,absolute,player,neighbor,possession,exit
 * MAT_NEARBY           - everything near
 * MAT_OBJECTS          - me,absolute,player,neigbor,possession
 * MAT_NEAR_THINGS      - objects near
 * MAT_REMOTE           - absolute,player,remote_contents,exit,remotes
 * MAT_LIMITED          - absolute,player,neighbor
 */

#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "conf.h"
#include "mushdb.h"
#include "externs.h"
#include "case.h"
#include "match.h"
#include "parse.h"
#include "flags.h"
#include "dbdefs.h"
#include "confmagic.h"

static dbref match_result_internal
  (dbref who, const char *name, int type, long flags);
static dbref simple_matches(dbref who, const char *name, long flags);
static int parse_english(const char **name, long *flags);
static dbref match_me(const dbref who, const char *name);
static dbref match_here(const dbref who, const char *name);
/** Convenience alias for parse_objid */
#define match_absolute(name) parse_objid(name)
static dbref match_player(const dbref matcher, const char *match_name);
static dbref match_pmatch(const dbref matcher, const char *match_name);
static dbref choose_thing(const dbref match_who, const int preferred_type,
			  long int flags, dbref thing1, dbref thing2);
extern int check_alias(const char *command, const char *list);	/* game.c */


/** A wrapper for returning a match, AMBIGUOUS, or NOTHING.
 * This function attempts to match a name for who, and 
 * can return the matched dbref, AMBIGUOUS, or NOTHING.
 * \param who the looker.
 * \param name name to try to match.
 * \param type type of object to match.
 * \param flags match flags.
 * \return dbref of matched object, or AMBIGUOUS, or NOTHING.
 */
dbref
match_result(const dbref who, const char *name, const int type,
	     const long flags)
{
  return match_result_internal(who, name, type, flags);
}

/** A noisy wrapper for returning a match or NOTHING.
 * This function attempts to match a name for who, and 
 * can return the matched dbref or NOTHING (in ambiguous cases,
 * NOTHING is returned). If no match is made, the looker is notified
 * of the failure to match or ambiguity.
 * \param who the looker.
 * \param name name to try to match.
 * \param type type of object to match.
 * \param flags match flags.
 * \return dbref of matched object, or  NOTHING.
 */
dbref
noisy_match_result(const dbref who, const char *name, const int type,
		   const long flags)
{
  return match_result_internal(who, name, type, flags | MAT_NOISY);
}

/** A noisy wrapper for returning a match or NOTHING.
 * This function attempts to match a name for who, and 
 * can return the matched dbref or NOTHING. In ambiguous cases,
 * the last matched thing is returned.
 * \param who the looker.
 * \param name name to try to match.
 * \param type type of object to match.
 * \param flags match flags.
 * \return dbref of matched object, or  NOTHING.
 */
dbref
last_match_result(const dbref who, const char *name, const int type,
		  const long flags)
{
  return match_result_internal(who, name, type, flags | MAT_LAST);
}

/** Wrapper for a noisy match with control checks.
 * This function performs a noisy_match_result() and then checks that
 * the looker controls the matched object before returning it.
 * If the control check fails, the looker is notified and NOTHING
 * is returned.
 * \param player the looker.
 * \param name name to try to match.
 * \return dbref of matched controlled object, or NOTHING.
 */
dbref
match_controlled(dbref player, const char *name)
{
  dbref match;
  match = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
  if (GoodObject(match) && !controls(player, match)) {
    notify(player, T("Permission denied."));
    return NOTHING;
  } else {
    return match;
  }
}

/* The real work. Here's the spec:
 * str  --> "me"
 *      --> "here"
 *      --> "#dbref"
 *      --> "*player"
 *      --> adj-phrase name
 *      --> name
 * adj-phrase --> adj
 *            --> adj count
 *            --> count
 * adj  --> "my", "me" (restrict match to inventory)
 *      --> "here", "this", "this here" (restrict match to neighbor objects)
 *      --> "toward" (restrict match to exits)
 * count --> 1st, 21st, etc.
 *       --> 2nd, 22nd, etc.
 *       --> 3rd, 23rd, etc.
 *       --> 4th, 10th, etc.
 * name --> exit_alias
 *      --> full_obj_name
 *      --> partial_obj_name
 *
 * 1. Look for exact matches and return immediately:
 *  a. "me" if requested
 *  b. "here" if requested
 *  c. #dbref, possibly with a control check
 *  c. *player
 * 2. Parse for adj-phrases and restrict further matching and/or
 *    remember the object count
 * 3. Look for matches (remote contents, neighbor, inventory, exits, 
 *    containers, carried exits)
 *  a. If we don't have an object count, collect the number of exact
 *     and partial matches and the best partial match.
 *  b. If we do have an object count, collect the nth exact match
 *     and the nth match (exact or partial). number of matches is always
 *     0 or 1.
 * 4. Make decisions
 *  a. If we got a single exact match, return it
 *  b. If we got multiple exact matches, complain
 *  c. If we got no exact matches, but a single partial match, return it
 *  d. If we got multiple partial matches, complain
 *  e. If we got no matches, complain
 */

#define MATCH_NONE      0x0	/**< No matches were found */
#define MATCH_EXACT     0x1	/**< At least one exact match found */
#define MATCH_PARTIAL   0x2	/**< At least one partial match found, no exact */
/** Prototype for matching functions */
#define MATCH_FUNC_PROTO(fun_name) \
  /* ARGSUSED */ /* try to keep lint happy */ \
  static int fun_name(const dbref who, const char *name, const int type, \
      const long flags, dbref first, \
      dbref *match, int *exact_matches_to_go, int *matches_to_go)
/** Common declaration for matching functions */
#define MATCH_FUNC(fun_name) \
  static int fun_name(const dbref who, const char *name, const int type, \
      const long flags, dbref first __attribute__ ((__unused__)), \
      dbref *match, int *exact_matches_to_go, int *matches_to_go)
/** Macro to execute matching and store some results */
#define RUN_MATCH_FUNC(fun,first) \
   { \
    result = fun(who, name, type, flags, first, &match, \
          &exact_matches_to_go, &matches_to_go); \
    if (result == MATCH_EXACT) { \
      exact_match = match; \
      /* If it's the nth exact match, we're done */ \
      if (matchnum && !exact_matches_to_go) \
        goto finished; \
      /* If it's the n'th match, remember it */ \
      if (matchnum && !matches_to_go) \
        last_match = match; \
    } else if (result == MATCH_PARTIAL) { \
      if (!matchnum || !matches_to_go) \
        last_match = match; \
    } \
   }

MATCH_FUNC_PROTO(match_possession);
MATCH_FUNC_PROTO(match_neighbor);
MATCH_FUNC_PROTO(match_exit);
MATCH_FUNC_PROTO(match_exit_internal);
MATCH_FUNC_PROTO(match_container);
MATCH_FUNC_PROTO(match_list);

static dbref
match_result_internal(dbref who, const char *name, int type, long flags)
{
  dbref match = NOTHING, last_match = NOTHING, exact_match = NOTHING;
  int exact_matches_to_go, matches_to_go;
  int matchnum = 0;
  int result;

  /* The quick ones that can never be ambiguous */
  match = simple_matches(who, name, flags);
  if (GoodObject(match))
    return match;

  /* Check for adjective phrases */
  matchnum = parse_english(&name, &flags);

  /* Perform matching. We've already had flags restricted by any
   * adjective phrases. If matchnum is set, collect the matchnum'th
   * exact match (and stop) and the matchnum'th match (exact or partial, 
   * and store this in case we don't get enough exact matches).
   * If not, collect the number of exact and partial matches and the 
   * last exact and partial matches.
   */
  exact_matches_to_go = matches_to_go = matchnum;
  if (flags & MAT_POSSESSION)
    RUN_MATCH_FUNC(match_possession, NOTHING);
  if (flags & MAT_NEIGHBOR)
    RUN_MATCH_FUNC(match_neighbor, NOTHING);
  if (flags & MAT_REMOTE_CONTENTS)
    RUN_MATCH_FUNC(match_possession, NOTHING);
  if (flags & MAT_EXIT)
    RUN_MATCH_FUNC(match_exit, NOTHING);
  if (flags & MAT_CONTAINER)
    RUN_MATCH_FUNC(match_container, NOTHING);
  if (flags & MAT_CARRIED_EXIT)
    RUN_MATCH_FUNC(match_exit_internal, who);

finished:
  /* Set up the default match_result behavior */
  if (matchnum) {
    /* nth exact match? */
    if (!exact_matches_to_go)
      match = exact_match;
    else if (GoodObject(last_match))
      match = last_match;	/* nth exact-or-partial match, or nothing? */
    /* This shouldn't happen, but just in case we have a valid match,
     * and an invalid last_match in the matchnum case, fall through and
     * use the match.
     */
  } else if (GoodObject(exact_match)) {
    /* How many exact matches? */
    if (exact_matches_to_go == -1)
      match = exact_match;	/* Good */
    else if (flags & MAT_LAST)
      match = exact_match;	/* Good enough */
    else
      match = AMBIGUOUS;
  } else {
    if (!matches_to_go)
      match = NOTHING;		/* No matches */
    else if (matches_to_go == -1)
      match = last_match;	/* Good */
    else if (flags & MAT_LAST)
      match = last_match;	/* Good enough */
    else
      match = AMBIGUOUS;
  }

  /* Handle noisy_match_result */
  if (flags & MAT_NOISY) {
    switch (match) {
    case NOTHING:
      notify(who, T("I can't see that here."));
      return NOTHING;
    case AMBIGUOUS:
      notify(who, T("I don't know which one you mean!"));
      return NOTHING;
    default:
      return match;
    }
  }
  return match;
}

static dbref
simple_matches(dbref who, const char *name, long flags)
{
  dbref match = NOTHING;
  if (flags & MAT_ME) {
    match = match_me(who, name);
    if (GoodObject(match))
      return match;
  }
  if (flags & MAT_HERE) {
    match = match_here(who, name);
    if (GoodObject(match))
      return match;
  }
  if (!(flags & MAT_NEAR) || Long_Fingers(who)) {
    if (flags & MAT_ABSOLUTE) {
      match = match_absolute(name);
      if (GoodObject(match)) {
	if (flags & MAT_CONTROL) {
	  /* Check for control */
	  if (controls(who, match) || nearby(who, match))
	    return match;
	} else {
	  return match;
	}
      }
    }
    if (flags & MAT_PLAYER) {
      match = match_player(who, name);
      if (GoodObject(match))
	return match;
    }
    if (flags & MAT_PMATCH) {
      match = match_pmatch(who, name);
      if (GoodObject(match))
	return match;
    }
  } else {
    /* We're doing a nearby match and the player doesn't have
     * long_fingers, so it's a controlled absolute
     */
    match = match_absolute(name);
    if (GoodObject(match) && (controls(who, match) || nearby(who, match)))
      return match;
  }
  return NOTHING;
}


/* 
 * adj-phrase --> adj
 *            --> adj count
 *            --> count
 * adj  --> "my", "me" (restrict match to inventory)
 *      --> "here", "this", "this here" (restrict match to neighbor objects)
 *      --> "toward" (restrict match to exits)
 * count --> 1st, 21st, etc.
 *       --> 2nd, 22nd, etc.
 *       --> 3rd, 23rd, etc.
 *       --> 4th, 10th, etc.
 *
 * We return the count, we position the pointer at the end of the adj-phrase
 * (or at the beginning, if we fail), and we modify the flags if there
 * are restrictions
 */
static int
parse_english(const char **name, long *flags)
{
  int saveflags = *flags;
  const char *savename = *name;
  char *mname;
  char *e;
  int count = 0;

  /* Handle restriction adjectives first */
  if (*flags & MAT_NEIGHBOR) {
    if (!strncasecmp(*name, "this here ", 10)) {
      *name += 10;
      *flags &= ~(MAT_POSSESSION | MAT_EXIT);
    } else if (!strncasecmp(*name, "here ", 5)
	       || !strncasecmp(*name, "this ", 5)) {
      *name += 5;
      *flags &=
	~(MAT_POSSESSION | MAT_EXIT | MAT_REMOTE_CONTENTS | MAT_CONTAINER);
    }
  }
  if ((*flags & MAT_POSSESSION) && (!strncasecmp(*name, "my ", 3)
				    || !strncasecmp(*name, "me ", 3))) {
    *name += 3;
    *flags &= ~(MAT_NEIGHBOR | MAT_EXIT | MAT_CONTAINER | MAT_REMOTE_CONTENTS);
  }
  if ((*flags & MAT_EXIT) && (!strncasecmp(*name, "toward ", 7))) {
    *name += 7;
    *flags &=
      ~(MAT_NEIGHBOR | MAT_POSSESSION | MAT_CONTAINER | MAT_REMOTE_CONTENTS);
  }

  while (**name == ' ')
    (*name)++;

  /* If the name was just 'toward' (with no object name), reset 
   * everything and press on.
   */
  if (!**name) {
    *name = savename;
    *flags = saveflags;
    return 0;
  }

  /* Handle count adjectives */
  if (!isdigit((unsigned char) **name)) {
    /* Quick exit */
    return 0;
  }
  mname = strchr(*name, ' ');
  if (!mname) {
    /* Quick exit - count without a noun */
    return 0;
  }
  /* Ok, let's see if we can get a count adjective */
  savename = *name;
  *mname = '\0';
  count = strtoul(*name, &e, 10);
  if (e && *e) {
    if (count < 1) {
      count = -1;
    } else if ((count > 10) && (count < 14)) {
      if (strcasecmp(e, "th"))
	count = -1;
    } else if ((count % 10) == 1) {
      if (strcasecmp(e, "st"))
	count = -1;
    } else if ((count % 10) == 2) {
      if (strcasecmp(e, "nd"))
	count = -1;
    } else if ((count % 10) == 3) {
      if (strcasecmp(e, "rd"))
	count = -1;
    } else if (strcasecmp(e, "th")) {
      count = -1;
    }
  }
  *mname = ' ';
  if (count < 0) {
    /* An error (like '0th' or '12nd') - this wasn't really a count
     * adjective. Reset and press on. */
    *name = savename;
    return 0;
  }
  /* We've got a count adjective */
  *name = mname + 1;
  while (**name == ' ')
    (*name)++;
  return count;
}


static dbref
match_me(const dbref who, const char *name)
{
  return (!strcasecmp(name, "me")) ? who : NOTHING;
}

static dbref
match_here(const dbref who, const char *name)
{
  return (!strcasecmp(name, "here") && GoodObject(Location(who))) ?
    Location(who) : NOTHING;
}


static dbref
match_player(const dbref matcher, const char *match_name)
{
  dbref match;
  const char *p;

  if (*match_name != LOOKUP_TOKEN)
    return NOTHING;
  for (p = match_name + 1; isspace((unsigned char) *p); p++) ;
  /* If lookup_player fails, try a partial match on connected
   * players, 2.0 style. That can return match, NOTHING, AMBIGUOUS
   */
  match = lookup_player(p);
  return (match != NOTHING) ? match : visible_short_page(matcher, p);
}

static dbref
match_pmatch(const dbref matcher, const char *match_name)
{
  dbref match;
  const char *p;

  for (p = match_name; isspace((unsigned char) *p); p++) ;
  /* If lookup_player fails, try a partial match on connected
   * players, 2.0 style. That can return match, NOTHING, AMBIGUOUS
   */
  match = lookup_player(p);
  return (match != NOTHING) ? match : visible_short_page(matcher, p);
}

/* Run down a contents list and try to match */
MATCH_FUNC(match_list)
{
  dbref absolute;
  dbref alias_match;
  int match_type = MATCH_NONE;
  int nth_match = (*exact_matches_to_go != 0);

  /* If we were given an absolute dbref, remember it */
  absolute = match_absolute(name);
  /* If we were given a player name, remember it */
  alias_match = lookup_player(name);

  DOLIST(first, first) {
    if (first == absolute) {
      /* Got an absolute match, return it */
      *match = first;
      (*exact_matches_to_go)--;
      (*matches_to_go)--;
      return MATCH_EXACT;
    } else if (can_interact(first, who, INTERACT_MATCH) &&
	       (!strcasecmp(Name(first), name) ||
		(GoodObject(alias_match) && (alias_match == first)))) {
      /* An exact match, but there may be others */
      (*exact_matches_to_go)--;
      (*matches_to_go)--;
      if (nth_match) {
	if (!(*exact_matches_to_go)) {
	  /* We're done */
	  *match = first;
	  return MATCH_EXACT;
	}
      } else {
	if (match_type == MATCH_EXACT)
	  *match = choose_thing(who, type, flags, *match, first);
	else
	  *match = first;
	match_type = MATCH_EXACT;
      }
    } else if ((match_type != MATCH_EXACT) && string_match(Name(first), name)
	       && can_interact(first, who, INTERACT_MATCH)) {
      /* A partial match, and we haven't done an exact match yet */
      (*matches_to_go)--;
      if (nth_match) {
	if (!(*matches_to_go))
	  *match = first;
      } else if (match_type == MATCH_PARTIAL)
	*match = choose_thing(who, type, flags, *match, first);
      else
	*match = first;
      match_type = MATCH_PARTIAL;
    }
  }
  /* If we've made the nth partial match in this round, there's none to go */
  if (nth_match && *matches_to_go < 0)
    *matches_to_go = 0;
  return match_type;
}

MATCH_FUNC(match_exit)
{
  dbref loc;
  loc = (IsRoom(who)) ? who : Location(who);
  if (flags & MAT_REMOTES) {
    if (GoodObject(loc))
      return match_exit_internal(who, name, type, flags, Zone(loc),
				 match, exact_matches_to_go, matches_to_go);
    else
      return NOTHING;
  } else if (flags & MAT_GLOBAL)
    return match_exit_internal(who, name, type, flags, MASTER_ROOM,
			       match, exact_matches_to_go, matches_to_go);
  return match_exit_internal(who, name, type, flags, loc,
			     match, exact_matches_to_go, matches_to_go);
}

MATCH_FUNC(match_exit_internal)
{
  dbref exit_tmp;
  dbref absolute;
  int match_type = MATCH_NONE;
  int nth_match = (*exact_matches_to_go != 0);

  if (!GoodObject(first) || !IsRoom(first) || !name || !*name)
    return NOTHING;
  /* Store an absolute dbref match if given */
  absolute = match_absolute(name);
  DOLIST(exit_tmp, Exits(first)) {

    if ((exit_tmp == absolute) && (can_interact(exit_tmp, who, INTERACT_MATCH))) {
      /* Absolute match. Return immediately */
      *match = exit_tmp;
      (*exact_matches_to_go)--;
      (*matches_to_go)--;
      return MATCH_EXACT;
    } else if (check_alias(name, Name(exit_tmp))
	       && (can_interact(exit_tmp, who, INTERACT_MATCH))) {
      /* Matched an exit alias, but there may be more */
      (*exact_matches_to_go)--;
      (*matches_to_go)--;
      if (nth_match) {
	if (!(*exact_matches_to_go)) {
	  /* We're done */
	  *match = exit_tmp;
	  return MATCH_EXACT;
	}
      } else {
	if (match_type == MATCH_EXACT)
	  *match = choose_thing(who, type, flags, *match, exit_tmp);
	else
	  *match = exit_tmp;
	match_type = MATCH_EXACT;
      }
    }
  }
  /* If we've made the nth partial match in this round, there's none to go */
  if (nth_match && *matches_to_go < 0)
    *matches_to_go = 0;
  return match_type;
}


MATCH_FUNC(match_possession)
{
  if (!GoodObject(who))
    return NOTHING;
  return match_list(who, name, type, flags, Contents(who), match,
		    exact_matches_to_go, matches_to_go);
}

MATCH_FUNC(match_container)
{
  if (!GoodObject(who))
    return NOTHING;
  return match_list(who, name, type, flags, Location(who), match,
		    exact_matches_to_go, matches_to_go);
}

MATCH_FUNC(match_neighbor)
{
  dbref loc;
  if (!GoodObject(who))
    return NOTHING;
  loc = Location(who);
  if (!GoodObject(loc))
    return NOTHING;
  return match_list(who, name, type, flags, Contents(loc), match,
		    exact_matches_to_go, matches_to_go);
}


static dbref
choose_thing(const dbref match_who, const int preferred_type, long flags,
	     dbref thing1, dbref thing2)
{
  int has1;
  int has2;
  /* If there's only one valid thing, return it */
  /* (Apologies to Theodor Geisel) */
  if (thing1 == NOTHING)
    return thing2;
  else if (thing2 == NOTHING)
    return thing1;

  /* If a type is given, and only one thing is of that type, return it */
  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 we've asked for a basic lock check, and only one passes, use that */
  if (flags & MAT_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;
  }

  /* No luck. Return the higher dbref */
  return (thing1 > thing2 ? thing1 : thing2);
}