/
teeny/db/
teeny/dbm/
teeny/docs/
teeny/includes/
teeny/misc/
teeny/news/
teeny/text/
/* match.c */

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

#include <stdio.h>
#include <ctype.h>

#include "teeny.h"
#include "match.h"
#include "case.h"


/*
 * Matching routines for TeenyMUD. Matching against contents lists is
 * different* from matching against exit lists, so be careful. 
 *
 * Some matching rouines require dinking with private data elsewhere, so
 * match_player() (which hacks through the DB internals) lives with the db
 * stuff in db.c, and match_who() (which hacks around with the WHO list
 * maintained in misc.c) and match_active_player (likewise) is in misc.c. 
 *
 */


struct match   *matches = NULL;
struct match   *free_matches = NULL;
int             current_best;

int             match_thing_name();
int             match_an_exit();
int             match_tokens();
struct match   *get_match();

#ifdef OLD_RAND
long            rand();
#else
long            random();
#endif					/* OLD_RAND */


int		best_match();
int		score_match();
int		pick2();
int		pick3();

/*
 * Searches the contents list, matching contents-wise (i.e. inexact matches
 * OK, pick the best), and if that comes up empty, then the exits list
 * matching exit-wise (i.e. must match an alias exactly). It returns one of
 * the best it finds, chosen at random. Note that any match at all in the
 * contents list will take precedence. 
 *
 * Now it will return -2 if a tie occurs that should be notified as
 * "I can't tell which one you mean."
 *
 */

int 
match_here(player, thing, str, code)
  int             player;
  int             thing;
  char           *str;
  int             code;
{
  int             list, total, the_match;
  int             loc, flags;

  /* Catch "me" if we are, in fact, searching the player's location */

  if ((code & MAT_PLAYERS) && strcmp(str, "me") == 0)
  {
    if (get_int_elt(player, LOC, &loc) == -1)
    {
      warning("match_here", "could not get LOC on player");
      return (-1);
    }
    if (loc == thing)
    {
      return (player);
    }
  }
  /* Catch object number references to, verify locations and types */
  /* Fall through if anything is wrong, so *names* like #22 are OK */

  if (*str == '#' && isdigit(str[1]))
  {
    the_match = atoi(str + 1);
    if (exists_object(the_match))
    {
      if (get_int_elt(the_match, LOC, &loc) == -1)
      {
	warning("match_here", "could not get LOC");
	return (-1);
      }
      if (loc == thing)
      {
	if (get_int_elt(the_match, FLAGS, &flags) == -1)
	{
	  warning("match_here", "bad flags ref");
	  return (-1);
	}
	switch (flags & TYPE_MASK)
	{
	case TYP_THING:
	  if (code & MAT_THINGS)
	    return (the_match);
	  break;
	case TYP_PLAYER:
	  if (code & MAT_PLAYERS)
	    return (the_match);
	  break;
	case TYP_EXIT:
	  if (code & MAT_EXITS)
	    return (the_match);
	  break;
	}
      }
    }
  }
  /* Do exits first. Only exact matches count here, so we  */
  /* avoid troubles with 's' matching a thing called 'Sam' */
  /* rather than the exit 's;so;sou;sout;south'            */

  if (code & MAT_EXITS)
  {

    if (get_int_elt(thing, EXITS, &list) == -1)
    {
      warning("match_here", "bad exits list on thing.");
      return (-1);
    }
    matches = match_exits(str, list, &total);

    if (matches != NULL)
    {
      /* OK, we have some exits on this list. Pick one. */

      the_match = choose_match(total);
      free_match_list(matches);
      matches = NULL;
      return (the_match);
    }
  }
  /* no dice on exits. Try other stuff */

  if (code & (MAT_THINGS | MAT_PLAYERS))
  {

    /* Go get the list of things on the thing. */

    if (get_int_elt(thing, CONTENTS, &list) == -1)
    {
      warning("match_here", "bad contents list on thing.");
      return (-1);
    }
    current_best = 0;
    total = match_contents(str, list, code);
    if (total == -2) return (-2);
    if (total != 0)
    {
      the_match = choose_match(total);
      free_match_list(matches);
      matches = NULL;
      return (the_match);
    }
  }
  return (-1);
}


/*
 * Match against a contents list. Makes a list of all the stuff in the list
 * that matched as well as possible. Returns total number of matches. May be
 * called multiple times without spamming the matches list. 
 *
 * Caller MUST zero current_best. 
 */

match_contents(str, list, code)

  char           *str;
  int             list;		/* First object in the contents list */
  int             code;

{
  int             count, total_matches, perfect_match;
  char           *name, *p, ch;
  struct match   *current_match;
  int             flags, type;

  /* Loop across the entire list */

  total_matches = 0;
  perfect_match = 0;	/* if we have >1 perfect match, return -2 (tie) */
  while (list != -1)
  {				/* While not end of list */

    if (get_str_elt(list, NAME, &name) == -1)
    {
      warning("match_contents", "nameless object found!");
      break;
    }
    if (get_int_elt(list, FLAGS, &flags) == -1)
    {
      warning("match_contents", "bad flags reference");
      break;
    }
    type = TYPE_MASK & flags;

    /* Check to see if we are looking for this sort of thing */

    if ((!(code & MAT_PLAYERS) && type == TYP_PLAYER)
	|| (!(code & MAT_THINGS) && type == TYP_THING))
    {
      if (get_int_elt(list, NEXT, &list) == -1)
      {
	warning("match_contents", "bad contents list");
	break;
      }
      continue;
    }
    /* If it's a player, terminate after the name, before pwd */

    if (type == TYP_PLAYER)
    {
      p = name;
      while (!isspace(*p) && *p)
	p++;
      ch = *p;
      *p = '\0';
    } else
    {
      ch = '\0';
    }

    count = match_thing_name(str, name);

    if (ch != '\0')			/* If required, repair the name */
      *p = ch;

    if (count != 0)
    {
      if (current_best == count)
      {

	/* This match was just as good */

	current_match = get_match();
	current_match->obj = list;
	insert_match(current_match, &matches);
	total_matches++;

      } else
	if ((count > current_best && current_best >= 0)
	    || count == -1)
      {

	/* This latest was a better match */

	free_match_list(matches);
	matches = NULL;
	current_match = get_match();
	current_match->obj = list;
	insert_match(current_match, &matches);
	total_matches = 1;
	current_best = count;
      }
    }
    if (get_int_elt(list, NEXT, &list) == -1)
    {
      warning("match_contents", "bad contents list");
      break;
    }
  }
  /*
   * if we have a tie and the string isn't specific enough,
   * then return -2 and let the player know we can't tell what
   * he's referring to.  Otherwise it's okay to choose randomly
   * from the choices, or if there's only one, well yay.
   */
  if(current_best != -1 && total_matches > 1)
    return (-2);
  else
    return (total_matches);
}

/*
 * Matches a string against the list of exits. Only exact matches are
 * allowed, we handle semi-colon seperated aliases on exits. Matching is not
 * case sensitive. Things better not have whitespace leading or following. 
 *
 */

struct match   *
match_exits(str, list, count)
  char           *str;
  int             list;
  int            *count;
{
  int             total_matches;
  struct match   *current_match, *exits_matched;
  char           *name;

  while (*str && isspace(*str))
    str++;

  /* Loop across the list. 'list' should be the first element */

  total_matches = 0;
  exits_matched = NULL;
  while (list != -1)
  {
    if (get_str_elt(list, NAME, &name) == -1)
    {
      warning("match_exits", "bad exits list");
      break;
    }
    while (*name && isspace(*name))
      name++;

    /* See if we have a match on this name */

    if (match_an_exit(str, name))
    {

      /* Get a new match struct and enlist it */

      current_match = get_match();
      current_match->obj = list;
      insert_match(current_match, &exits_matched);
      total_matches++;
    }
    /* Get the next list element */

    if (get_int_elt(list, NEXT, &list) == -1)
    {
      warning("match_exits", "bad exits list");
      break;
    }
  }
  *count = total_matches;


  if (total_matches == 0)
  {
    return (NULL);
  }
  return (exits_matched);
}


/*
 * Chooses a match at random from the 'matches' list, using the number passed
 * as a count of how many things are on the list. 
 *
 */

int 
choose_match(total)
  int             total;
{
  int             the_match;
  struct match   *current_match;

  if (total == 0)
  {
    return (-1);
  }
#ifdef OLD_RAND
  the_match = (int) (rand() % (long) total);
#else
  the_match = (int) (random() % (long) total);
#endif					/* OLD_RAND */

  current_match = matches;

  while(the_match > 0)
  {
    current_match = current_match->fwd;
    the_match--;
  } 

/*
 * Old way.  Made match confused.
 * if (total > 1)
 *   return (-2);
 */

  the_match = current_match->obj;	/* This is bad. Recycling vbls! */

  return (the_match);
}

/*
 * Matches a string against an exit name. We require exact (though not case
 * sensitive) matches. Cope with semi-colon seperated aliases in the exit
 * name. 
 *
 * Return 1 if a match, 0 if not. 
 *
 */

int 
match_an_exit(str, name)

  char           *str, *name;

{
  char           *p;

  p = str;
  while (1)
  {

    while (DOWNCASE(*p) == DOWNCASE(*name) && *p
	   && *name != ';' && *name)
    {
      p++;
      name++;
    }

    /* Post mortem */

    if ((*name == ';' || *name == '\0') && *p == '\0')
    {
      return (1);
    }
    while (*name != ';' && *name)
      name++;
    if (*name == '\0')
    {
      break;
    } else
    {
      /* Skip the semi-colon and any whitespace after it */
      while (*name && ((*name == ';') || isspace(*name)))
	name++;
    }

    p = str;
  }
  return (0);
}

/*
 * Match a thing name. The p is, roughly, what the monkey at the keyboard
 * typed, q is the name we're matching against. Returns number of characters
 * matched, or -1 to indicate an exact match. 
 *
 * Case insensitive. 
 */

int 
match_thing_name(p, q)

  char           *p, *q;
{
  int             matched = 0;
  int             old_matched = 0;
  int             exact;
  int             initial_token;

  while (isspace(*p) && *p)
    p++;
  if (*p == '\0')
  {
    return (0);
  }
  /* We slide along q, trying to match tokens in p against initial */
  /* segments of sequential tokens of q. We keep track of stuff.   */

  initial_token = 1;
  while (1)
  {				/* Break out when we run out of space in q */

    while (isspace(*q) && *q)
      q++;
    if (*q == '\0')
      break;

    matched = match_tokens(p, q, &exact);

    if (exact && initial_token)
    {
      return (-1);		/* Exact match */
    } else
    if (matched > old_matched)
    {
      old_matched = matched;
    }
    /* Skip q ahead to the end of this token */

    while (!isspace(*q) && *q)
      q++;
    if (*q == '\0')
      break;
    initial_token = 0;
  }
  return (old_matched);		/* This will be the best count we found */
}

/*
 * Match p against q, token by token. p's tokens must be initial segments of
 * q's tokens, in sequence, otherwise no match at all. Returns number of
 * characters matched. Sets the value of exact to 1 or 0 to indicate whether
 * or not all tokens matched exactly. 
 *
 * Not case sensitive. Pretty much assumes no leading or trailing whitespace,
 * this should not be a big problem. It's a *game*. 
 *
 */

int 
match_tokens(p, q, exact)

  char           *p, *q;
  int            *exact;

{
  int             count = 0;

  *exact = 1;
  while (1)
  {

    /* Loop across characters of this token */

    while (DOWNCASE(*p) == DOWNCASE(*q) && !isspace(p[1])
	   && !isspace(q[1]) && *p && *q)
    {

      p++;
      q++;
      count++;
    }

    /* Post mortem. */

    if (!(*q) && *p)
    {				/* q ran out early. */
      count = *exact = 0;
      break;
    }
    if (*p == '\0')
    {
      if (*q)
	*exact = 0;
      break;
    }
    /* Neither *p nor *q are \0 */

    if (DOWNCASE(*p) != DOWNCASE(*q))
    {				/* No Match */
      count = *exact = 0;
      break;
    }
    /* OK. one of p[1] or q[1] was a space */

    if (isspace(p[1]) || p[1] == '\0')
    {
      count++;			/* Count misses a char. */
      p++;
      while (isspace(*p) && *p)
	p++;
    } else
    {
      count = *exact = 0;
      break;			/* No match */
    }
    if (!isspace(q[1]) && q[1])
    {
      *exact = 0;
    }
    while (!isspace(*q) && *q)
      q++;
    while (isspace(*q) && *q)
      q++;
  }
  return (count);
}

/*
 * Insert a match struct into a match list. 
 */

insert_match(mt, mtlist)

  struct match   *mt, **mtlist;

{
  if (*mtlist == NULL)
  {
    mt->back = mt->fwd = *mtlist = mt;
    return;
  }
  mt->fwd = (*mtlist)->fwd;
  mt->back = (*mtlist);
  ((*mtlist)->fwd)->back = mt;
  (*mtlist)->fwd = mt;
}

/*
 * Management code for match structs. We don't wanna malloc() and free all
 * over the place, so we do it more efficiently ourselves. I know, this is
 * anal retentive and probably no faster. Hah! 
 *
 */

struct match   *
get_match()
{
  int             i;
  struct match   *ret;

  /* If we have no free match structs around to give away, go get some */

  if (free_matches == NULL)
  {
    free_matches = (struct match *) ty_malloc(
					      32 * sizeof(struct match),
					      "get_match");

    /* Link them up into a free list */

    for (i = 0; i < 31; i++)
    {
      free_matches[i].fwd = &(free_matches[i + 1]);
      free_matches[i + 1].back = &(free_matches[i]);
    }
    free_matches[0].back = &(free_matches[31]);
    free_matches[31].fwd = &(free_matches[0]);
  }
  /* Grab a match struct off the free list */

  if (free_matches->back == free_matches)
  {
    ret = free_matches;
    free_matches = NULL;
  } else
  {
    ret = free_matches->fwd;
    (ret->fwd)->back = free_matches;
    free_matches->fwd = ret->fwd;
  }

  return (ret);
}

/*
 * Places a list of matches on the free list. Copes with an empty list. 
 */

free_match_list(mt)

  struct match   *mt;

{
  if (mt == NULL)
  {
    return;
  }
  if (free_matches == NULL)
  {
    free_matches = mt;
    return;
  }
  (mt->back)->fwd = free_matches->fwd;
  (free_matches->fwd)->back = mt->back;
  mt->back = free_matches;
  free_matches->fwd = mt;

}

/*
 * Takes four objects, and returns the best match with a given string.
 */
int best_match(str, match1, match2, match3, match4)
char *str;
int match1, match2, match3, match4;
{
  int mscore[4], smatch[4], i, j, tie;
  int tieflag = 0;

  smatch[0] = match1;
  smatch[1] = match2;
  smatch[2] = match3;
  smatch[3] = match4;

  for(i=0,j=0;i<4;i++)
  {
    if(smatch[i] == -2) {
      smatch[i] = -1;
      tieflag = 1;
    }
    if(smatch[i] == -1)
      mscore[i] = -2;
    else
      mscore[i] = score_match(str, smatch[i]);
    if(mscore[i] > 1) mscore[i] = 1;
    j += mscore[i];
  }
  if (j == -8)
    return (-1 - tieflag); 	/* No matches (-1) ties exist (-2) */

  if (smatch[3] > 0) {			/* all exits are perfect */
    mscore[3] = -1;
  }

					/* If there are perfect matches, */
					/* pick one randomly.            */
#ifdef OLD_RAND
  j = rand();
#else
  j = random();
#endif					/* OLD_RAND */
  for (i=0;i<4;i++,j++)
    if (mscore[j%4] == -1)
      return (smatch[j%4]);

  /*
   * If tieflag is set and there's no perfect matches, we do have
   * ties, so return -2 immediately.
   */
  if (tieflag) return (-2);

  /*
   * Okay, no exact matches.  Next, check for ties... (ties return -2) 
   * Using smart algorithm, at the same, check for best match.
   */
/*
  j = 0;
  best = mscore[0];
  tie = 0;
  for (i=1;i<4;i++)
  {
    if (mscore[i] == best)
      tie = 1;
    if (mscore[i] > best)
    {
      best = mscore[i];
      tie = 0;
      j = i;
    }
  }
  if (tie) return (-2);

  return (smatch[j]);
*/
  for(tie=i=0;i<4;i++)
  {
    if (mscore[i] == 1) j = i;
    tie += mscore[i];
  }
  if (tie > 1) return (-2);

  return (smatch[j]);
}

int score_match(str, object)
char *str;
int object;
{
  int flags, score;
  char *name, *p, ch;
  if(get_str_elt(object, NAME, &name) == -1
     || get_int_elt(object, FLAGS, &flags) == -1)
  {
    warning("score_match", "bad object name or flags ref");
    /* treat as not a match. */
    return (-2);
  }
  if((flags & TYPE_MASK) == TYP_PLAYER)
  {
    p = name;
    while (!isspace(*p) && *p)
      p++;
    ch = *p;
    *p = '\0';
  } else
  {
    ch = '\0';
  }
  if((flags & TYPE_MASK) == TYP_EXIT)
    score = match_an_exit(str, name) ? -1 : 0;
  else
    score = match_thing_name(str, name);

  if (ch != '\0')			/* clean up. */
    *p = ch;

  return (score);
}