/* match.c -- Routines for parsing arguments */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char *RCSid = "$Id: match.c,v 1.7 1995/03/21 00:00:39 ambar Exp $";
USE(RCSid);
#endif

#include "externs.h"
#include "match.h"
#include "attrs.h"

static dbref exact_match = NOTHING;	/* holds result of exact match */
static int have_exact = 0;	/* Have a precise match (dbref, 'me', etc) */
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 = 0;	/* holds total number of inexact matches */
static dbref match_who = NOTHING;	/* player performing match */
static const char *match_name = "";	/* name to match */
static int preferred_type = NOTYPE;	/* preferred type */
static int local_match = 0;	/* Matched something locally, not by number */

/*
 * This function removes repeated spaces from the template to which object
 * names are being matched.  It also removes inital and terminal spaces.
 */

static char *
munge_space_for_match(name)
    char *name;
{
    static char buffer[LBUF_SIZE];
    char *p, *q;

    p = name;
    q = buffer;
    while (isspace(*p))
	p++;			/* remove inital spaces */
    while (*p) {
	while (*p && !isspace(*p))
	    *q++ = *p++;
	while (*p && isspace(*++p));
	if (*p)
	    *q++ = ' ';
    }
    *q = '\0';			/* remove terminal spaces and terminate
			 * string */
    return (buffer);
}

/*
 * check a list for objects that are prefix's for string, && are controlled
 * || link_ok
 */

dbref 
pref_match(player, list, string)
    dbref player, list;
    const char *string;
{
    dbref lmatch;
    int mlen;

    lmatch = NOTHING;
    mlen = 0;
    while (list != NOTHING) {
	if (string_prefix(string, Name(list)) &&
	    Puppet(list) &&
	    controls(player, list)) {
	    if (strlen(Name(list)) > mlen) {
		lmatch = list;
		mlen = strlen(Name(list));
	    }
	}
	list = Next(list);
    }
    return (lmatch);
}

void 
init_match(player, name, type)
    dbref player;
    const char *name;
    int type;
{
    exact_match = last_match = NOTHING;
    have_exact = 0;
    match_count = 0;
    match_who = player;
    match_name = munge_space_for_match((char *) name);
    check_keys = 0;
    preferred_type = type;
    local_match = 0;
}

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

static dbref 
choose_thing(thing1, thing2)
    dbref thing1, 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, A_LOCK);
	has2 = could_doit(match_who, thing2, A_LOCK);

	if (has1 && !has2) {
	    return thing1;
	} else if (has2 && !has1) {
	    return thing2;
	}
	/* else fall through */
    }
    return (random() % 2 ? thing1 : thing2);
}

void 
NDECL(match_player)
{
    dbref match;
    char *p;

    if (have_exact)
	return;
    if (*match_name == LOOKUP_TOKEN) {
	for (p = (char *) match_name + 1; isspace(*p); p++);
	if ((match = lookup_player(NOTHING, p, 1)) != NOTHING) {
	    exact_match = match;
	    have_exact = 1;
	    local_match = 0;
	}
    }
}
/* returns nnn if name = #nnn, else NOTHING */

static dbref 
absolute_name(need_pound)
    int need_pound;
{
    dbref match;
    char *mname;

    mname = (char *) match_name;
    if (need_pound) {
	if (*match_name != NUMBER_TOKEN) {
	    return NOTHING;
	} else {
	    mname++;
	}
    }
    if (*mname) {
	match = parse_dbref(mname);
	if (Good_obj(match)) {
	    return match;
	}
    }
    return NOTHING;
}

void 
NDECL(match_absolute)
{
    dbref match;

    if (have_exact)
	return;
    match = absolute_name(1);
    if (Good_obj(match)) {
	exact_match = match;
	have_exact = 1;
	local_match = 0;
    }
}

void 
NDECL(match_numeric)
{
    dbref match;

    if (have_exact)
	return;
    match = absolute_name(0);
    if (Good_obj(match)) {
	exact_match = match;
	have_exact = 1;
	local_match = 0;
    }
}

void 
NDECL(match_controlled_absolute)
{
    dbref match;

    if (have_exact)
	return;

    match = absolute_name(1);
    if (Good_obj(match) &&
	(Controls(match_who, match) || nearby(match_who, match))) {
	exact_match = match;
	have_exact = 1;
	local_match = 0;
    }
}

void 
NDECL(match_me)
{
    if (have_exact)
	return;
    if (!string_compare(match_name, "me")) {
	exact_match = match_who;
	have_exact = 1;
	local_match = 1;
    }
}

void 
NDECL(match_home)
{
    if (have_exact)
	return;
    if (!string_compare(match_name, "home")) {
	exact_match = HOME;
	have_exact = 1;
	local_match = 0;
    }
}

void 
NDECL(match_here)
{
    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_location(match_who)) {
	if (!string_compare(match_name, "here") &&
	    Good_obj(Location(match_who))) {
	    exact_match = Location(match_who);
	    have_exact = 1;
	    local_match = 1;
	}
    }
}

static void 
match_list(first)
    dbref first;
{
    dbref absolute;
    char *namebuf;

    if (have_exact)
	return;
    absolute = absolute_name(1);
    if (!Good_obj(absolute))
	absolute = NOTHING;

    DOLIST(first, first) {
	if (first == absolute) {
	    exact_match = first;
	    have_exact = 1;
	    local_match = 1;
	    return;
	}
	/* Warning: make sure there are no other calls to Name()
	 * in choose_thing or its called subroutines; they would
	 * overwrite Name()'s static buffer which is needed by
	 * string_match().
	 */

	namebuf = Name(first);
	if (!string_compare(namebuf, match_name)) {
	    /* if multiple exact matches, randomly choose one */
	    exact_match = choose_thing(exact_match, first);
	    local_match = 1;
	} else if (string_match(namebuf, match_name)) {
	    last_match = first;
	    match_count++;
	    local_match = 1;
	}
    }
}

void 
NDECL(match_possession)
{
    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_contents(match_who))
	match_list(Contents(match_who));
}

void 
NDECL(match_neighbor)
{
    dbref loc;

    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_location(match_who)) {
	loc = Location(match_who);
	if (Good_obj(loc)) {
	    match_list(Contents(loc));
	}
    }
}

static void 
match_exit_internal(loc)
    dbref loc;
{
    dbref exit, absolute;

    if (!Good_obj(loc) || !Has_exits(loc))
	return;
    absolute = absolute_name(1);
    if (!Good_obj(absolute) || !controls(match_who, absolute))
	absolute = NOTHING;

    DOLIST(exit, Exits(loc)) {
	if (exit == absolute) {
	    exact_match = exit;
	    local_match = 1;
	    return;
	}
	if (matches_exit_from_list((char *) match_name, Name(exit))) {
	    exact_match = choose_thing(exact_match, exit);
	    local_match = 1;
	}
    }
}

void 
NDECL(match_exit)
{
    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_location(match_who))
	match_exit_internal(Location(match_who));
}

void 
NDECL(match_exit_with_parents)
{
    dbref parent;
    int lev;

    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_location(match_who)) {
	ITER_PARENTS(Location(match_who), parent, lev) {
	    match_exit_internal(parent);
	    if (exact_match != NOTHING)
		break;
	}
    }
}

void 
NDECL(match_carried_exit)
{
    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_exits(match_who))
	match_exit_internal(match_who);
}

void 
NDECL(match_carried_exit_with_parents)
{
    dbref parent;
    int lev;

    if (have_exact)
	return;
    if (Good_obj(match_who) && Has_exits(match_who)) {
	ITER_PARENTS(match_who, parent, lev) {
	    match_exit_internal(parent);
	    if (exact_match != NOTHING)
		break;
	}
    }
}

void 
NDECL(match_master_exit)
{
    if (exact_match != NOTHING)
	return;
    if (Good_obj(mudconf.master_room))
	match_exit_internal(mudconf.master_room);
}

void 
match_everything(key)
    int key;
{
    /*
     * Try matching me, then here, then absolute, then player FIRST, since
     * this will hit most cases. STOP if we get something, since those are
     * exact matches.
     */

    match_me();
    match_here();
    match_absolute();
    if (key & MAT_NUMERIC)
	match_numeric();
    if (key & MAT_HOME)
	match_home();
    match_player();
    if (have_exact)
	return;

    if (!(key & MAT_NO_EXITS)) {
	if (key & MAT_EXIT_PARENTS) {
	    match_carried_exit_with_parents();
	    match_exit_with_parents();
	} else {
	    match_carried_exit();
	    match_exit();
	}
    }
    match_neighbor();
    match_possession();
}

dbref 
NDECL(match_result)
{
    if (exact_match != NOTHING)
	return exact_match;
    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 
NDECL(last_match_result)
{
    if (exact_match != NOTHING)
	return exact_match;
    return last_match;
}

dbref 
match_status(player, match)
    dbref player, match;
{
    switch (match) {
    case NOTHING:
	notify(player, NOMATCH_MESSAGE);
	return NOTHING;
    case AMBIGUOUS:
	notify(player, AMBIGUOUS_MESSAGE);
	return NOTHING;
    case NOPERM:
	notify(player, NOPERM_MESSAGE);
	return NOTHING;
    }
    return match;
}

dbref 
NDECL(noisy_match_result)
{
    return match_status(match_who, match_result());
}

dbref 
dispatched_match_result(player)
    dbref player;
{
    dbref match, save_player;

    save_player = match_who;
    match_who = player;
    match = noisy_match_result();
    match_who = save_player;
    return match;
}

int 
NDECL(matched_locally)
{
    return local_match;
}

void 
save_match_state(mstate)
    MSTATE *mstate;
{
    mstate->exact_match = exact_match;
    mstate->have_exact = have_exact;
    mstate->check_keys = check_keys;
    mstate->last_match = last_match;
    mstate->match_count = match_count;
    mstate->match_who = match_who;
    mstate->match_name = alloc_lbuf("save_match_state");
    strcpy(mstate->match_name, match_name);
    mstate->preferred_type = preferred_type;
    mstate->local_match = local_match;
}

void 
restore_match_state(mstate)
    MSTATE *mstate;
{
    exact_match = mstate->exact_match;
    have_exact = mstate->have_exact;
    check_keys = mstate->check_keys;
    last_match = mstate->last_match;
    match_count = mstate->match_count;
    match_who = mstate->match_who;
    strcpy((char *) match_name, mstate->match_name);
    free_lbuf(mstate->match_name);
    preferred_type = mstate->preferred_type;
    local_match = mstate->local_match;
}

dbref 
match_controlled_quiet(player, name)
    dbref player;
    const char *name;
{
    dbref mat;

    init_match(player, name, NOTYPE);
    match_everything(MAT_EXIT_PARENTS);
    mat = match_result();
    if (Good_obj(mat) && !Controls(player, mat)) {
	return NOTHING;
    } else {
	return (mat);
    }
}
