/* match.c */
/* Routines for parsing arguments */
#include "os.h"
#include "copyright.h"
#include "config.h"
#include "db.h"
#include "externs.h"
#include "globals.h"
#include "match.h"

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 */

#ifdef DO_GLOBALS
static int global = 0;          /* when 0 -- normal match */
                                /*      1 -- global match */
                                /*      2 -- remote match */
#endif

char const *nomatch_message = "I don't see that here.";
char const *ambiguous_message = "I don't know which one you mean!";

/*
 * check a list for objects that are prefix's for string, && are controlled
 * || link_ok
 */
dbref pref_match (dbref player, dbref list, const char *string)
{
  dbref lmatch = NOTHING;
  unsigned int mlen = 0;

  while (list != NOTHING) {
    if (string_prefix (string, db[list].name) &&
      ((Typeof (list) == TYPE_THING) && (db[list].flags & THING_PUPPET))
      && controls (player, list)) {
      if (strlen (db[list].name) > mlen) {
        lmatch = list;
        mlen = strlen (db[list].name);
      }
    }
    list = db[list].next;
  }
  return (lmatch);
}

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;
#ifdef DO_GLOBALS
  global = NORMAL_MATCH;
#endif
}

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

#ifdef DO_GLOBALS
void init_match_global (dbref player, const char *name, int type)
{
  init_match (player, name, type);
  global = GLOBAL_MATCH;
}

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

void init_match_remote (dbref location, const char *name, int type)
{
  exact_match = last_match = NOTHING;
  match_count = 0;
  match_who = location;
  match_name = name;
  check_keys = 0;
  preferred_type = type;
  global = REMOTE_MATCH;
}
#endif

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) {
    for (p = match_name + 1; isspace ((int)*p); p++);
    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_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) {
    Access (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)) {
      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);
  }
}

#ifdef DO_GLOBALS
void match_remote_contents (void)
{
  if (match_who != NOTHING)
    match_list (db[match_who].contents);
}
#endif

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

void match_exit (void)
{
  dbref loc;
  dbref exit;
  dbref absolute;
  const char *match;
  const char *p;
#ifdef DO_GLOBALS
  switch (global) {
  case NORMAL_MATCH:
    loc = db[match_who].location;
    break;
  case GLOBAL_MATCH:
    loc = MASTER_ROOM;
    break;
  case REMOTE_MATCH:
    loc = match_who;
    break;
  default:
    loc = db[match_who].location;
  }
#else
  loc = db[match_who].location;
#endif
  if (loc != NOTHING) {
    if (Typeof (loc) != TYPE_ROOM)
      return;
    absolute = absolute_name ();
    if (!controls (match_who, absolute))
      absolute = NOTHING;

    DOLIST (exit, db[loc].exits) {
      Access (exit);
      if (exit == absolute) {
        exact_match = exit;
      } else {
        match = db[exit].name;
        while (*match) {
          /* check out this one */
          for (p = match_name; (*p && DOWNCASE (*p) == DOWNCASE (*match)
              && *match != EXIT_DELIMITER); p++, match++);
          /* did we get it? */
          if (*p == '\0') {
            /* make sure there's nothing afterwards */
            while (isspace ((int)*match))
              match++;
            if (*match == '\0' || *match == EXIT_DELIMITER) {
              /* we got it */
              exact_match = choose_thing (exact_match, exit);
              goto next_exit;   /* got this match */
            }
          }
          /* we didn't get it, find next match */
          while (*match && *match++ != EXIT_DELIMITER);
          while (isspace ((int)*match))
            match++;
        }
      }
    next_exit:
      ;
    }
  }
}

void match_everything (void)
{
  match_exit ();
  match_neighbor ();
  match_possession ();
  match_me ();
  match_here ();
  match_absolute ();
  match_player ();
}

#ifdef DO_GLOBALS
void match_remote (void)
{
  match_exit ();
  match_remote_contents ();
  match_absolute ();
  match_player ();
}
#endif

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;
  }
}

dbref match_controlled (dbref player, const char *name)
{
  dbref match;
  init_match (player, name, NOTYPE);
  match_everything ();

  match = noisy_match_result ();
  if (match != NOTHING && !controls (player, match)) {
    notify (player, "Permission denied.");
    return NOTHING;
  } else {
    return match;
  }
}