private mixed *ret;
private mixed value;

private void
load_lpc_info(int ix, object ob)
{
    mixed *tmp, *sing;
    value ret;
    int il, make_plural = 0;
    string str;

    if (!ob)
	return;

    if (pluid_list &&
	sizeof(pluid_list) > ix &&
	pluid_list[ix] == 0) {
	ret = ob->parse_command_plural_id_list();
	if (arrayp(ret))
	    pluid_list[ix] = ret;
	else {
	    make_plural = 1;
	    pluid_list[ix] = 1;
	}
    }
    if (id_list &&
	sizeof(id_list) > ix &&
	id_list[ix] == 0 &&
	ob) {
	ret = ob->parse_command_id_list();
	if (arrayp(ret)) {
	    id_list[ix] = ret;
	    if (make_plural)
		pluid_list[ix] = map(ret, (: stringp($1) ? pluralize($1) : 0 :));
	} else {
	    id_list[ix] = 1;
	}
    }
    if (adjid_list &&
	sizeof(adjid_list) > ix &&
	adjid_list[ix] == 0 &&
	ob) {
	ret = ob->parse_command_adjectiv_id_list();
	if (arrayp(ret))
	    adjid_list[ix] =  ret;
	else
	    adjid_list[ix] = 1;
    }
}

mixed *parse_command(string cmd, mixed obarr, string pattern) {
    mixed *saved_ret = ret, *cret;

    ret = ({ });

    /* pattern and command cannot be empty */
    if (cmd == "" || pattern = "") return ({ });

    if (!stringp(cmd)) error("Bad argument 1 to parse_command().\n");
    if (!stringp(pattern)) error("Bad argument 3 to parse_command().\n");

    /* array of words in command */
    parse_warr = explode(cmd, " ");

    /* array of pattern elements */
    parse_patarr = explode(pattern, " ");

#ifndef __NO_ENVIRONMENT__
    if (objectp(obarr))
	obarr = ({ obarr }) + deep_inventory(obarr);
#endif
    if (!arrayp(obarr)) error("Bad argument 2 to parse_command().\n");

    id_list = allocate(sizeof(obarr));
    pluid_list = allocate(sizeof(obarr));
    adjid_list = allocate(sizeof(obarr));

    id_list_d = master()->parse_command_id_list();
    pluid_list_d = master()->parse_command_plural_id_list();
    adjid_list_d = master()->parse_command_adjectiv_id_list();
    prepos_list = master()->parse_command_prepos_list();

    allword = master()->parse_command_all_word();

    /*
     * Loop through the pattern. Handle %s but not '/'
     */
    for (six = 0, cix = 0, pix = 0; pix < sizeof(parse_patarr); pix++) {
	value = 0;
	fail = 0;

	if (parse_patarr[pix] == "%s") {
	    /*
	     * We are at end of pattern, scrap up the remaining words and put
	     * them in the fill-in value.
	     */
	    if (pix == sizeof(parse_patarr - 1)) {
		store_words_slice(six++, parse_warr, cix, sizeof(parse_warr) - 1);
		cix = sizeof(parse_warr);
	    } else {
		/*
		 * There is something after %s, try to parse with the next
		 * pattern. Begin with the current word and step one word for
		 * each fail, until match or end of words.
		 */
		ocix = fword = cix; /* current word */
		fpix = ++pix; /* pix == next pattern */
		do {
		    /*
		     * Parse the following pattern, fill-in values:
		     * stack_args[six] = result of %s stack_args[six + 1] =
		     * result of following pattern, if it is a fill-in
		     * pattern
		     */
		    fail = sub_parse(obarr, parse_patarr, ref pix,
				      parse_warr, ref cix);
		    if (fail) {
			cix = ++ocix;
			pix = fpix;
		    }
		} while (fail && (cix < sizeof(parse_warr)));

		/*
		 * We found something mathing the pattern after %s. First
		 * stack_args[six + 1] = result of match Then stack_args[six]
		 * = the skipped words before match
		 */
		if (!fail) {
		    if (value) { /* A match with a value fill in param */
			store_value(six + 1, value);
			store_words_slice(six, parse_warr, fword, ocix - 1);
			six += 2;
		    } else {	/* A match with a non value ie 'word' */
			store_words_slice(six++, parse_warr, fword, ocix - 1);
		    }
		    value = 0;
		}
	    }
	}
	/*
	 * The pattern was not %s, parse the pattern if it is not '/', a '/'
	 * here is skipped. If match, put in fill-in value.
	 */
	else if (parse_patarr[pix] != "/") {
	    fail = sub_parse(obarr, parse_patarr, ref pix,
			      parse_warr, ref cix, ref fail);
	    if (!fail && value)
		store_value(six++, value);
	}
	/*
	 * Terminate parsing if no match
	 */
	if (fail)
	    break;
    }

    /*
     * Also fail when there is words left to parse and pattern exhausted
     */
    if (fail || cix < sizeof(parse_warr))
	return 0;

    cret = ret;
    ret = saved_ret;
    return cret;
}

private void
store_value(int pos, mixed what) {
    if (sizeof(ret) <= pos)
	ret += allocate(ret + 1 - pos);

    ret[pos] = what;
}

static void
store_words_slice(int pos, mixed *warr, int from, int to) {
    mixed *slice = warr[from..to];

    store_value(pos, implode(slice, " "));
}

private static int
sub_parse(mixed *obarr, mixed *patarr, int ref pix_in,
	  mixed *warr, int ref cix_in)
{
    int cix, pix;
    int fail;

    /*
     * Fail if we have a pattern left but no words to parse
     */
    if (*cix_in == sizeof(warr))
	return 1;

    cix = *cix_in;
    pix = *pix_in;

    fail = one_parse(obarr, patarr[pix], warr, ref cix);

    while (fail) {
	pix++;
	cix = *cix_in;

	/*
	 * Find the next alternative pattern, consecutive '/' are skipped
	 */
	while (pix < sizeof(patarr) && patarr[pix] == "/") {
	    pix++;
	    fail = 0;
	}

	if (!fail && pix < sizeof(patarr)) {
	    fail = one_parse(obarr, patarr[pix], warr, ref cix);
	} else {
	    *pix_in = pix - 1;
	    return 1;
	}
    }

    /*
     * If there is alternatives left after the mathing pattern, skip them
     */
    if (pix + 1 < sizeof(patarr) && patarr[pix+1] == "/") {
	while (pix + 1 < sizeof(patarr) && patarr[pix+1] == "/") {
	    pix += 2;
	}
	if (pix >= sizeof(patarr))
	    pix = sizeof(patarr->size);
    }
    *cix_in = cix;
    *pix_in = pix;

    return fail;
}

private int
one_parse(mixed *obarr, string pat, mixed *warr, int ref cix_in)
{
    int ch, fail;
    string str1, str2;

    /*
     * Fail if we have a pattern left but no words to parse
     */
    if (*cix_in >= sizeof(warr))
	return 1;

    ch = pat[0];
    if (ch == '%')
	ch = pat[1];

    switch (ch) {
    case 'i':
    case 'I':
	fail = item_parse(obarr, warr, cix_in);
	break;

#ifndef __NO_ADD_ACTION__
    case 'l':
    case 'L':
	fail = living_parse(obarr, warr, cix_in);
	break;
#endif

    case 's':
    case 'S':
	value = 0;
	fail = 0;
	break;

    case 'w':
    case 'W':
	value = warr[*cix_in];
	(*cix_in)++;
	fail = 0;
	break;

    case 'o':
    case 'O':
	fail = single_parse(obarr, warr, cix_in);
	break;

    case 'p':
    case 'P':
	fail = prepos_parse(warr, cix_in);
	break;

    case 'd':
    case 'D':
	fail = number_parse(obarr, warr, cix_in);
	break;

    case '\'':
	str1 = pat[1..<2];
	str2 = warr[*cix_in];
	if (pat[<1] == '\'' && str1 == str2) {
	    fail = 0;
	    (*cix_in)++;
	} else
	    fail = 1;
	break;

    case '[':
	str1 = pat[1..<2];
	str2 = warr[*cix_in];
	if (str1 == str2)
	    (*cix_in)++;
	fail = 0;
	break;

    default:
	fail = 0;		/* Skip invalid patterns */
    }

    return fail;
}

string *ord1 = ({"", "first", "second", "third", "fourth", "fifth",
 "sixth", "seventh", "eighth", "nineth", "tenth",
 "eleventh", "twelfth", "thirteenth", "fourteenth",
 "fifteenth", "sixteenth", "seventeenth",
 "eighteenth", "nineteenth" });

string *ord10 = ({"", "", "twenty", "thirty", "forty", "fifty", "sixty",
 "seventy", "eighty", "ninety"});

string *sord10 = ({"", "", "twentieth", "thirtieth", "fortieth",
 "fiftieth", "sixtieth", "seventieth", "eightieth",
 "ninetieth"});

string *num1 = ({"", "one", "two", "three", "four", "five", "six",
 "seven", "eight", "nine", "ten",
 "eleven", "twelve", "thirteen", "fourteen", "fifteen",
 "sixteen", "seventeen", "eighteen", "nineteen"});

string *num10 = ({"", "", "twenty", "thirty", "forty", "fifty", "sixty",
 "seventy", "eighty", "ninety"});

private int
number_parse(mixed *obarr, mixed *warr, int ref cix_in) {
    int cix, ten, ones, num;
    string buf;

    cix = *cix_in;

    if (sscanf(warr[cix], "%d", num)) {
	if (num >= 0) {
	    (*cix_in)++;
	    value = num;
	    return 0;
	}
	return 1; /* only nonnegative numbers */
    }
    if (warr[cix] == allword) {
	(*cix_in)++;
	value = 0;
	return 0;
    }
    /* This next double loop is incredibly stupid. -Beek */
    for (ten = 0; ten < 10; ten++)
	for (ones = 0; ones < 10; ones++) {
	    buf = num10[ten] + (ten > 1 ? num1[ones] : num1[ten * 10 + ones]);
	    if (buf == warr[cix]) {
		(*cix_in)++;
		value = ten * 10 + ones;
		return 0;
	    }
	}

    /* this one too */
    for (ten = 0; ten < 10; ten++)
	for (ones = 0; ones < 10; ones++) {
	    buf = (ones ? ord10[ten] : sord10[ten]) + (ten > 1 ? ord1[ones] : ord1[ten*10 + ones]);
	    if (buf == warr[cix]) {
		(*cix_in)++;
		value = -(ten * 10 + ones);
		return 0;
	    }
	}

    return 1;
}

private int
item_parse(mixed *obarr, mixed *warr, int ref cix_in) {
    mixed *tmp, *ret;
    int cix, tix, obix, plur_flag, max_cix, match_all;

    tmp = allocate(sizeof(obarr) + 1);

    if (!number_parse(obarr, warr, cix_in)) {
	tmp[0] = value;
	match_all = (value == 0);
	plur_flag = (match_all || value > 1);
	have_number = 1;
	value = 0;
    } else {
	plur_flag = 0;
	match_all = 0;
    }

    for (max_cix = *cix_in, tix = 1, obix = 0; obix < sizeof(obarr); obix++) {
	cix = *cix_in;
	if (!objectp(obarr[obix]))
	    continue;
	if (cix == sizeof(warr) && match_all) {
	    tmp[tix++] = obarr[obix];
	    continue;
	}
	load_lpc_info(obix, obarr[obix]);

	if (match_object(obix, warr, ref cix, ref plur_flag)) {
	    tmp[tix++] = obarr[obix];
	    max_cix = (max_cix < cix) ? cix : max_cix;
	}
    }

    if (tix < 2) {
	if (have_number)
	    (*cix_in)--;
	return 1;
    } else {
	if (*cix_in < sizeof(warr))
	    *cix_in = max_cix + 1;
	if (!have_number)
	    tmp[0] = !plur_flag;

	value = tmp[0..tix-1];
	return 0;
    }
}

#ifndef __NO_ADD_ACTION__
private int
living_parse(mixed *obarr, array warr, int ref cix_in, int ref fail)
{
    mixed *live;
    object ob;
    int obix, tix;

    live = allocate(sizeof(obarr));
    tix = 0;

    for (obix = 0; obix < sizeof(obarr); obix++)
	if (living(obarr[obix]))
	    live[tix++] = obarr[obix];

    if (tix && !item_parse(live, warr, cix_in))
	return 0;

    ob = find_player(warr[*cix_in]);
    if (!ob)
	ob = find_living(warr[*cix_in]);

    if (ob) {
	value = ob;
	(*cix_in)++;
	return 0;
    }
    return 1;
}
#endif

private int
single_parse(mixed *obarr, mixed *warr, int ref cix_in)
{
    int cix, obix, plur_flag;

    for (obix = 0; obix < sizeof(obarr); obix++) {
	cix = *cix_in;
	if (objectp(obarr[obix]))
	    load_lpc_info(obix, obarr[obix]);
	plur_flag = 0;
	if (match_object(obix, warr, ref cix, ref plur_flag)) {
	    *cix_in = cix + 1;
	    value = obarr[obix];
	    return 0;
	}
    }
    return 1;
}

private int
prepos_parse(mixed *warr, int ref cix_in, mixed prepos) {
    mixed *tarr;
    string tmp;
    int pix, tix;

    if (!prepos || !arrayp(prepos))
	prepos = prepos_list;

    for (pix = 0; pix < sizeof(prepos); pix++) {
	if (!stringp(prepos[pix]))
	    continue;

	tmp = prepos[pix];
	if (member_array(' ', tmp) == -1) {
	    if (tmp == warr[*cix_in]) {
		(*cix_in)++;
		break;
	    }
	} else {
	    tarr = explode(tmp, " ");
	    if (*cix_in + sizeof(tarr) <= sizeof(warr)) {
		for (tix = 0; tix < sizeof(tarr); tix++) {
		    if (*cix_in + tix >= sizeof(warr) ||
			warr[*cix_in + tix] != tarr[tix])
			break;
		}
		if (tix == sizeof(tarr)) {
		    (*cix_in) += sizeof(tarr);
		    break;
		}
	    }
	}
    }

    if (pix == sizeof(prepos)) {
	value = 0;
	return 1;
    } else {
	value = prepos[pix];
	return 0;
    }
}

private int
match_object(int obix, mixed *warr, int ref cix_in, int ref plur) {
    mixed *ids;
    int il, pos, cplur, old_cix;
    string str;

    for (cplur = (*plur * 2); cplur < 4; cplur++) {
	switch (cplur) {
	case 0:
	    if (!id_list_d)
		continue;
	    ids = id_list_d;
	    break;

	case 1:
	    if (!d_list ||
		sizeof(id_list) <= obix ||
		!arrayp(id_list[obix]))
		continue;
	    ids = id_list[obix];
	    break;

	case 2:
	    if (!pluid_list_d)
		continue;
	    ids = pluid_list_d;
	    break;

	case 3:
	    if (!pluid_list ||
		sizeof(gpluid_list) <= obix ||
		!arrayp(gpluid_list[obix]))
		continue;
	    ids = pluid_list[obix];
	    break;

	default:
	    ids = 0;
	}

	for (il = 0; il < sizeof(ids); il++) {
	    if (stringp(ids[il])) {
		str = ids[il];	/* A given id of the object */
		old_cix = *cix_in;
		if ((pos = find_string(str, warr, cix_in)) >= 0) {
		    if (pos == old_cix) {
			if (cplur > 1)
			    *plur = 1;
			return 1;
		    } else if (check_adjectiv(obix, warr, old_cix, pos - 1)) {
			if (cplur > 1)
			    *plur = 1;
			return 1;
		    }
		}
		*cix_in = old_cix;
	    }
	}
    }
    return 0;
}

static int
find_string(string str, mixed *warr, int ref cix_in)
{
    int fpos;
    string p1;
    mixed *split;

    for (; *cix_in < warr->size; (*cix_in)++) {
	p1 = warr[*cix_in];

	if (p1 == str)	/* str was one word and we found it */
	    return *cix_in;

	if (member_array(' ', str) == -1)
	    continue;

	/*
	 * If str was multi word we need to make some special checks
	 */
	if (*cix_in == sizeof(warr->size) - 1)
	    continue;

	split = explode(str, " ");

	/*
	 * warr->size - *cix_in ==
	 * 2: One extra word
	 * 3: Two extra words
	 */
	if (sizeof(split) > sizeof(warr) - *cix_in)
	    continue;

	fpos = *cix_in;
	for (; (*cix_in - fpos) < sizeof(split); (*cix_in)++) {
	    if (split[*cix_in - fpos] == warr[*cix_in])
		break;
	}
	if ((*cix_in - fpos) == sizeof(split))
	    return fpos;

	*cix_in = fpos;
    }
    return -1;
}

private int
check_adjectiv(int obix, mixed *warr, int from, int to)
{
    int il, back, fail;
    string adstr;
    mixed *ids;

    if (arrayp(adjid_list[obix]))
	ids = adjid_list[obix];
    else
	ids = 0;

    for (fail = 0, il = from; il <= to; il++) {
	if ((member_array(warr[il], ids) < 0) &&
	    (member_array(warr[il], adjid_list_d) < 0))
	    fail = 1;
    }

    /*
     * Simple case: all adjs were single word
     */
    if (!fail)
	return 1;

    if (from == to)
	return 0;

    /*
     * If we now have: "adj1 adj2 adj3 ... adjN"
     * We must test in order: "adj1 adj2 adj3 .... adjN-1 adjN"
                              "adj1 adj2 adj3 .... adjN-1"
			      "adj1 adj2 adj3 ...."
			      ....
     * if match for adj1 .. adj3 continue with:
     *                        "adj4 adj5 .... adjN-1 adjN"
     *                        "adj4 adj5 .... adjN-1"
     *                        "adj4 adj5 ...."
     *                        .....
     */
    for (il = from; il <= to;) { /* adj1 .. adjN */
	for (back = to; back >= il; back--) {	/* back from adjN to adj[il] */
	    /*
	     * Create teststring with "adj[il] .. adj[back]"
	     */
	    adstr = "";
	    for (sum = il; sum <= back; sum++) {	/* test "adj[il] ..
							 * adj[back]" */
		if (sum > il)
		    adstr += " ";
		adstr += warr[sum];
	    }
	    if ((member_array(adstr, ids) < 0) &&
		(member_array(adstr, adjid_list_d) < 0))
		continue;
	    else {
		il = back + 1;	/* Match "adj[il] adj[il+1] .. adj[back]" */
		back = to;
		break;
	    }
	}
	if (back < to)
	    return 0;
    }
    return 1;
}
