/*
 * hook.c: Does those naughty hook functions. 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */
#include <stdio.h>
#include "irc.h"
#include "hook.h"
#include "vars.h"
#include "ircaux.h"
#include "alias.h"
#include "list.h"
#include "window.h"
#include "server.h"
#include "output.h"

#define SILENT 0
#define QUIET 1
#define NORMAL 2
#define NOISY 3

/*
 * The various ON levels: SILENT means the DISPLAY will be OFF and it will
 * suppress the default action of the event, QUIET means the display will be
 * OFF but the default action will still take place, NORMAL means you will be
 * notified when an action takes place and the default action still occurs,
 * NOISY means you are notified when an action occur plus you see the action
 * in the display and the default actions still occurs 
 */
static	char	*noise_level[] = { "SILENT", "QUIET", "NORMAL", "NOISY" };

#define	HS_NOGENERIC	0x1000

/* Hook: The structure of the entries of the hook functions lists */
typedef struct	hook_stru
{
	struct	hook_stru *next;	/* pointer to next element in list */
	char	*nick;			/* The Nickname */
	int	not;			/* If true, this entry should be
					 * ignored when matched, otherwise it
					 * is a normal entry */
	int	noisy;			/* flag indicating how much output
					 * should be given */
	int	server;			/* the server in which this hook
					 * applies. (-1 if none). If bit 0x1000
					 * is set, then no other hooks are
					 * tried in the given server if all the
					 * server specific ones fail
					 */
	int	sernum;			/* The serial number for this hook. This
					 * is used for hooks which will be
					 * concurrent with others of the same
					 * pattern. The default is 0, which
					 * means, of course, no special
					 * behaviour. If any 1 hook suppresses
					 * the * default output, output will be
					 * suppressed.
					 */
	char	*stuff;			/* The this that gets done */
}	Hook;

/* HookFunc: A little structure to keep track of the various hook functions */
typedef struct
{
	char	*name;			/* name of the function */
	Hook	*list;			/* pointer to head of the list for this
					 * function */
	int	params;			/* number of parameters expected */
	int	mark;
	unsigned flags;
}	HookFunc;

#define	HF_LOOKONLY  0x0001
#define HF_NORECURSE 0x0002

extern	int	load_depth;

int	in_on_who = 0;

/*
 * NumericList: a special list type to dynamically handle numeric hook
 * requests 
 */
typedef struct numericlist_stru
{
	struct	numericlist_stru *next;
	char	*name;
	Hook	*list;
}	NumericList;

static	NumericList *numeric_list = null(NumericList *);
/* hook_functions: the list of all hook functions available */
static	HookFunc hook_functions[] =
{
	{ "ACTION",		null(Hook *),	3,	0,	0 },
	{ "CHANNEL_NICK",	null(Hook *),	3,	0,	0 },
	{ "CHANNEL_SIGNOFF",	null(Hook *),	3,	0,	0 },
	{ "CONNECT",		null(Hook *),	1,	0,	0 },
	{ "CTCP",		null(Hook *),	4,	0,	0 },
	{ "CTCP_REPLY",		null(Hook *),	3,	0,	0 },
	{ "DCC_CHAT",		null(Hook *),	2,	0,	0 },
	{ "DCC_RAW",		null(Hook *),	3,	0,	0 },
	{ "DISCONNECT",		null(Hook *),	1,	0,	0 },
	{ "EXEC",		null(Hook *),	2,	0,	0 },
	{ "EXEC_ERRORS",	null(Hook *),	2,	0,	0 },
	{ "EXEC_EXIT",		null(Hook *),	3,	0,	0 },
	{ "EXEC_PROMPT",	null(Hook *),	2,	0,	0 },
	{ "FLOOD",		null(Hook *),	3,	0,	0 },
	{ "HELP",		null(Hook *),	2,	0,	0 },
	{ "HOOK",		null(Hook *),	1,	0,	0 },
	{ "IDLE",		null(Hook *),	1,	0,	0 },
	{ "INPUT",		null(Hook *),	1,	0,	0 },
	{ "INVITE",		null(Hook *),	2,	0,	0 },
	{ "JOIN",		null(Hook *),	2,	0,	0 },
	{ "LEAVE",		null(Hook *),	2,	0,	0 },
	{ "LIST",		null(Hook *),	3,	0,	HF_LOOKONLY },
	{ "MAIL",		null(Hook *),	2,	0,	0 },
	{ "MODE",		null(Hook *),	3,	0,	0 },
	{ "MSG",		null(Hook *),	2,	0,	0 },
	{ "MSG_GROUP",		null(Hook *),	3,	0,	0 },
	{ "NAMES",		null(Hook *),	2,	0,	HF_LOOKONLY },
	{ "NICKNAME",		null(Hook *),	2,	0,	0 },
	{ "NOTE",		null(Hook *),	10,	0,	0 },
	{ "NOTICE",		null(Hook *),	2,	0,	0 },
	{ "NOTIFY_SIGNOFF",	null(Hook *),	1,	0,	0 },
	{ "NOTIFY_SIGNON",	null(Hook *),	1,	0,	0 },
	{ "PUBLIC",		null(Hook *),	3,	0,	0 },
	{ "PUBLIC_MSG",		null(Hook *),	3,	0,	0 },
	{ "PUBLIC_NOTICE",	null(Hook *),	3,	0,	0 },
	{ "PUBLIC_OTHER",	null(Hook *),	3,	0,	0 },
	{ "RAW_IRC",		null(Hook *),	1,	0,	0 },
	{ "SEND_ACTION",	null(Hook *),	2,	0,	0 },
	{ "SEND_DCC_CHAT",	null(Hook *),	2,	0,	0 },
	{ "SEND_MSG",		null(Hook *),	2,	0,	0 },
	{ "SEND_NOTICE",	null(Hook *),	2,	0,	0 },
	{ "SEND_PUBLIC",	null(Hook *),	2,	0,	0 },
	{ "SEND_TALK",		null(Hook *),	2,	0,	0 },
	{ "SERVER_NOTICE",	null(Hook *),	1,	0,	0 },
	{ "SIGNOFF",		null(Hook *),	1,	0,	0 },
	{ "TALK",		null(Hook *),	2,	0,	0 },
	{ "TIMER",		null(Hook *),	1,	0,	0 },
	{ "TOPIC",		null(Hook *),	2,	0,	0 },
	{ "WALL",		null(Hook *),	2,	0,	HF_LOOKONLY },
	{ "WALLOP",		null(Hook *),	3,	0,	HF_LOOKONLY },
	{ "WHO",		null(Hook *),	6,	0,	HF_LOOKONLY },
	{ "WIDELIST",		null(Hook *),	1,	0,	HF_LOOKONLY },
	{ "WINDOW",		null(Hook *),	2,	0,	HF_NORECURSE },
	{ "WINDOW_KILL",	null(Hook *),	1,	0,	0 },
#ifdef ON_KICK
	{ "KICK",		null(Hook *),	3,	0,	HF_LOOKONLY }
#endif /* ON_KICK */
};

int	BelongsToServer(Item)
Hook	*Item;
{
	if (Item->server == -1 || from_server ==
			(Item->server & ~HS_NOGENERIC))
		return 1;
	else
		return 0;
}

char	*fill_it_out(str, params)
char	*str;
int	params;
{
	char	buffer[BIG_BUFFER_SIZE + 1];
	char	*arg,
		*free_ptr = null(char *),
		*ptr;
	int	i = 0;

	malloc_strcpy(&free_ptr, str);
	ptr = free_ptr;
	*buffer = null(char);
	while (arg = next_arg(ptr, &ptr))
	{
		if (*buffer)
			strmcat(buffer, " ", BIG_BUFFER_SIZE);
		strmcat(buffer, arg, BIG_BUFFER_SIZE);
		if (++i == params)
			break;
	}
	for (; i < params; i++)
		strmcat(buffer, (i < params-1) ? " %" : " *", BIG_BUFFER_SIZE);
	if (*ptr)
	{
		strmcat(buffer, " ", BIG_BUFFER_SIZE);
		strmcat(buffer, ptr, BIG_BUFFER_SIZE);
	}
	malloc_strcpy(&free_ptr, buffer);
	return (free_ptr);
}


/*
 * A variety of comparison functions used by the hook routines follow.
 */

struct	CmpInfoStruc
{
	int	ServerRequired;
	int	SkipSerialNum;
	int	SerialNumber;
	int	Flags;
}	cmp_info;

#define	CIF_NOSERNUM	0x0001
#define	CIF_SKIP	0x0002

void	setup_struct(ServReq, SkipSer, SerNum, flags)
int	ServReq;
int	SkipSer;
int	SerNum;
int	flags;
{
	cmp_info.ServerRequired = ServReq;
	cmp_info.SkipSerialNum = SkipSer;
	cmp_info.SerialNumber = SerNum;
	cmp_info.Flags = flags;
}

static	int  Execute_Check(Item, Name)
Hook	*Item;
char	*Name;
{
	if ((cmp_info.Flags & CIF_SKIP) &&
			Item->sernum <= cmp_info.SkipSerialNum)
		return 0;
	if (cmp_info.Flags & CIF_NOSERNUM)
	{
		cmp_info.SerialNumber = Item->sernum;
		cmp_info.Flags &= ~CIF_NOSERNUM;
	}
	else if (Item->sernum != cmp_info.SerialNumber)
		return 0;
	if (Item->sernum != cmp_info.SerialNumber)
		return 0;
	if (cmp_info.ServerRequired != -1 &&
		cmp_info.ServerRequired != (Item->server & ~HS_NOGENERIC))
		return 0;
	return wild_match(Item->nick, Name);
}

static	int	Add_Remove_Check(Item, Name)
Hook	*Item;
char	*Name;
{
	int	comp;

	if (cmp_info.SerialNumber != Item->sernum)
		return (Item->sernum > cmp_info.SerialNumber) ? 1 : -1;
	if (comp = stricmp(Item->nick, Name))
		return comp;
	if (Item->server != cmp_info.ServerRequired)
		return (Item->server > cmp_info.ServerRequired) ? 1 : -1;
	return 0;
}

static	void	add_numeric_hook(numeric, nick, stuff, noisy, not, server,
				 sernum)
int	numeric;
char	*nick,
	*stuff;
int	noisy,
	not;
int	server,
	sernum;
{
	NumericList *entry;
	Hook	*new;
	char	buf[4];

	sprintf(buf, "%3.3u", numeric);
	if ((entry = (NumericList *) find_in_list(&numeric_list, buf, 0)) ==
			null(NumericList *))
	{
		entry = (NumericList *) new_malloc(sizeof(NumericList));
		entry->name = null(char *);
		entry->list = null(Hook *);
		malloc_strcpy(&(entry->name), buf);
		add_to_list(&numeric_list, entry);
	}

	setup_struct((server==-1) ? -1 : (server & ~HS_NOGENERIC),
	    sernum-1, sernum, 0);
	if (new = (Hook *) remove_from_list_ext(&(entry->list), nick,
		Add_Remove_Check))
	{
		new->not = 1;
		new_free(&(new->nick));
		new_free(&(new->stuff));
		wait_new_free(&new);
	}
	new = (Hook *) new_malloc(sizeof(Hook));
	new->nick = null(char *);
	new->noisy = noisy;
	new->server = server;
	new->sernum = sernum;
	new->not = not;
	new->stuff = null(char *);
	malloc_strcpy(&new->nick, nick);
	malloc_strcpy(&new->stuff, stuff);
	upper(new->nick);
	add_to_list_ext(&(entry->list), new, Add_Remove_Check);
}

/*
 * add_hook: Given an index into the hook_functions array, this adds a new
 * entry to the list as specified by the rest of the parameters.  The new
 * entry is added in alphabetical order (by nick). 
 */
static	void	add_hook(which, nick, stuff, noisy, not, server, sernum)
int	which;
char	*nick,
	*stuff;
int	noisy,
	not;
int	server,
	sernum;
{
	Hook	*new;

	if (which < 0)
	{
		add_numeric_hook(-which, nick, stuff, noisy, not, server,
			sernum);
		return;
	}
	setup_struct((server == -1) ? -1 : (server & ~HS_NOGENERIC),
	    sernum-1, sernum, 0);
	if (new = (Hook *) remove_from_list_ext(&(hook_functions[which].list),
			nick, Add_Remove_Check))
	{
		new->not = 1;
		new_free(&(new->nick));
		new_free(&(new->stuff));
		wait_new_free(&new);
	}
	new = (Hook *) new_malloc(sizeof(Hook));
	new->nick = null(char *);
	new->noisy = noisy;
	new->server = server;
	new->sernum = sernum;
	new->not = not;
	new->stuff = null(char *);
	malloc_strcpy(&new->nick, nick);
	malloc_strcpy(&new->stuff, stuff);
	upper(new->nick);
	add_to_list_ext(&(hook_functions[which].list), new, Add_Remove_Check);
}

/* show_hook shows a single hook */
static	void	show_hook(list, name)
Hook	*list;
char	*name;
{
	if (list->server != -1)
		say("On %s from \"%s\" do %s [%s] <%d> (Server %d)%s",
		    name, list->nick,
		    (list->not ? "nothing" : list->stuff),
		    noise_level[list->noisy], list->sernum,
		    list->server&~HS_NOGENERIC,
		    (list->server&HS_NOGENERIC) ? " Exclusive" : empty_string);
	else
		say("On %s from \"%s\" do %s [%s] <%d>",
		    name, list->nick,
		    (list->not ? "nothing" : list->stuff),
		    noise_level[list->noisy],
		    list->sernum);
}

/*
 * show_numeric_list: If numeric is 0, then all numeric lists are displayed.
 * If numeric is non-zero, then that particular list is displayed.  The total
 * number of entries displayed is returned 
 */
static	int show_numeric_list(numeric)
int	numeric;
{
	NumericList *tmp;
	Hook	*list;
	char	buf[4];
	int	cnt = 0;

	if (numeric)
	{
		sprintf(buf, "%3.3u", numeric);
		if (tmp = (NumericList *) find_in_list(&numeric_list, buf, 0))
		{
			for (list = tmp->list; list; list = list->next, cnt++)
				show_hook(list, tmp->name);
		}
	}
	else
	{
		for (tmp = numeric_list; tmp; tmp = tmp->next)
		{
			for (list = tmp->list; list; list = list->next, cnt++)
				show_hook(list, tmp->name);
		}
	}
	return (cnt);
}

/*
 * show_list: Displays the contents of the list specified by the index into
 * the hook_functions array.  This function returns the number of entries in
 * the list displayed 
 */
static	int show_list(which)
int	which;
{
	Hook	*list;
	int	cnt = 0;

	/* Less garbage when issueing /on without args. (lynx) */
	for (list = hook_functions[which].list; list; list = list->next, cnt++)
		show_hook(list, hook_functions[which].name);
	return (cnt);
}

/*
 * do_hook: This is what gets called whenever a MSG, INVITES, WALL, (you get
 * the idea) occurs.  The nick is looked up in the appropriate list. If a
 * match is found, the stuff field from that entry in the list is treated as
 * if it were a command. First it gets expanded as though it were an alias
 * (with the args parameter used as the arguments to the alias).  After it
 * gets expanded, it gets parsed as a command.  This will return as its value
 * the value of the noisy field of the found entry, or -1 if not found. 
 */
/*VARARGS*/
int	do_hook(which, format, arg1, arg2, arg3, arg4, arg5, arg6)
int	which;
char	*format;
char	*arg1,
	*arg2,
	*arg3,
	*arg4,
	*arg5,
	*arg6;
{
	Hook	*tmp, **list;
	char	buffer[BIG_BUFFER_SIZE + 1],
		*name = null(char *);
	int	RetVal;
	unsigned int	display;
	int	i,
		old_in_on_who;
	int	SerNum = 0;
	int	Flags;
	int	NoGeneric;
	Hook	*hook_array[2048];
	int	hook_num = 0;
	static	int hook_level = 0;

	hook_level++;

	/* I'm keeping this because its sooooo lame - phone, march 1993 */
#if 0
	for(i = 0; i < BIG_BUFFER_SIZE+1; ++i)
		buffer[i] = '\0';
#endif
	bzero(buffer, sizeof(buffer));

	sprintf(buffer, format, arg1, arg2, arg3, arg4, arg5, arg6);
	if (which < 0)
	{
		NumericList *hook;
		char	foo[4];

		sprintf(foo, "%3.3u", -which);
		if (hook = (NumericList *) find_in_list(&numeric_list, foo, 0))
		{
			name = hook->name;
			list = &hook->list;
		}
		else
			list = null(Hook **);
	}
	else
	{
		if (hook_functions[which].mark &&
				(hook_functions[which].flags & HF_NORECURSE))
			list = null(Hook **);
		else
		{
			list = &(hook_functions[which].list);
			name = hook_functions[which].name;
		}
	}
	if (!list)
		return really_free(--hook_level), 1;

	if (which >= 0)
		hook_functions[which].mark++;
	NoGeneric = 0;
	for (tmp = *list; tmp; tmp = tmp->next)
		if (tmp->server != -1 &&
		    (tmp->server & HS_NOGENERIC) &&
		    (tmp->server & ~HS_NOGENERIC) == from_server)
			NoGeneric = 1;
	RetVal = 1;
	Flags = CIF_NOSERNUM;
	while (1)
	{
		setup_struct(from_server, SerNum, 0, Flags);
		if (!(tmp = (Hook *) find_in_list_ext(list, buffer, 1,
				Execute_Check)) && !NoGeneric)
		{
			setup_struct(-1, SerNum, 0, Flags);
			tmp = (Hook *) find_in_list_ext(list, buffer, 1,
				Execute_Check);
		}
		if (!tmp)
			break;
		hook_array[hook_num++] = tmp;
		SerNum = cmp_info.SerialNumber;
		Flags |= CIF_SKIP;
	}
	for (i = 0; i < hook_num; i++)
	{
		tmp = hook_array[i];
		if (!tmp)
		{
			if (which >= 0)
				hook_functions[which].mark--;
			return really_free(--hook_level), RetVal;
		}
		if (tmp->not)
			continue;
		send_text_flag = which;
		if (tmp->noisy > QUIET)
			say("%s activated by \"%s\"", name, buffer);
		display = window_display;
		if (tmp->noisy < NOISY)
			window_display = 0;

		save_message_from();
		old_in_on_who = in_on_who;
		if (which == WHO_LIST || (which <= -311 && which >= -318))
			in_on_who = 1;
		{ 
			int	len = strlen(tmp->stuff) + 1; 
			char	*foo = new_malloc(len);

			bcopy(tmp->stuff, foo, len);
			parse_line(null(char *), foo, buffer, 0, 0);
			new_free(&foo);
		}
		in_on_who = old_in_on_who;
		window_display = display;
		send_text_flag = -1;
		restore_message_from();
		if (!tmp->noisy && !tmp->sernum)
			RetVal = 0;
	}
	if (which >= 0)
		hook_functions[which].mark--;
	return really_free(--hook_level), RetVal;
}

static	void	remove_numeric_hook(numeric, nick, server, sernum)
int	numeric;
char	*nick;
int	server;
int	sernum;
{
	NumericList *hook;
	Hook	*tmp,
		*next;
	char	buf[4];

	sprintf(buf, "%3.3u", numeric);
	if (hook = (NumericList *) find_in_list(&numeric_list, buf, 0))
	{
		if (nick)
		{
			setup_struct((server==-1) ? -1 :
				(server & ~HS_NOGENERIC), sernum-1, sernum, 0);
			if (tmp = (Hook *) remove_from_list(&(hook->list),
					nick))
			{
				say("\"%s\" removed from %s list", nick, buf);
				tmp->not = 1;
				new_free(&(tmp->nick));
				new_free(&(tmp->stuff));
				wait_new_free(&tmp);
				if (hook->list == null(Hook *))
				{
					if (hook = (NumericList *)
						remove_from_list(&numeric_list,
						buf))
					{
						new_free(&(hook->name));
						new_free(&hook);
					}
				}
				return;
			}
		}
		else
		{
			for(tmp = hook->list;tmp;tmp=next)
			{
				next = tmp->next;
				tmp->not = 1;
				new_free(&(tmp->nick));
				new_free(&(tmp->stuff));
				wait_new_free(&tmp);
			}
			hook->list = null(Hook *);
			say("The %s list is empty", buf);
			return;
		}
	}
	if (nick)
		say("\"%s\" is not on the %s list", nick, buf);
	else
		say("The %s list is empty", buf);
}

static	void	remove_hook(which, nick, server, sernum)
int	which;
char	*nick;
int	server,
	sernum;
{
	Hook	*tmp,
		*next;

	if (which < 0)
	{
		remove_numeric_hook(-which, nick, server, sernum);
		return;
	}
	if (nick)
	{
		setup_struct((server == -1) ? -1 : (server & ~HS_NOGENERIC),
			sernum-1, sernum, 0);
		if (tmp = (Hook *)
			remove_from_list_ext(&(hook_functions[which].list),
			nick, Add_Remove_Check))
		{
			say("\"%s\" removed from %s list", nick,
					hook_functions[which].name);
			tmp->not = 1;
			new_free(&(tmp->nick));
			new_free(&(tmp->stuff));
			wait_new_free(&tmp);
		}
		else
			say("\"%s\" is not on the %s list", nick,
					hook_functions[which].name);
	}
	else
	{
		for(tmp = hook_functions[which].list; tmp; tmp=next)
		{
			next = tmp->next;
			tmp->not = 1;
			new_free(&(tmp->nick));
			new_free(&(tmp->stuff));
			wait_new_free(&tmp);
		}
		hook_functions[which].list = null(Hook *);
		say("The %s list is empty", hook_functions[which].name);
	}
}

extern char *new_next_arg();

/* on: The ON command */
/*ARGSUSED*/
void	on(command, args)
char	*command,
*args;
{
	char	*func,
	*nick,
	*serial;
	/* int noisy = NORMAL, not = 0, remove = 0, -not used */
	int	noisy,
		not,
		server,
		sernum,
		remove,
		len,
		which = 0,
		cnt,
		i;

	if (get_int_var(NOVICE_VAR) && !load_depth)
	{
	    yell("*** You may not type ON commands when you have the NOVICE");
	    yell("*** variable set to ON. Some ON commands may cause a");
	    yell("*** security breach on your machine, or enable another");
	    yell("*** user to control your IRC session. Read the help files");
	    yell("*** in /HELP ON before using ON");
	    return;
	}
	if (func = next_arg(args, &args))
	{
		if (*func == '#')
		{
			if (!(serial = next_arg(args, &args)))
			{
				say("No serial number specified");
				return;
			}
			sernum = atoi(serial);
			func++;
		}
		else
			sernum = 0;
		switch (*func)
		{
		case '&':
			server = from_server;
			func++;
			break;
		case '@':
			server = from_server|HS_NOGENERIC;
			func++;
			break;
		default:
			server = -1;
			break;
		}
		switch (*func)
		{
		case '-':
			noisy = QUIET;
			func++;
			break;
		case '^':
			noisy = SILENT;
			func++;
			break;
		case '+':
			noisy = NOISY;
			func++;
			break;
		default:
			noisy = NORMAL;
			break;
		}
		if ((len = strlen(func)) == 0)
		{
			say("You must specify an event type!");
			return;
		}
		for (cnt = 0, i = 0; i < NUMBER_OF_LISTS; i++)
		{
			if (!strnicmp(func, hook_functions[i].name, len))
			{
				if (strlen(hook_functions[i].name) == len)
				{
					cnt = 1;
					which = i;
					break;
				}
				else
				{
					cnt++;
					which = i;
				}
			}
			else if (cnt)
				break;
		}
		if (cnt == 0)
		{
			if (is_number(func))
			{
				which = atoi(func);
				if ((which < 1) || (which > 999))
				{
					say("Numerics must be between 001 and \
999");
					return;
				}
				which = -which;
			}
			else
			{
				say("No such ON function: %s", func);
				return;
			}
		}
		else if (cnt > 1)
		{
			say("Ambiguous ON function: %s", func);
			return;
		}
		else 
		{
			if (get_int_var(INPUT_PROTECTION_VAR) &&
					!strnicmp(hook_functions[which].name,
					"INPUT", 5))
			{
	say("You cannot use /ON INPUT with INPUT_PROTECTION set");
	say("Please read /HELP ON INPUT, and /HELP SET INPUT_PROTECTION");
	return;
			}
		}
		remove = 0;
		not = 0;
		switch (*args)
		{
		case '-':
			remove = 1;
			args++;
			break;
		case '^':
			not = 1;
			args++;
			break;
		}
		if (nick = new_next_arg(args, &args))
		{
			if (which < 0)
				nick = fill_it_out(nick, 1);
			else
				nick = fill_it_out(nick,
					hook_functions[which].params);
			if (remove)
			{
				if (strlen(nick) == 0)
					say("No expression specified");
				else
					remove_hook(which, nick, server,sernum);
			}
			else

	/* Indent this bit back a couple of tabs - phone */

	{
		if (not)
			args = empty_string;
		if (*nick)
		{
			if (*args == LEFT_BRACE)
			{
				char	*ptr = MatchingBracket(++args,
						LEFT_BRACE, RIGHT_BRACE);
				if (!ptr)
				{
					say("Unmatched brace in ON");
					new_free(&nick);
					return;
				}
				else if (ptr[1])
				{
					say("Junk after closing brace in ON");
					new_free(&nick);
					return;
				}
				else
					*ptr = '\0';
			}
			add_hook(which, nick, args, noisy, not, server, sernum);
			if (which < 0)
				say("On %3.3u from \"%s\" do %s [%s] <%d>",
				    -which, nick, (not ? "nothing" : args),
				    noise_level[noisy], sernum);
			else
				say("On %s from \"%s\" do %s [%s] <%d>",
					hook_functions[which].name, nick,
					(not ? "nothing" : args),
					noise_level[noisy], sernum);
			new_free(&nick);
		}
	}
	/* End of doovie intentation */
		}
		else
		{
			if (remove)
				remove_hook(which, null(char *), server,sernum);
			else
			{
				if (which < 0)
				{
					if (show_numeric_list(-which) == 0)
						say("The %3.3u list is empty.",
							-which);
				}
				else if (show_list(which) == 0)
					say("The %s list is empty.",
						hook_functions[which].name);
			}
		}
	}
	else
	{
		int	total = 0;

		say("ON listings:");
		for (which = 0; which < NUMBER_OF_LISTS; which++)
			total += show_list(which);
		total += show_numeric_list(0);
		if (total == 0)
			say("All ON lists are empty.");
	}
}

static	void	write_hook(fp, hook, name)
FILE	*fp;
Hook	*hook;
char	*name;
{
	char	*stuff = null(char *);

	if (hook->server!=-1)
		return;
	switch (hook->noisy)
	{
	case SILENT:
		stuff = "^";
		break;
	case QUIET:
		stuff = "-";
		break;
	case NORMAL:
		stuff = empty_string;
		break;
	case NOISY:
		stuff = "+";
		break;
	}
	if (hook->sernum)
		fprintf(fp, "ON #%s%s %d \"%s\"", stuff, name, hook->sernum,
			hook->nick);
	else
		fprintf(fp, "ON %s%s \"%s\"", stuff, name, hook->nick);
	fprintf(fp, " %s\n", hook->stuff);
}

/*
 * save_hooks: for use by the SAVE command to write the hooks to a file so it
 * can be interpreted by the LOAD command 
 */
void	save_hooks(fp)
FILE	*fp;
{
	Hook	*list;
	NumericList *numeric;
	int	which;

	for (which = 0; which < NUMBER_OF_LISTS; which++)
	{
		for (list = hook_functions[which].list; list; list = list->next)
			write_hook(fp, list, hook_functions[which].name);
	}
	for (numeric = numeric_list; numeric; numeric = numeric->next)
	{
		for (list = numeric->list; list; list = list->next)
			write_hook(fp, list, numeric->name);
	}
}
