/*
 * alias.c Handles command aliases for irc.c 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990, 1995 Michael Sandroff and others 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#include "irc.h"
#include "alias.h"
#include "alist.h"
#include "array.h"
#include "dcc.h"
#include "commands.h"
#include "files.h"
#include "history.h"
#include "hook.h"
#include "input.h"
#include "ircaux.h"
#include "names.h"
#include "notify.h"
#include "numbers.h"
#include "output.h"
#include "parse.h"
#include "screen.h"
#include "server.h"
#include "status.h"
#include "vars.h"
#include "window.h"
#include "ircterm.h"
#include "server.h"
#include "struct.h"
#include "list.h"
#include "keys.h"
#include "bot.h"
#include "userlist.h"
#include "misc.h"
#include "newio.h"
#include "stack.h"
#include "module.h"
#include "timer.h"
#include <sys/stat.h>

#define LEFT_BRACE '{'
#define RIGHT_BRACE '}'
#define LEFT_BRACKET '['
#define RIGHT_BRACKET ']'
#define LEFT_PAREN '('
#define RIGHT_PAREN ')'
#define DOUBLE_QUOTE '"'

extern int start_time;

#ifdef WANT_DLL
BuiltInDllFunctions *dll_functions = NULL;
#endif

/* alias_illegals: characters that are illegal in alias names */
	char	alias_illegals[] = " #+-*/\\()={}[]<>!@$%^~`,?;:|'\"";

/* Used for statistics gathering */
static unsigned long alias_total_allocated = 0;
static unsigned long alias_total_bytes_allocated = 0;
static unsigned long var_cache_hits = 0;
static unsigned long var_cache_misses = 0;
static unsigned long var_cache_passes = 0;
static unsigned long cmd_cache_hits = 0;
static unsigned long cmd_cache_misses = 0;
static unsigned long cmd_cache_passes = 0;

int max_recursions = DEFAULT_MAX_RECURSIONS;

/* function_stack and function_stkptr - hold the return values from functions */
static	char	*function_stack[128] = { NULL };
static	int	function_stkptr = 0;

static	void	delete_var_alias   (char *name);
static	void	delete_cmd_alias   (char *name);
static	void	list_cmd_alias     (char *name);
static	void	list_var_alias     (char *name);
static	void	list_local_alias   (char *name);
static	char *	get_variable_with_args (char *str, char *args, int *args_flag);

/*
 * aliascmd: The user interface for /ALIAS
 */
BUILT_IN_COMMAND(aliascmd)
{
	char *name;
	char *real_name;
	void show_alias_caches(void);

	/*
	 * If no name present, list all aliases
	 */
	if (!(name = next_arg(args, &args)))
	{
		list_cmd_alias(NULL);
		return;
	}

	/*
	 * Alias can take an /s arg, which shows some data we've collected
	 */
	if (!my_strnicmp(name, "/S", 2))
	{
		say("Total aliases handled: %ld", alias_total_allocated);
		say("Total bytes allocated to aliases: %ld", alias_total_bytes_allocated);
		say("Var command hits/misses/passes [%ld/%ld/%ld]", var_cache_hits, var_cache_misses, var_cache_passes);
		say("Cmd command hits/misses/passes [%ld/%ld/%ld]", cmd_cache_hits, cmd_cache_misses, cmd_cache_passes);
		show_alias_caches();
		return;
	}

	/*
	 * Canonicalize the alias name
	 */
	real_name = remove_brackets(name, NULL, NULL);

	/*
	 * Find the argument body
	 */
	while (my_isspace(*args))
		args++;

	/*
	 * If there is no argument body, we probably want to delete alias
	 */
	if (!args || !*args)
	{
		/*
		 * If the alias name starts with a hyphen, then we are
		 * going to delete it.
		 */
		if (real_name[0] == '-')
		{
			if (real_name[1])
				delete_cmd_alias(real_name + 1);
			else
				say("You must specify an alias to be removed.");
		}

		/*
		 * Otherwise, the user wants us to list that alias
		 */
		else
			list_cmd_alias(real_name);
	}

	/*
	 * If there is an argument body, then we have to register it
	 */
	else
	{
		/*
		 * Aliases' bodies can be surrounded by a set of braces,
		 * which are stripped off.
		 */
		if (*args == LEFT_BRACE)
		{
			char	*ptr = MatchingBracket(++args, LEFT_BRACE, RIGHT_BRACE);

			if (!ptr)
				say("Unmatched brace in %s", command);
			else 
			{
				*ptr++ = 0;
				while (*ptr && my_isspace(*ptr))
					ptr++;

				if (*ptr)
					say("Junk after closing brace in %s", command);

				while (*args && my_isspace(*args))
					args++;

			}
		}

		/*
		 * Register the alias
		 */
		add_cmd_alias(real_name, args);
	}

	new_free(&real_name);
	return;
}

/*
 * User front end to the ASSIGN command
 * Syntax to add variable:    ASSIGN name text
 * Syntax to delete variable: ASSIGN -name
 */
BUILT_IN_COMMAND(assigncmd)
{
	char *real_name;
	char *name;

	/*
	 * If there are no arguments, list all the global variables
	 */
	if (!(name = next_arg(args, &args)))
	{
		list_var_alias(NULL);
		return;
	}

	/*
	 * Canonicalize the variable name
	 */
	real_name = remove_brackets(name, NULL, NULL);

	/*
	 * Find the stuff to assign to the variable
	 */
	while (my_isspace(*args))
		args++;

	/*
	 * If there is no body, then the user probably wants to delete
	 * the variable
	 */
	if (!args || !*args)
	{
		/*
	 	 * If the variable name starts with a hyphen, then we remove
		 * the variable
		 */
		if (real_name[0] == '-')
		{
			if (real_name[1])
				delete_var_alias(real_name + 1);
			else
				say("You must specify an alias to be removed.");
		}

		/*
		 * Otherwise, the user wants us to list the variable
		 */
		else
			list_var_alias(real_name);
	}

	/*
	 * Register the variable
	 */
	else
		add_var_alias(real_name, args);

	new_free(&real_name);
	return;
}

/*
 * User front end to the STUB command
 * Syntax to stub an alias to a file:	STUB ALIAS name[,name] filename(s)
 * Syntax to stub a variable to a file:	STUB ASSIGN name[,name] filename(s)
 */
BUILT_IN_COMMAND(stubcmd)
{
	int type;
	char *cmd;
	char *name;
	char *usage = "(alias|assign) <name> <file(s)>";

	/*
	 * The first argument is the type of stub to make
	 * (alias or assign)
	 */
	if (!(cmd = upper(next_arg(args, &args))))
	{
		userage(command, usage);
		return;
	}

	if (!strncmp(cmd, "ALIAS", strlen(cmd)))
		type = COMMAND_ALIAS;
	else if (!strncmp(cmd, "ASSIGN", strlen(cmd)))
		type = VAR_ALIAS;
	else
	{
		userage(command, usage);
		return;
	}

	/*
	 * The next argument is the name of the item to be stubbed.
	 * This is not optional.
	 */
	if (!(name = next_arg(args, &args)))
	{
		userage(command, usage);
		return;
	}

	/*
	 * Find the filename argument
	 */
	while (my_isspace(*args))
		args++;

	/*
	 * The rest of the argument(s) are the files to load when the
	 * item is referenced.  This is not optional.
	 */
	if (!args || !*args)
	{
		userage(command, usage);
		return;
	}

	/*
	 * Now we iterate over the item names we were given.  For each
	 * item name, seperated from the next by a comma, stub that item
	 * to the given filename(s) specified as the arguments.
	 */
	while (name && *name)
	{
		char 	*next_name;
		char	*real_name;

		if ((next_name = strchr(name, ',')))
			*next_name++ = 0;

		real_name = remove_brackets(name, NULL, NULL);
		if (type == COMMAND_ALIAS)
			add_cmd_stub_alias(real_name, args);
		else
			add_var_stub_alias(real_name, args);

		new_free(&real_name);
		name = next_name;
	}
}

BUILT_IN_COMMAND(localcmd)
{
	char *name;

	if (!(name = next_arg(args, &args)))
	{
		list_local_alias(NULL);
		return;
	}

	while (args && *args && my_isspace(*args))
		args++;

	if (!args)
		args = empty_string;

	while (name && *name)
	{
		char 	*next_name;
		char	*real_name;

		if ((next_name = strchr(name, ',')))
			*next_name++ = 0;

		real_name = remove_brackets(name, NULL, NULL);
		add_local_alias(real_name, args);
		new_free(&real_name);
		name = next_name;
	}
}

BUILT_IN_COMMAND(dumpcmd)
{
	FILE *fp;
	char *filename = NULL;
	char *expand = NULL;
	char 	*blah;

	if (!args || !*args)
	{
		if (command)
			userage("dump", helparg);
		else
		{
			destroy_aliases(COMMAND_ALIAS);
			destroy_aliases(VAR_ALIAS);
			destroy_aliases(VAR_ALIAS_LOCAL);
			flush_on_hooks();
			delete_all_timers();
		}
		return;
		
	}
	while ((blah = next_arg(args, &args)))
	{
		if (!my_strnicmp(blah,"FI",2))
		{
			malloc_sprintf(&filename, "~/%s.dump", version);
			expand = expand_twiddle(filename);
			new_free(&filename);
			if ((fp = fopen(expand, "w")) != NULL)
			{
				save_bindings(fp, 0);
				save_hooks(fp, 0);
				save_variables(fp, 0);
				save_aliases(fp, 0);
				fclose(fp);
			}
			bitchsay("Saved to ~/%s.dump", version);
			new_free(&expand);
		}
		else if (!my_strnicmp(blah,"B",1))
			init_keys_1();
		else if (!my_strnicmp(blah,"V",1))			
			destroy_aliases(VAR_ALIAS);
		else if (!my_strnicmp(blah,"ALI",3))
			destroy_aliases(COMMAND_ALIAS);
		else if (!my_strnicmp(blah,"O",1))
			flush_on_hooks();
		else if (!my_strnicmp(blah,"CH",2))
			flush_channel_stats();
		else if (!my_strnicmp(blah,"LO",2))
			destroy_aliases(VAR_ALIAS_LOCAL);
		else if (!my_strnicmp(blah, "TI", 2))
			delete_all_timers();
		else if (!my_strnicmp(blah,"ALL",3))
		{
			init_keys_1();
			destroy_aliases(COMMAND_ALIAS);
			destroy_aliases(VAR_ALIAS);
			destroy_aliases(VAR_ALIAS_LOCAL);
			flush_on_hooks();
			delete_all_timers();
		}
	}
}

/**************************** INTERMEDIATE INTERFACE *********************/
/* We define Alias here to keep it encapsulated */
/*
 * This is the description of an alias entry
 * This is an ``array_item'' structure
 */
typedef	struct	AliasItemStru
{
	char	*name;			/* name of alias */
	char	*stuff;			/* what the alias is */
	char	*stub;			/* the file its stubbed to */
	int	global;			/* set if loaded from `global' */
}	Alias;

/*
 * This is the description for a list of aliases
 * This is an ``array_set'' structure
 */
#define ALIAS_CACHE_SIZE 30

typedef struct	AliasStru
{
	Alias **list;
	int	max;
	int	max_alloc;
	Alias  *cache[ALIAS_CACHE_SIZE];
}	AliasSet;

static AliasSet var_alias = 	{ NULL, 0, 0, { NULL } };
static AliasSet cmd_alias = 	{ NULL, 0, 0, { NULL } };

static	Alias *	find_var_alias     (char *name);
static	Alias *	find_cmd_alias     (char *name, int *cnt);
static	Alias *	find_local_alias   (char *name, AliasSet **list);

/*
 * This is the ``stack frame''.  Each frame has a ``name'' which is
 * the name of the alias or on of the frame, or is NULL if the frame
 * is not an ``enclosing'' frame.  Each frame also has a ``current command''
 * that is being executed, which is used to help us when the client crashes.
 * Each stack also contains a list of local variables.
 */
typedef struct RuntimeStackStru
{
	char	*name;		/* Name of the stack */
	char 	*current;	/* Current cmd being executed */
	AliasSet alias;		/* Local variables */
	int	locked;
	int	parent;
}	RuntimeStack;

/*
 * This is the master stack frame.  Its size is saved in ``max_wind''
 * and the current frame being used is stored in ``wind_index''.
 */
static 	RuntimeStack *call_stack = NULL;
	int 	max_wind = -1;
	int 	wind_index = -1;

void show_alias_caches(void)
{
	int i;
	for (i = 0; i < ALIAS_CACHE_SIZE; i++)
	{
		if (var_alias.cache[i])
			yell("VAR cache [%d]: [%s] [%s]", i, var_alias.cache[i]->name, var_alias.cache[i]->stuff);
		else
			yell("VAR cache [%d]: empty", i);
	}

	for (i = 0; i < ALIAS_CACHE_SIZE; i++)
	{
		if (cmd_alias.cache[i])
			yell("CMD cache [%d]: [%s] [%s]", i, cmd_alias.cache[i]->name, cmd_alias.cache[i]->stuff);
		else
			yell("CMD cache [%d]: empty", i);
	}
}




Alias *make_new_Alias (char *name)
{
	Alias *tmp = (Alias *) new_malloc(sizeof(Alias));
	tmp->name = m_strdup(name);
	tmp->stuff = tmp->stub = NULL;
	alias_total_bytes_allocated += sizeof(Alias) + strlen(name);
	return tmp;
}


/*
 * add_var_alias: Add a global variable
 *
 * name -- name of the alias to create (must be canonicalized)
 * stuff -- what to have ``name'' expand to.
 *
 * If ``name'' is FUNCTION_RETURN, then it will create the local
 * return value (die die die)
 *
 * If ``name'' refers to an already created local variable, that
 * local variable is used (invisibly)
 */
void	add_var_alias	(char *name, char *stuff)
{
	char *ptr;
	Alias *tmp = NULL;
	int af;

	name = remove_brackets(name, NULL, &af);

	/*
	 * Weed out invalid variable names
	 */
	if ((ptr = sindex(name, alias_illegals)) != NULL)
		yell("ASSIGN names may not contain '%c'", *ptr);
	

	/*
	 * Weed out FUNCTION_RETURN (die die die)
	 */
	else if (!strcmp(name, "FUNCTION_RETURN"))
	{
		if (function_stack[function_stkptr])
			new_free(&function_stack[function_stkptr]);
		malloc_strcpy(&function_stack[function_stkptr], stuff);
	}

	/*
	 * Pass the buck on local variables
	 */
	else if (find_local_alias(name, NULL))
		add_local_alias(name, stuff);

	else if (stuff && *stuff)
	{
		int cnt, loc;

		/*
		 * Look to see if the given alias already exists.
		 * If it does, and the ``stuff'' to assign to it is
		 * empty, then we should remove the variable outright
		 */
		tmp = (Alias *) find_array_item ((array *)&var_alias, name, &cnt, &loc);
		if (!tmp || cnt >= 0)
		{
			tmp = make_new_Alias(name);
			add_to_array ((array *)&var_alias, (array_item *)tmp);
		}

		/*
		 * Then we fill in the interesting details
		 */
		malloc_strcpy(&(tmp->stuff), stuff);
		new_free(&tmp->stub);
		tmp->global = loading_global;

		alias_total_allocated++;
		alias_total_bytes_allocated += strlen(tmp->name) + strlen(tmp->stuff);

		/*
		 * And tell the user.
		 */
		say("Assign %s added [%s]", name, stuff);
	}
	else
		delete_var_alias(name);

	new_free(&name);
	return;
}

void	add_local_alias	(char *name, char *stuff)
{
	char *ptr;
	Alias *tmp = NULL;
	AliasSet *list = NULL;
	int af;

	name = remove_brackets(name, NULL, &af);

	/*
	 * Weed out invalid variable names
	 */
	if ((ptr = sindex(name, alias_illegals)) != NULL)
		yell("LOCAL names may not contain '%c'", *ptr);
	
	/*
	 * Weed out FUNCTION_RETURN
	 */
	else if (!strcmp(name, "FUNCTION_RETURN"))
	{
		if (function_stack[function_stkptr])
			new_free(&function_stack[function_stkptr]);
		malloc_strcpy(&function_stack[function_stkptr], stuff);
	}

	else
	{
		/*
		 * Now we see if this local variable exists anywhere
		 * within our view.  If it is, we dont care where.
		 * If it doesnt, then we add it to the current frame,
		 * where it will be reaped later.
		 */
		if (!(tmp = find_local_alias (name, &list)))
		{
			tmp = make_new_Alias(name);
			add_to_array ((array *)list, (array_item *)tmp);
		}

		/* Fill in the interesting stuff */
		malloc_strcpy(&(tmp->stuff), stuff);
		alias_total_allocated++;
		alias_total_bytes_allocated += strlen(tmp->stuff);
		say("Assign %s (local) added [%s]", name, stuff);
	}

	new_free(&name);
	return;
}

void	add_cmd_alias	(char *name, char *stuff)
{
	Alias *tmp = NULL;
	int cnt, af, loc;

	name = remove_brackets(name, NULL, &af);

	tmp = (Alias *) find_array_item ((array *)&cmd_alias, name, &cnt, &loc);
	if (!tmp || cnt >= 0)
	{
		tmp = make_new_Alias(name);
		add_to_array ((array *)&cmd_alias, (array_item *)tmp);
	}

	malloc_strcpy(&(tmp->stuff), stuff);
	new_free(&tmp->stub);
	tmp->global = loading_global;

	alias_total_allocated++;
	alias_total_bytes_allocated += strlen(tmp->stuff);
	say("Alias	%s added [%s]", name, stuff);
	new_free(&name);
	return;
}

void	add_var_stub_alias  (char *name, char *stuff)
{
	Alias *tmp = NULL;
	char *ptr;
	int af;

	name = remove_brackets(name, NULL, &af);

	if ((ptr = sindex(name, alias_illegals)) != NULL)
		yell("Assign names may not contain '%c'", *ptr);

	else if (!strcmp(name, "FUNCTION_RETURN"))
		yell("You may not stub the FUNCTION_RETURN variable.");

	else 
	{
		int cnt, loc;

		tmp = (Alias *) find_array_item ((array *)&var_alias, name, &cnt, &loc);
		if (!tmp || cnt >= 0)
		{
			tmp = make_new_Alias(name);
			add_to_array ((array *)&var_alias, (array_item *)tmp);
		}

		malloc_strcpy(&(tmp->stub), stuff);
		new_free(&tmp->stuff);
		tmp->global = loading_global;

		alias_total_allocated++;
		alias_total_bytes_allocated += strlen(tmp->stuff);
		say("Assign %s stubbed to file %s", name, stuff);
	}

	new_free(&name);
	return;
}


void	add_cmd_stub_alias  (char *name, char *stuff)
{
	Alias *tmp = NULL;
	int cnt, af;

	name = remove_brackets(name, NULL, &af);
	if (!(tmp = find_cmd_alias(name, &cnt)) || cnt >= 0)
	{
		tmp = make_new_Alias(name);
		add_to_array ((array *)&cmd_alias, (array_item *)tmp);
	}

	malloc_strcpy(&(tmp->stub), stuff);
	new_free(&tmp->stuff);
	tmp->global = loading_global;

	alias_total_allocated++;
	alias_total_bytes_allocated += strlen(tmp->stub);
	say("Assign %s stubbed to file %s", name, stuff);
	new_free(&name);
	return;
}


/************************ LOW LEVEL INTERFACE *************************/
/* XXXX */
static void unstub_alias (Alias *item);

/*
 * 'name' is expected to already be in canonical form (uppercase, dot notation)
 */
static
Alias *	find_var_alias (char *name)
{
	Alias *item = NULL;
	int cache;
	int loc;
	int cnt = 0;
	int i;

	if (!strcmp(name, "::"))
		name +=2;
		
	for (cache = 0; cache < ALIAS_CACHE_SIZE; cache++)
	{
		if (var_alias.cache[cache] && 
		    var_alias.cache[cache]->name &&
		    !strcmp(name, var_alias.cache[cache]->name))
		{
			item = var_alias.cache[cache];
			cnt = -1;
			var_cache_hits++;
			break;
		}
	}

	if (!item)
	{
		cache = ALIAS_CACHE_SIZE - 1;
		if ((item = (Alias *) find_array_item ((array *)&var_alias, name, &cnt, &loc)))
			var_cache_misses++;
		else
			var_cache_passes++;
	}

	if (cnt < 0)
	{
		for (i = cache; i > 0; i--)
			var_alias.cache[i] = var_alias.cache[i - 1];
		var_alias.cache[0] = item;

		if (item->stub)
			unstub_alias(item);

		return item;
	}

	return NULL;
}

static
Alias *	find_cmd_alias (char *name, int *cnt)
{
	Alias *item = NULL;
	int loc;
	int i;
	int cache;

	for (cache = 0; cache < ALIAS_CACHE_SIZE; cache++)
	{
		if (cmd_alias.cache[cache])
		{
			/* XXXX this is bad cause this is free()d! */
			if (!cmd_alias.cache[cache]->name)
				cmd_alias.cache[cache] = NULL;
			else if (!strcmp(name, cmd_alias.cache[cache]->name))
			{
				item = cmd_alias.cache[cache];
				*cnt = -1;
				cmd_cache_hits++;
				break;
			}
		}
	}

	if (!item)
	{
		cache = ALIAS_CACHE_SIZE - 1;
		if ((item = (Alias *) find_array_item ((array *)&cmd_alias, name, cnt, &loc)))
			cmd_cache_misses++;
		else
			cmd_cache_passes++;
	}

	if (*cnt < 0 || *cnt == 1)
	{
		for (i = cache; i > 0; i--)
			cmd_alias.cache[i] = cmd_alias.cache[i - 1];
		cmd_alias.cache[0] = item;

		if (item->stub)
			unstub_alias(item);

		return item;
	}

	return NULL;
}


static void unstub_alias (Alias *item)
{
	static int already_looking = 0;
	char *copy;

#ifdef __GNUC__
	copy = alloca(strlen(item->stub) + 1);
	strcpy(copy, item->stub);
#else
	copy = m_strdup(item->stub);
#endif

	new_free((char **)&item->stub);

	/* 
	 * If already_looking is 1, then
	 * we are un-stubbing this alias
	 * because we are loading a file
	 * that presumably it must be in.
	 * So we dont load it again (duh).
	 */
	if (already_looking)
		return;

	already_looking = 1;
	load("LOAD", copy, empty_string, NULL);
	already_looking = 0;
}


/*
 * An example will best describe the semantics:
 *
 * A local variable will be returned if and only if there is already a
 * variable that is exactly ``name'', or if there is a variable that
 * is an exact leading subset of ``name'' and that variable ends in a
 * period (a dot).
 */
static Alias *	find_local_alias (char *name, AliasSet **list)
{
	Alias *alias = NULL;
	int c = wind_index;
	char *ptr;
	int implicit = -1;

	/* No name is an error */
	if (!name)
		return NULL;

	if ((ptr = sindex(name, alias_illegals)) != NULL)
		return NULL;

	/*
	 * Search our current local variable stack, and wind our way
	 * backwards until we find a NAMED stack -- that is the enclosing
	 * alias or ON call.  If we find a variable in one of those enclosing
	 * stacks, then we use it.  If we dont, we progress.
	 *
	 * This needs to be optimized for the degenerate case, when there
	 * is no local variable available...  It will be true 99.999% of
	 * the time.
	 */
	for (c = wind_index; c >= 0; c = call_stack[c].parent)
	{
		if (x_debug & DEBUG_LOCAL_VARS)
			yell("Looking for [%s] in level [%d]", name, c);

		if (call_stack[c].alias.list)
		{
			int x;

			/* XXXX - This is bletcherous */
			for (x = 0; x < call_stack[c].alias.max; x++)
			{
				int len = strlen(call_stack[c].alias.list[x]->name);

				if (streq(call_stack[c].alias.list[x]->name, name) == len)
				{
					if (call_stack[c].alias.list[x]->name[len-1] == '.')
						implicit = c;
					else if (strlen(name) == len)
					{
						alias = call_stack[c].alias.list[x];
						break;
					}
				}
				else
				{
					if (my_stricmp(call_stack[c].alias.list[x]->name, name) > 0)
						continue;
				}
			}

			if (!alias && implicit >= 0)
			{
				alias = make_new_Alias(name);
				add_to_array ((array *)&call_stack[implicit].alias, (array_item *)alias);
			}
		}

		if (alias)
		{
			if (x_debug & DEBUG_LOCAL_VARS) 
				yell("I found [%s] in level [%d] (%s)", name, c, alias->stuff);
			break;
		}

		if (*call_stack[c].name || call_stack[c].parent == -1)
		{
			if (x_debug & DEBUG_LOCAL_VARS) 
				yell("I didnt find [%s], stopped at level [%d]", name, c);
			break;
		}
	}

	if (alias)
	{
		if (list)
			*list = &call_stack[c].alias;
		return alias;
	}
	else if (list)
		*list = &call_stack[wind_index].alias;

	return NULL;
}





static void	delete_var_alias (char *name)
{
	Alias *item;
	int i;

	upper(name);
	if ((item = (Alias *)remove_from_array ((array *)&var_alias, name)))
	{
		for (i = 0; i < ALIAS_CACHE_SIZE; i++)
		{
			if (var_alias.cache[i] == item)
				var_alias.cache[i] = NULL;
		}

		new_free(&(item->name));
		new_free(&(item->stuff));
		new_free(&(item->stub));
		new_free((char **)&item);
		say("Assign %s removed", name);
	}
	else
		say("No such assign: %s", name);
}

static void	delete_cmd_alias (char *name)
{
	Alias *item;
	int i;

	upper(name);
	if ((item = (Alias *)remove_from_array ((array *)&cmd_alias, name)))
	{
		for (i = 0; i < ALIAS_CACHE_SIZE; i++)
		{
			if (cmd_alias.cache[i] == item)
				cmd_alias.cache[i] = NULL;
		}

		new_free(&(item->name));
		new_free(&(item->stuff));
		new_free(&(item->stub));
		new_free((char **)&item);
		say("Alias	%s removed", name);
	}
	else
		say("No such alias: %s", name);
}





static void	list_local_alias (char *name)
{
	say("Visible Local Assigns:");
	if (name)
		upper(name);

/*	say("I didnt finish this function yet.  Go yell at me.");*/
	/* FINISH THIS */
}

/*
 * This function is strictly O(N).  Its possible to address this.
 */
static void	list_var_alias (char *name)
{
	int	len;
	int	DotLoc,
		LastDotLoc = 0;
	char	*LastStructName = NULL;
	int	cnt;
	char	*s;

	say("Assigns:");

	if (name)
	{
		upper(name);
		len = strlen(name);
	}
	else
		len = 0;

	for (cnt = 0; cnt < var_alias.max; cnt++)
	{
		if (!name || !strncmp(var_alias.list[cnt]->name, name, len))
		{
			if ((s = strchr(var_alias.list[cnt]->name + len, '.')))
			{
				DotLoc = s - var_alias.list[cnt]->name;
				if (!LastStructName || (DotLoc != LastDotLoc) || strncmp(var_alias.list[cnt]->name, LastStructName, DotLoc))
				{
					say("\t%*.*s\t<Structure>", DotLoc, DotLoc, var_alias.list[cnt]->name);
					LastStructName = var_alias.list[cnt]->name;
					LastDotLoc = DotLoc;
				}
			}
			else
			{
				if (var_alias.list[cnt]->stub)
					say("\t%s STUBBED TO %s", var_alias.list[cnt]->name, var_alias.list[cnt]->stub);
				else
					say("\t%s\t%s", var_alias.list[cnt]->name, var_alias.list[cnt]->stuff);
			}
		}
	}
}

/*
 * This function is strictly O(N).  Its possible to address this.
 */
static void	list_cmd_alias (char *name)
{
	int	len;
	int	DotLoc,
		LastDotLoc = 0;
	char	*LastStructName = NULL;
	int	cnt;
	char	*s;

	say("Aliases:");

	if (name)
	{
		upper(name);
		len = strlen(name);
	}
	else
		len = 0;

	for (cnt = 0; cnt < cmd_alias.max; cnt++)
	{
		if (!name || !strncmp(cmd_alias.list[cnt]->name, name, len))
		{
			if ((s = strchr(cmd_alias.list[cnt]->name + len, '.')))
			{
				DotLoc = s - cmd_alias.list[cnt]->name;
				if (!LastStructName || (DotLoc != LastDotLoc) || strncmp(cmd_alias.list[cnt]->name, LastStructName, DotLoc))
				{
					say("\t%*.*s\t<Structure>", DotLoc, DotLoc, cmd_alias.list[cnt]->name);
					LastStructName = cmd_alias.list[cnt]->name;
					LastDotLoc = DotLoc;
				}
			}
			else
			{
				if (cmd_alias.list[cnt]->stub)
					say("\t%s STUBBED TO %s", cmd_alias.list[cnt]->name, cmd_alias.list[cnt]->stub);
				else
					say("\t%s\t%s", cmd_alias.list[cnt]->name, cmd_alias.list[cnt]->stuff);
			}
		}
	}
}


/************************* DIRECT VARIABLE EXPANSION ************************/
/*
 * get_variable: This simply looks up the given str.  It first checks to see
 * if its a user variable and returns it if so.  If not, it checks to see if
 * it's an IRC variable and returns it if so.  If not, it checks to see if
 * its and environment variable and returns it if so.  If not, it returns
 * null.  It mallocs the returned string 
 */
char 	*get_variable 	(char *str)
{
	int af = 0;
	return get_variable_with_args(str, NULL, &af);
}


static  char	*get_variable_with_args (char *str, char *args, int *args_flag)
{
	Alias	*alias = NULL;
	char	*ret = NULL;
	char	*name = NULL;

	name = remove_brackets(str, args, args_flag);

	if ((alias = find_local_alias(name, NULL)))
		ret = alias->stuff;
	else if ((alias = find_var_alias(name)) != NULL)
		ret = alias->stuff;
	else if ((strlen(str) == 1) && (ret = built_in_alias(*str)))
	{
		new_free(&name);
		return ret;
	}
	else if ((ret = make_string_var(str)))
		;
	else if ((ret = make_fstring_var(str)))
		;
	else
		ret = getenv(str);

	new_free(&name);
	return m_strdup(ret);
}

char *	get_cmd_alias (char *name, int *howmany, char **complete_name)
{
	Alias *item;

	if ((item = find_cmd_alias(name, howmany)))
	{
		if (complete_name)
			malloc_strcpy(complete_name, item->name);
		return item->stuff;
	}
	return NULL;
}

/*
 * This function is strictly O(N).  This should probably be addressed.
 */
char **	glob_cmd_alias (char *name, int *howmany)
{
	int	cnt;
	int	cmp;
	int 	len;
	char 	**matches = NULL;
	int 	matches_size = 5;

	len = strlen(name);
	*howmany = 0;
	matches = RESIZE(matches, char *, matches_size);

	for (cnt = 0; cnt < cmd_alias.max; cnt++)
	{
		if (!(cmp = strncmp(name, cmd_alias.list[cnt]->name, len)))
		{
			if (strchr(cmd_alias.list[cnt]->name + len, '.'))
				continue;

			matches[*howmany] = m_strdup(cmd_alias.list[cnt]->name);
			*howmany +=1;
			if (*howmany == matches_size)
			{
				matches_size += 5;
				RESIZE(matches, char *, matches_size);
			}
		}
		else if (cmp < 0)
			break;
	}

	if (*howmany == 0)
		new_free((char **)&matches);

	return matches;
}

/*
 * This function is strictly O(N).  This should probably be addressed.
 */
char **	get_subarray_elements (char *root, int *howmany, int type)
{
	AliasSet *as;		/* XXXX */

	int len, len2;
	int cnt;
	int cmp = 0;
	char **matches = NULL;
	int matches_size = 5;
	size_t end;
	char *last = NULL;
	
	if (type == COMMAND_ALIAS)
		as = &cmd_alias;
	else
		as = &var_alias;

	len = strlen(root);
	*howmany = 0;
	matches = RESIZE(matches, char *, matches_size);
	for (cnt = 0; cnt < as->max; cnt++)
	{
		len2 = strlen(as->list[cnt]->name);
		if ( (len < len2) &&
		     ((cmp = streq(root, as->list[cnt]->name)) == len))
		{
			if (as->list[cnt]->name[cmp] == '.')
			{
				end = strcspn(as->list[cnt]->name + cmp + 1, ".");
				if (last && !my_strnicmp(as->list[cnt]->name, last, cmp + 1 + end))
					continue;
				matches[*howmany] = m_strndup(as->list[cnt]->name, cmp + 1 + end);
				last = matches[*howmany];
				*howmany += 1;
				if (*howmany == matches_size)
				{
					matches_size += 5;
					RESIZE(matches, char *, matches_size);
				}
			}
		}
	}

	if (*howmany)
		matches[*howmany] = NULL;
	else
		new_free((char **)&matches);

	return matches;
}

/************************************************************************/
/*
 * call_user_function: Executes a user alias (by way of parse_command.
 * The actual function ends up being routed through execute_alias (below)
 * and we just keep track of the retval and stuff.  I dont know that anyone
 * depends on command completion with functions, so we can save a lot of
 * CPU time by just calling execute_alias() directly.
 */
char 	*call_user_function	(char *alias_name, char *args)
{
	char *result;
	char *sub_buffer;
	int cnt;

	function_stack[++function_stkptr] = NULL;

	sub_buffer = get_cmd_alias(alias_name, &cnt, NULL);
	if (cnt < 0)
		parse_line(alias_name, sub_buffer, args, 0, 1); /* XXX why 1? */

	if (!(result = function_stack[function_stkptr]))
		result = m_strdup(empty_string);

	function_stack[function_stkptr--] = NULL;
	return result;
}

/*
 * save_aliases: This will write all of the aliases to the FILE pointer fp in
 * such a way that they can be read back in using LOAD or the -l switch 
 */
void 	save_aliases	(FILE *fp, int do_all)
{
	int cnt = 0;

	for (cnt = 0; cnt < var_alias.max; cnt++)
	{
		if (!var_alias.list[cnt]->global || do_all)
		{
			if (var_alias.list[cnt]->stub)
				fprintf(fp, "STUB ");
			fprintf(fp, "ASSIGN %s %s\n", var_alias.list[cnt]->name, var_alias.list[cnt]->stuff);
		}
	}
	for (cnt = 0; cnt < cmd_alias.max; cnt++)
	{
		if (!cmd_alias.list[cnt]->global || do_all)
		{
			if (cmd_alias.list[cnt]->stub)
				fprintf(fp, "STUB ");
			fprintf(fp, "ALIAS %s %s\n", cmd_alias.list[cnt]->name, cmd_alias.list[cnt]->stuff);
		}
	}	
}

void 	destroy_aliases (int type)
{
	int cnt = 0;
	AliasSet *array = NULL;

	if (type == COMMAND_ALIAS)
		array = &cmd_alias;
	else if (type == VAR_ALIAS)
		array = &var_alias;
	else if (type == VAR_ALIAS_LOCAL)
		array = &call_stack[wind_index].alias;

	for (cnt = 0; cnt < array->max; cnt++)
	{
		new_free((void **)&(array->list[cnt]->stuff));
		new_free((void **)&(array->list[cnt]->name));
		new_free((void **)&(array->list[cnt]->stub));
		new_free((void **)&(array->list[cnt]));
	}
	new_free((void **)&(array->list));
	array->max = array->max_alloc = 0;
	for (cnt = 0; cnt < ALIAS_CACHE_SIZE; cnt++)
		array->cache[cnt] = NULL;
}

/******************* RUNTIME STACK SUPPORT **********************************/

void 	make_local_stack 	(char *name)
{
	wind_index++;

	if (wind_index >= max_wind)
	{
		int tmp_wind = wind_index;

		if (max_wind == -1)
			max_wind = max_recursions;
		else
			max_wind <<= 1;

		RESIZE(call_stack, RuntimeStack, max_wind);
		for (; wind_index < max_wind; wind_index++)
		{
			call_stack[wind_index].alias.max = 0;
			call_stack[wind_index].alias.max_alloc = 0;
			call_stack[wind_index].alias.list = NULL;
			call_stack[wind_index].current = NULL;
			call_stack[wind_index].name = NULL;
			call_stack[wind_index].parent = -1;
		}
		wind_index = tmp_wind;
	}

	/* Just in case... */
	destroy_local_stack();
	wind_index++;		/* XXXX - chicanery */

	if (name)
	{
		call_stack[wind_index].name = name;
		call_stack[wind_index].parent = -1;
	}
	else
	{
		call_stack[wind_index].name = empty_string;
		call_stack[wind_index].parent = wind_index - 1;
	}
	call_stack[wind_index].locked =  0;
}

int	find_locked_stack_frame	(void)
{
	int i;
	for (i = 0; i < wind_index; i++)
		if (call_stack[i].locked)
 			return i;

	return -1;
}

void	bless_local_stack	(void)
{
	call_stack[wind_index].name = empty_string;
	call_stack[wind_index].parent = find_locked_stack_frame();
}

void 	destroy_local_stack 	(void)
{
	/*
	 * We clean up as best we can here...
	 */
	if (call_stack[wind_index].alias.list)
		destroy_aliases(VAR_ALIAS_LOCAL);
	if (call_stack[wind_index].current)
		call_stack[wind_index].current = 0;
	if (call_stack[wind_index].name)
		call_stack[wind_index].name = 0;

	wind_index--;
}

void 	set_current_command 	(char *line)
{
	call_stack[wind_index].current = line;
}

void 	unset_current_command 	(void)
{
	call_stack[wind_index].current = NULL;
}

void	lock_stack_frame 	(void)
{
	call_stack[wind_index].locked = 1;
}

void	unlock_stack_frame	(void)
{
	int lock = find_locked_stack_frame();
	if (lock >= 0)
		call_stack[lock].locked = 0;
}
  

void 	dump_call_stack 	(void)
{
	int my_wind_index = wind_index;
	if (my_wind_index >= 0)
	{
		say("Call stack");
		while (my_wind_index--)
			say("[%3d] %s", my_wind_index, call_stack[my_wind_index].current);
		say("End of call stack");
	}
}

void 	panic_dump_call_stack 	(void)
{
	int my_wind_index = wind_index;
	printf("Call stack\n");
	while (my_wind_index--)
		printf("[%3d] %s\n", my_wind_index, call_stack[my_wind_index].current);
	printf("End of call stack\n");
}


/*
 * You may NOT call this unless youre about to exit.
 * If you do (call this when youre not about to exit), and you do it
 * very often, max_wind will get absurdly large.  So dont do it.
 *
 * XXXX - this doesnt clean up everything -- but do i care?
 */
void 	destroy_call_stack 	(void)
{
	wind_index = 0;
	new_free((char **)&call_stack);
}


#include "expr.c"

/****************************** ALIASCTL ************************************/
#define EMPTY empty_string
#define RETURN_EMPTY return m_strdup(EMPTY)
#define RETURN_IF_EMPTY(x) if (empty( x )) RETURN_EMPTY
#define GET_INT_ARG(x, y) {RETURN_IF_EMPTY(y); x = my_atol(safe_new_next_arg(y, &y));}
#define GET_FLOAT_ARG(x, y) {RETURN_IF_EMPTY(y); x = atof(safe_new_next_arg(y, &y));}
#define GET_STR_ARG(x, y) {RETURN_IF_EMPTY(y); x = new_next_arg(y, &y);RETURN_IF_EMPTY(x);}
#define RETURN_STR(x) return m_strdup(x ? x : EMPTY)
#define RETURN_INT(x) return m_strdup(ltoa(x));

/* Used by function_aliasctl */
/* MUST BE FIXED */
char 	*aliasctl 	(char *input)
{
	int list = -1;
	char *listc;
	enum { GET, SET, MATCH } op;

	GET_STR_ARG(listc, input);
	if (!my_strnicmp(listc, "AS", 2))
		list = VAR_ALIAS;
	else if (!my_strnicmp(listc, "AL", 2))
		list = COMMAND_ALIAS;
	else if (!my_strnicmp(listc, "LO", 2))
		list = VAR_ALIAS_LOCAL;
	else
		RETURN_EMPTY;

	GET_STR_ARG(listc, input);
	if (!my_strnicmp(listc, "G", 1))
		op = GET;
	else if (!my_strnicmp(listc, "S", 1))
		op = SET;
	else if (!my_strnicmp(listc, "M", 1))
		op = MATCH;
	else
		RETURN_EMPTY;

	GET_STR_ARG(listc, input);

	switch (op)
	{
		case (GET) :
		{
			Alias *alias = NULL;
			AliasSet *a_list;
			int dummy;

			upper(listc);
			if (list == VAR_ALIAS_LOCAL)
				alias = find_local_alias(listc, &a_list);
			else if (list == VAR_ALIAS)
				alias = find_var_alias(listc);
			else
				alias = find_cmd_alias(listc, &dummy);

			if (alias)
				RETURN_STR(alias->stuff);
			else
				RETURN_EMPTY;
		}
		case (SET) :
		{
			upper(listc);
			if (list == VAR_ALIAS_LOCAL)
				add_local_alias(listc, input);
			else if (list == VAR_ALIAS)
				add_var_alias(listc, input);
			else
				add_cmd_alias(listc, input);

			RETURN_INT(1)
		}
		case (MATCH) :
		{
			char **mlist = NULL;
			char *mylist = NULL;
			int num = 0, ctr;

			if (!my_stricmp(listc, "*"))
				listc = empty_string;

			upper(listc);

			if (list == COMMAND_ALIAS)
				mlist = glob_cmd_alias(listc, &num);
			else
/* XXXXXXXXXXX */
				yell("aliasctl: MATCH isnt supported on that yet.");

			for (ctr = 0; ctr < num; ctr++)
			{
				m_s3cat(&mylist, space, mlist[ctr]);
				new_free((char **)&mlist[ctr]);
			}
			new_free((char **)&mlist);
			if (mylist)
				return mylist;
			RETURN_EMPTY;
		}
		default :
			yell("aliasctl: Error");
			RETURN_EMPTY;
	}
	RETURN_EMPTY;
}

/*************************** stacks **************************************/
typedef	struct	aliasstacklist
{
	int	which;
	char	*name;
	Alias	*list;
	struct aliasstacklist *next;
}	AliasStack;

static  AliasStack *	alias_stack = NULL;
static	AliasStack *	assign_stack = NULL;

void	do_stack_alias (int type, char *args, int which)
{
	char		*name;
	AliasStack	*aptr, **aptrptr;
	Alias		*alptr;
	int		cnt;
	int 		my_which = 0;
	
	if (which == STACK_DO_ALIAS)
	{
		name = "ALIAS";
		aptrptr = &alias_stack;
		my_which = COMMAND_ALIAS;
	}
	else
	{
		name = "ASSIGN";
		aptrptr = &assign_stack;
		my_which = VAR_ALIAS;
	}

	if (!*aptrptr && (type == STACK_POP || type == STACK_LIST))
	{
		say("%s stack is empty!", name);
		return;
	}

	if (type == STACK_PUSH)
	{
		if (which == STACK_DO_ALIAS)
		{
			if ((alptr = find_var_alias(args)))
				delete_var_alias(args);
		}
		else
		{
			if ((alptr = find_cmd_alias(args, &cnt)))
				delete_cmd_alias(args);
		}

		aptr = (AliasStack *)new_malloc(sizeof(AliasStack));
		aptr->list = alptr;
		aptr->name = m_strdup(args);
		aptr->next = aptrptr ? *aptrptr : NULL;
		*aptrptr = aptr;
		return;
	}

	if (type == STACK_POP)
	{
		AliasStack *prev = NULL;

		for (aptr = *aptrptr; aptr; prev = aptr, aptr = aptr->next)
		{
			/* have we found it on the stack? */
			if (!my_stricmp(args, aptr->name))
			{
				/* remove it from the list */
				if (prev == NULL)
					*aptrptr = aptr->next;
				else
					prev->next = aptr->next;

				/* throw away anything we already have */
				delete_cmd_alias(args);

				/* put the new one in. */
				if (aptr->list)
				{
					if (which == STACK_DO_ALIAS)
						add_to_array((array *)&cmd_alias, (array_item *)(aptr->list));
					else
						add_to_array((array *)&var_alias, (array_item *)(aptr->list));
				}

				/* free it */
				new_free((char **)&aptr->name);
				new_free((char **)&aptr);
				return;
			}
		}
		say("%s is not on the %s stack!", args, name);
		return;
	}
	if (STACK_LIST == type)
	{
		AliasStack	*tmp;

		say("%s STACK LIST", name);
		for (tmp = *aptrptr; tmp; tmp = tmp->next)
		{
			if (!tmp->list)
				say("\t%s\t<Placeholder>", tmp->name);

			else if (tmp->list->stub)
				say("\t%s STUBBED TO %s", tmp->name, tmp->list->stub);

			else
				say("\t%s\t%s", tmp->name, tmp->list->stuff);
		}
		return;
	}
	say("Unknown STACK type ??");
}



