/*
 * hook.c: Does those naughty hook functions. 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "irc.h"

#include "hook.h"
#include "vars.h"
#include "ircaux.h"
#include "expr.h"
#include "list.h"
#include "window.h"
#include "server.h"
#include "output.h"
#include "commands.h"
#include "parse.h"
#include "fset.h"
#include "misc.h"
#include <stdarg.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
#define HF_NORECURSE	0x0002
#define HF_GLOBAL	0x0004

int in_on_who = 0;

NumericList *numeric_list = NULL;

/* hook_functions: the list of all hook functions available */
HookFunc hook_functions[] = {
    {"ACTION", NULL, 3, 0, 0},
    {"AR_PUBLIC", NULL, 3, 0, 0},
    {"AR_PUBLIC_OTHER", NULL, 3, 0, 0},
    {"AR_REPLY", NULL, 1, 0, 0},
    {"BANS", NULL, 4, 0, 0},
    {"BANS_HEADER", NULL, 4, 0, 0},

    {"CHANNEL_NICK", NULL, 3, 0, 0},
    {"CHANNEL_SIGNOFF", NULL, 3, 0, 0},
    {"CHANNEL_STATS", NULL, 32, 0, 0},
    {"CHANNEL_SWITCH", NULL, 1, 0, 0},
    {"CHANNEL_SYNCH", NULL, 2, 0, 0},
    {"CLONE_READ", NULL, 4, 0, 0},
    {"CONNECT", NULL, 1, 0, 0},
    {"CTCP", NULL, 4, 0, 0},
    {"CTCP_REPLY", NULL, 3, 0, 0},
    {"DCC_CHAT", NULL, 2, 0, 0},
    {"DCC_CONNECT", NULL, 2, 0, 0},
    {"DCC_ERROR", NULL, 6, 0, 0},
    {"DCC_HEADER", NULL, 7, 0, 0},
    {"DCC_LOST", NULL, 2, 0, 0},
    {"DCC_POST", NULL, 7, 0, 0},
    {"DCC_RAW", NULL, 3, 0, 0},
    {"DCC_REQUEST", NULL, 4, 0, 0},
    {"DCC_STAT", NULL, 7, 0, 0},
    {"DCC_STATF", NULL, 7, 0, 0},
    {"DCC_STATF1", NULL, 5, 0, 0},
    {"DESYNC_MESSAGE", NULL, 2, 0, 0},
    {"DISCONNECT", NULL, 1, 0, 0},
    {"ENCRYPTED_NOTICE", NULL, 3, 0, 0},
    {"ENCRYPTED_PRIVMSG", NULL, 3, 0, 0},
    {"EXEC", NULL, 2, 0, 0},
    {"EXEC_ERRORS", NULL, 2, 0, 0},
    {"EXEC_EXIT", NULL, 3, 0, 0},
    {"EXEC_PROMPT", NULL, 2, 0, 0},
    {"EXIT", NULL, 1, 0, 0},
    {"FLOOD", NULL, 3, 0, 0},
    {"HELP", NULL, 2, 0, 0},
    {"HELPSUBJECT", NULL, 2, 0, 0},
    {"HELPTOPIC", NULL, 1, 0, 0},
    {"HOOK", NULL, 1, 0, 0},
    {"IDLE", NULL, 1, 0, 0},
    {"INPUT", NULL, 1, 0, 0},
    {"INVITE", NULL, 2, 0, 0},
    {"JOIN", NULL, 3, 0, 0},
    {"JOIN_ME", NULL, 1, 0, 0},
    {"KICK", NULL, 3, 0, 0},
    {"LEAVE", NULL, 2, 0, 0},
    {"LIST", NULL, 3, 0, 0},
    {"LLOOK_ADDED", NULL, 2, 0, 0},
    {"LLOOK_JOIN", NULL, 2, 0, 0},
    {"LLOOK_SPLIT", NULL, 2, 0, 0},
    {"MODE", NULL, 3, 0, 0},
    {"MODE_STRIPPED", NULL, 3, 0, 0},
    {"MSG", NULL, 2, 0, 0},
    {"MSG_GROUP", NULL, 3, 0, 0},
    {"MSGLOG", NULL, 4, 0, 0},
    {"NAMES", NULL, 2, 0, 0},
    {"NICKNAME", NULL, 2, 0, 0},
    {"NOTE", NULL, 3, 0, 0},
    {"NOTICE", NULL, 2, 0, 0},
    {"NOTIFY_SIGNOFF", NULL, 1, 0, 0},
    {"NOTIFY_SIGNOFF_UH", NULL, 3, 0, 0},
    {"NOTIFY_SIGNON", NULL, 1, 0, 0},
    {"NOTIFY_SIGNON_UH", NULL, 3, 0, 0},
    {"NSLOOKUP", NULL, 3, 0, 0},
    {"ODD_SERVER_STUFF", NULL, 3, 0, 0},
    {"PUBLIC", NULL, 3, 0, 0},
    {"PUBLIC_MSG", NULL, 3, 0, 0},
    {"PUBLIC_NOTICE", NULL, 3, 0, 0},
    {"PUBLIC_OTHER", NULL, 3, 0, 0},
    {"RAW_IRC", NULL, 1, 0, 0},
    {"SAVEFILE", NULL, 2, 0, 0},
    {"SAVEFILEPOST", NULL, 2, 0, 0},
    {"SAVEFILEPRE", NULL, 2, 0, 0},
    {"SEND_ACTION", NULL, 2, 0, HF_NORECURSE},
    {"SEND_DCC_CHAT", NULL, 2, 0, HF_NORECURSE},
    {"SEND_MSG", NULL, 2, 0, HF_NORECURSE},
    {"SEND_NOTICE", NULL, 2, 0, HF_NORECURSE},
    {"SEND_PUBLIC", NULL, 2, 0, HF_NORECURSE},
    {"SEND_TO_SERVER", NULL, 3, 0, 0},
    {"SERVER_NOTICE_FAKES", NULL, 3, 0, 0},
    {"SERVER_NOTICE_FAKES_MYCHANNEL", NULL, 3, 0, 0},
    {"SERVER_NOTICE_FOREIGN_KILL", NULL, 4, 0, 0},
    {"SERVER_NOTICE_KILL", NULL, 4, 0, 0},
    {"SERVER_NOTICE", NULL, 1, 0, 0},
    {"SERVER_NOTICE_LOCAL_KILL", NULL, 4, 0, 0},
    {"SERVER_NOTICE_SERVER_KILL", NULL, 4, 0, 0},
    {"SET", NULL, 2, 0, 0},
    {"SHOWIDLE_HEADER", NULL, 2, 0, 0},
    {"SHOWIDLE", NULL, 4, 0, 0},
    {"SIGNOFF", NULL, 1, 0, 0},
    {"SILENCE", NULL, 2, 0, 0},
    {"STAT", NULL, 5, 0, 0},
    {"STAT_HEADER", NULL, 5, 0, 0},
    {"STATUS_UPDATE", NULL, 2, 0, 0},
    {"TIMER", NULL, 1, 0, 0},
    {"TOPIC", NULL, 2, 0, 0},
    {"USAGE", NULL, 2, 0, 0},
    {"USERS", NULL, 7, 0, 0},
    {"USERS_HEADER", NULL, 7, 0, 0},
    {"USERS_SERVER", NULL, 2, 0, 0},
    {"USERS_SERVER_HEADER", NULL, 2, 0, 0},
    {"WALL", NULL, 2, 0, 0},
    {"WALLOP", NULL, 3, 0, 0},
    {"WHO", NULL, 6, 0, 0},
    {"WHOLEFT", NULL, 6, 0, 0},
    {"WHOLEFT_HEADER", NULL, 6, 0, 0},
    {"WIDELIST", NULL, 1, 0, 0},
    {"WINDOW", NULL, 2, 0, HF_NORECURSE},
    {"WINDOW_KILL", NULL, 1, 0, 0},
    {"YELL", NULL, 1, 0, 0}
};

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

    malloc_strcpy(&free_ptr, str);
    ptr = free_ptr;
    *buffer = (char) 0;

    while ((arg = next_arg(ptr, &ptr)) != NULL) {
	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 && *ptr) {
	strmcat(buffer, " ", BIG_BUFFER_SIZE);
	strmcat(buffer, ptr, BIG_BUFFER_SIZE);
    }
    malloc_strcpy(&free_ptr, buffer);
    return (free_ptr);
}
#endif

/*
 * This crap here is used so we can use the list manip stuff.  Maybe 
 * we should fix the problem instead of using nasty hacks like this.
 */

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

#define	CIF_NOSERNUM	0x0001
#define	CIF_SKIP	0x0002

int cmpinfodone = 0;

/*
 * setup_struct and Add_Remove_Check are used by the list manipulation
 * functions for adding and removing hooks from the hook list.
 */
static void setup_struct(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 Add_Remove_Check(Hook * Item, char *Name)
{
    int comp;

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

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

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

    setup_struct((server == -1) ? -1 : (server & ~HS_NOGENERIC), sernum - 1, sernum, 0);
    if ((new =
	 (Hook *) remove_from_list_ext((struct list **) &(entry->list), nick,
				       (int (*)(struct list *, char *)) Add_Remove_Check)) != NULL) {
	new->not = 1;
	new_free(&(new->nick));
	new_free(&(new->stuff));
	new_free((char **) &new);
    }
    new = (Hook *) new_malloc(sizeof(Hook));
    memset(new, 0, sizeof(Hook));
    new->noisy = noisy;
    new->server = server;
    new->sernum = sernum;
    new->not = not;
    new->flexible = flexible;
    malloc_strcpy(&new->nick, nick);
    malloc_strcpy(&new->stuff, stuff);
    upper(new->nick);
    add_to_list_ext((struct list **) &(entry->list), (struct list *) new, (cmp_fn *) Add_Remove_Check);
}
#endif

#if 0
/*
 * 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(int which, char *nick, char *stuff, int noisy, int not, int server, int sernum, int flexible)
{
    Hook *new;

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

/* show_hook shows a single hook */
extern void show_hook(Hook * list, char *name)
{
    const char *hooks = get_fset_var(FORMAT_HOOK_FSET);
    char *text = NULL;

    if (list->stuff)
	text = stripansi(list->stuff);
    if (list->server != -1) {
	if (hooks)
	    put_it("%s", convert_output_format(hooks, "%s %c %s %c %s %s %d Server %d %s",
					       name, (list->flexible ? '\'' : '"'), list->nick,
					       (list->flexible ? '\'' : '"'),
					       (list->not ? "nothing" : text),
					       noise_level[list->noisy], list->sernum,
					       list->server & ~HS_NOGENERIC,
					       (list->server & HS_NOGENERIC) ? " Exclusive" : empty_str));
	else
	    say("On %s from %c%s%c do %s [%s] <%d> (Server %d)%s",
		name, (list->flexible ? '\'' : '"'), list->nick,
		(list->flexible ? '\'' : '"'),
		(list->not ? "nothing" : text),
		noise_level[list->noisy], list->sernum,
		list->server & ~HS_NOGENERIC, (list->server & HS_NOGENERIC) ? " Exclusive" : empty_str);
    } else {
	if (hooks)
	    put_it("%s", convert_output_format(hooks, "%s %c %s %c %s %s %d",
					       name, (list->flexible ? '\'' : '"'), list->nick,
					       (list->flexible ? '\'' : '"'),
					       (list->not ? "nothing" : text), noise_level[list->noisy], list->sernum));
	else
	    say("On %s from %c%s%c do %s [%s] <%d>",
		name, (list->flexible ? '\'' : '"'), list->nick,
		(list->flexible ? '\'' : '"'), (list->not ? "nothing" : text), noise_level[list->noisy], list->sernum);
    }
    new_free(&text);
}

#if 0
/*
 * 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(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((struct list **) &numeric_list, buf, 0))
	    != NULL) {
	    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(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);
}
#endif

/*
 * 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. 
 */
/* huh-huh.. this sucks.. im going to re-write it so that it works */
extern int do_hook(int which, char *format, ...)
{
    Hook *tmp, **list;
    char buffer[BIG_BUFFER_SIZE * 10 + 1], *name = NULL;
    int RetVal = 1;
    unsigned int display;
    int i, old_in_on_who;
    Hook *hook_array[2048];
    int hook_num = 0;
    static int hook_level = 0;

    hook_level++;
    *buffer = 0;

    if (format) {
	va_list args;

	va_start(args, format);
	vsnprintf(buffer, BIG_BUFFER_SIZE * 10, format, args);
	va_end(args);
    }
    if (which < 0) {
	NumericList *hook;
	char foo[10];

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

    if (which >= 0)
	hook_functions[which].mark++;

    /* not attached, so dont "fix" it */
    {
	int currser = 0, oldser = 0;
	int currmatch = 0, oldmatch = 0;
	Hook *bestmatch = NULL;
	int nomorethisserial = 0;

	for (tmp = *list; tmp; tmp = tmp->next) {
	    char *tmpnick = NULL;
	    int sa;

	    currser = tmp->sernum;
	    if (currser != oldser) {	/* new serial number */
		oldser = currser;
		currmatch = oldmatch = nomorethisserial = 0;
		if (bestmatch)
		    hook_array[hook_num++] = bestmatch;
		bestmatch = NULL;
	    }

	    if (nomorethisserial)
		continue;
	    /* if there is a specific server hook and it doesnt match, then we make sure nothing from this serial number gets hooked */

	    if ((tmp->server != -1) && (tmp->server & HS_NOGENERIC) && (tmp->server != (from_server & HS_NOGENERIC))) {
		nomorethisserial = 1;
		bestmatch = NULL;
		continue;
	    }

	    if (tmp->flexible)
		tmpnick = expand_alias(tmp->nick, empty_str, &sa, NULL);
	    else
		tmpnick = tmp->nick;

	    currmatch = wild_match(tmpnick, buffer);
	    if (currmatch > oldmatch) {
		oldmatch = currmatch;
		bestmatch = tmp;
	    }
	    if (tmp->flexible)
		new_free(&tmpnick);
	}
	if (bestmatch)
	    hook_array[hook_num++] = bestmatch;
    }

    for (i = 0; i < hook_num; i++) {
	char *foo, *saved_who_from = NULL;
	int saved_who_level;

	if (!(tmp = hook_array[i])) {
	    if (which >= 0)
		hook_functions[which].mark--;
	    return RetVal;
	}
	if (tmp->not)
	    continue;

	current_on_hook = which;
	if (tmp->noisy > QUIET)
	    say("%s activated by %c%s%c", name, (tmp->flexible ? '\'' : '"'), buffer, (tmp->flexible ? '\'' : '"'));
	display = window_display;
	if (tmp->noisy < NOISY)
	    window_display = 0;

	save_message_from(&saved_who_from, &saved_who_level);
	old_in_on_who = in_on_who;

#ifdef ENFORCE_STRICTER_PROTOCOL
	if (which == WHO_LIST || (which <= -311 && which >= -318))
	    in_on_who = 1;
#else
	in_on_who = 0;
#endif
	if (!tmp->noisy && !tmp->sernum)
	    RetVal = 0;
	if (tmp->stuff && *tmp->stuff) {
	    char *name2 = alloca(strlen(name) + 1);

	    strcpy(name2, name);

	    foo = alloca(strlen(tmp->stuff) + 1);
	    strcpy(foo, tmp->stuff);
	    parse_line(name2, foo, buffer, 0, 0);
	}
	in_on_who = old_in_on_who;
	window_display = display;
	current_on_hook = -1;
	restore_message_from(saved_who_from, saved_who_level);
    }
    if (which >= 0)
	hook_functions[which].mark--;

    return RetVal;
}

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

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

extern void flush_on_hooks(void)
{
    int x;
    int old_display = window_display;

    window_display = 0;
    for (x = 1; x < 999; x++)
	remove_numeric_hook(x, NULL, 1, x, 1);
    for (x = 0; x < NUMBER_OF_LISTS; x++)
	remove_hook(x, NULL, 1, 0, 1);	/* the 4th arg should be 0, not x */
    window_display = old_display;
}

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

    if (which < 0) {
	remove_numeric_hook(-which, nick, server, sernum, quiet);
	return;
    }
    if (nick) {
	setup_struct((server == -1) ? -1 : (server & ~HS_NOGENERIC), sernum - 1, sernum, 0);

	if ((tmp = (Hook *) remove_from_list_ext((struct list **) &(hook_functions[which].list), nick,
						 (int (*)(struct list *, char *)) Add_Remove_Check)) != NULL) {
	    if (!quiet)
		say("%c%s%c removed from %s list",
		    (tmp->flexible ? '\'' : '"'), nick, (tmp->flexible ? '\'' : '"'), hook_functions[which].name);
	    tmp->not = 1;
	    new_free(&(tmp->nick));
	    new_free(&(tmp->stuff));
	    new_free((char **) &tmp);
	} else if (!quiet)
	    say("\"%s\" is not on the %s list", nick, hook_functions[which].name);
    } else {
	Hook *prev = NULL;
	Hook *top = NULL;

	for (tmp = hook_functions[which].list; tmp; prev = tmp, tmp = next) {
	    next = tmp->next;

	    /* If given a non-zero sernum, then we clean out only those hooks that are at that level. */
	    if (sernum && tmp->sernum != sernum) {
		if (!top)
		    top = tmp;
		continue;
	    }

	    if (prev)
		prev->next = tmp->next;
	    tmp->not = 1;
	    new_free(&(tmp->nick));
	    new_free(&(tmp->stuff));
	    new_free((char **) &tmp);
	}
	hook_functions[which].list = top;
	if (!quiet) {
	    if (sernum)
		say("The %s <%d> list is empty", hook_functions[which].name, sernum);
	    else
		say("The %s list is empty", hook_functions[which].name);
	}
    }
}

static void write_hook(FILE * fp, Hook * hook, char *name)
{
    char *stuff = NULL;
    char flexi = '"';

    if (hook->server != -1)
	return;

    if (hook->flexible)
	flexi = '\'';

    switch (hook->noisy) {
    case SILENT:
	stuff = "^";
	break;
    case QUIET:
	stuff = "-";
	break;
    case NORMAL:
	stuff = empty_str;
	break;
    case NOISY:
	stuff = "+";
	break;
    }

    if (hook->sernum)
	fprintf(fp, "ON #%s%s %d", stuff, name, hook->sernum);
    else
	fprintf(fp, "ON %s%s", stuff, name);

    fprintf(fp, " %c%s%c %s\n", flexi, hook->nick, flexi, 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 
 */
extern void save_hooks(FILE * fp, int do_all)
{
    Hook *list;
    NumericList *numeric;
    int which;

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

#define INVALID_HOOKNUM -1001

/*
 * find_hook: returns the numerical value for a specified hook name
 */
int find_hook(char *name)
{
    int which = INVALID_HOOKNUM, i, len, cnt;

    if (!(len = strlen(name))) {
	say("You must specify an event type!");
	return INVALID_HOOKNUM;
    }

    upper(name);

    for (cnt = 0, i = 0; i < NUMBER_OF_LISTS; i++) {
	if (!strncmp(name, 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(name)) {
	    which = my_atol(name);

	    if ((which < 1) || (which > 999)) {
		say("Numerics must be between 001 and 999");
		return INVALID_HOOKNUM;
	    }
	    which = -which;
	} else {
	    say("No such ON function: %s", name);
	    return INVALID_HOOKNUM;
	}
    } else if (cnt > 1) {
	say("Ambiguous ON function: %s", name);
	return INVALID_HOOKNUM;
    }

    return which;
}

 /* 
  * shook: the SHOOK command -- this probably doesnt belong here,
  * and shook is probably a stupid name
  */
BUILT_IN_COMMAND(shook)
{
    int which;
    char *arg;

    if (!args || !*args) {
	userage(command, helparg);
	return;
    }

    arg = next_arg(args, &args);

    if ((which = find_hook(arg)) == INVALID_HOOKNUM)
	return;
    else
	do_hook(which, "%s", args);
}

/* on: The ON command */
BUILT_IN_COMMAND(oncmd)
{
#if 0
    char *func, *nick, *serial;

    /* int noisy = NORMAL, not = 0, remove = 0, -not used */
    int noisy, not, server, sernum, remove, which = 0;
    int flexible;
    char type;

    if ((func = next_arg(args, &args)) != NULL) {
	if (*func == '#') {
	    if (!(serial = next_arg(args, &args))) {
		say("No serial number specified");
		return;
	    }
	    sernum = my_atol(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 ((which = find_hook(func)) == INVALID_HOOKNUM)
	    return;

	/* XXX This is bogus XXX */
	if (which == INPUT_LIST && get_int_var(INPUT_PROTECTION_VAR)) {
	    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_new_next_arg(args, &args, &type)) != NULL) {
	    if (which < 0)
		nick = fill_it_out(nick, 1);
	    else
		nick = fill_it_out(nick, hook_functions[which].params);

	    if (type == '\'')
		flexible = 1;
	    else
		flexible = 0;

	    if (remove) {
		if (strlen(nick) == 0)
		    say("No expression specified");
		else
		    remove_hook(which, nick, server, sernum, 0);
	    } else
		/* Indent this bit back a couple of tabs - phone */

	    {
		if (not)
		    args = empty_str;

		if (*nick) {
		    char *exp;

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

		    if (*args == '{') {
			if (!(exp = next_expr(&args, '{'))) {
			    say("Unmatched brace in ON");
			    new_free(&nick);
			    return;
			}
		    } else
			exp = args;

		    add_hook(which, nick, exp, noisy, not, server, sernum, flexible);
		    if (which < 0)
			say("On %3.3u from %c%s%c do %s [%s] <%d>",
			    -which, type, nick, type, (not ? "nothing" : exp), noise_level[noisy], sernum);
		    else
			say("On %s from %c%s%c do %s [%s] <%d>",
			    hook_functions[which].name, type, nick, type, (not ? "nothing" : exp), noise_level[noisy], sernum);
		    new_free(&nick);
		}
	    }
	    /* End of doovie intentation */
	} else {
	    if (remove)
		remove_hook(which, NULL, server, sernum, 0);
	    else {
		/* the help files say that that an /on 0 shows all the numeric /ONs.  Since the ACTION hook is "number 0", there is no
		 * way to tell right here whether the user user specified an ACTION or a "show me it all". blah. ("feature" rmd from
		 * helps) */
		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.");
    }
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1