/* $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 "os.h"

#include "db.h"
#include "config.h"
#include "match.h"
#include "externs.h"
#include "interface.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 (OS_RAND () % 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;
  }
}