/*
 * edit.c -- a historical name for the file that contains most of the
 * commands that you can use... The keybindings that used to be here
 * i moved to input.c.  What was left, i alphabetized to make things
 * easier to find.
 *
 * Written By Michael Sandrof
 * Reorganized with ruthless abandon for EPIC3pre2 (summer, 1995)
 *   by Jeremy Nelson (nelson@cs.uwp.edu)
 *
 * Copyright(c) 1990, 1995 Michael Sandroff and others
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#if 0
static	char	rcsid[] = "@(#)$Id: edit.c,v 1.25 1994/07/26 12:12:10 mrg Exp $";
#endif

#include "irc.h"

#include <sys/stat.h>

#ifdef ESIX
# include <lan/net_types.h>
#endif

#include "alias.h"
#include "crypt.h"
#include "ctcp.h"
#include "dcc.h"
#include "edit.h"
#include "exec.h"
#include "funny.h"
#include "help.h"
#include "history.h"
#include "hook.h"
#include "server.h"
#include "if.h"
#include "ignore.h"
#include "input.h"
#include "ircaux.h"
#include "keys.h"
#include "lastlog.h"
#include "names.h"
#include "notify.h"
#include "notice.h"
#include "numbers.h"
#include "output.h"
#include "parse.h"
#include "queue.h"
#include "screen.h"
#include "status.h"
#include "stack.h"
#include "term.h"
#include "timer.h"
#include "translat.h"
#include "vars.h"
#include "window.h"
#include "whois.h"

#define current_server server_list[current_screen->current_window->server]->write

/* 
 * defined to 1 if we are parsing something interactive from the user,
 * 0 if it is not being done interactive (within an alias)
 */
	int interactive = 0;

static	int	save_which;
static	int	save_do_all;
	int	in_e_nick = 0;

/* used with input_move_cursor */
#define RIGHT 1
#define LEFT 0

/* used with /save */
#define	SFLAG_ALIAS	0x0001
#define	SFLAG_BIND	0x0002
#define	SFLAG_ON	0x0004
#define	SFLAG_SET	0x0008
#define	SFLAG_NOTIFY	0x0010
#define	SFLAG_DIGRAPH	0x0020

/* The maximum number of recursive LOAD levels allowed */
#define MAX_LOAD_DEPTH 10

/* recv_nick: the nickname of the last person to send you a privmsg */
	char	*recv_nick = NULL;

/* sent_nick: the nickname of the last person to whom you sent a privmsg */
	char	*sent_nick = NULL;
	char	*sent_body = NULL;

/* 
 * Used to keep down the nesting of /LOADs and to determine if we
 * should activate the warning for /ONs if the NOVICE variable is set.
 */
	int	load_depth = 0;

typedef	struct	WaitCmdstru
{
	char	*stuff;
	struct	WaitCmdstru	*next;
}	WaitCmd;

static	WaitCmd	*start_wait_list = NULL,
		*end_wait_list = NULL;

	char	lame_wait_nick[] = "1#LAME";

/* commands and whatnot */
static  void    abortcmd 	_((char *, char *, char *));
static	void	away 		_((char *, char *, char *));
static	void	beepcmd 	_((char *, char *, char *));
static	void	comment 	_((char *, char *, char *));
static	void	ctcp 		_((char *, char *, char *));
static	void	dcc 		_((char *, char *, char *));
static	void	deop 		_((char *, char *, char *));
static	void	do_send_text 	_((char *, char *, char *));
static	void	sendlinecmd 	_((char *, char *, char *));
static	void	echo 		_((char *, char *, char *));
static	void	funny_stuff 	_((char *, char *, char *));
static	void	cd 		_((char *, char *, char *));
static	void	describe 	_((char *, char *, char *));
static  void    disconcmd 	_((char *, char *, char *));
static	void	e_call		_((char *, char *, char *));
static	void	e_channel 	_((char *, char *, char *));
static	void	e_nick 		_((char *, char *, char *));
static	void	e_privmsg 	_((char *, char *, char *));
static	void	e_pause		_((char *, char *, char *));
static	void	e_quit 		_((char *, char *, char *));
static	void	e_wallop 	_((char *, char *, char *));
static	void	evalcmd 	_((char *, char *, char *));
static	void	flush 		_((char *, char *, char *));
static	void	hook 		_((char *, char *, char *));
static	void	info 		_((char *, char *, char *));
static	void	inputcmd 	_((char *, char *, char *));
static	void	ison 		_((char *, char *, char *));
static	void	me 		_((char *, char *, char *));
static	void	mlist 		_((char *, char *, char *));
static	void	mload 		_((char *, char *, char *));
static	void	oper 		_((char *, char *, char *));
static	void	pingcmd 	_((char *, char *, char *));
static  void    pop_cmd 	_((char *, char *, char *));
static	void	pretend_cmd	_((char *, char *, char *));
static  void    push_cmd 	_((char *, char *, char *));
static	void	quote 		_((char *, char *, char *));
static  void    realname_cmd 	_((char *, char *, char *));
static  void    reconnect_cmd   _((char *, char *, char *));
static	void	redirect 	_((char *, char *, char *));
static	void	save_settings 	_((char *, char *, char *));
static	void	send_2comm 	_((char *, char *, char *));
static	void	send_comm 	_((char *, char *, char *));
static	void	send_topic 	_((char *, char *, char *));
static	void	send_kick 	_((char *, char *, char *));
static	void	send_channel_com _((char *, char *, char *));
static  void    set_username 	_((char *, char *, char *));
static  void    shift_cmd 	_((char *, char *, char *));
static	void	sleepcmd 	_((char *, char *, char *));
static  void    unshift_cmd 	_((char *, char *, char *));
static	void	userhost 	_((char *, char *, char *));
static	void	version 	_((char *, char *, char *));
static 	void	waitcmd 	_((char *, char *, char *));
static	void	who 		_((char *, char *, char *));
static	void	whois 		_((char *, char *, char *));
static	void	xtypecmd 	_((char *, char *, char *));

/* other */
static	void	eval_inputlist 	_((char *, char *));

/* I hate typedefs, but they sure can be useful.. */
typedef void (*CmdFunc) _((char *, char *, char *));

/* IrcCommand: structure for each command in the command table */
typedef	struct
{
	char *		name;			/* what the user types */
	char *		server_func;		/* what gets sent to the server (if anything) */
	CmdFunc 	func;			/* function that is the command */
	unsigned	flags;
}	IrcCommand;

#define NONOVICEABBREV 0x0001
#define	NOINTERACTIVE  0x0002
#define	NOSIMPLESCRIPT 0x0004
#define	NOCOMPLEXSCRIPT 0x0008

/*
 * irc_command: all the availble irc commands:  Note that the first entry has
 * a zero length string name and a null server command... this little trick
 * makes "/ blah blah blah" to always send the arguments to a channel, 
 * bypassing queries, etc.  Neato.  This list MUST be sorted.
 */
static	IrcCommand irc_command[] =
{
	{ "",		empty_string,	do_send_text,		NOSIMPLESCRIPT|NOCOMPLEXSCRIPT },
	{ "#",		NULL,		comment, 		0 },
	{ ":",		NULL,		comment, 		0 },
        { "ABORT",      NULL,           abortcmd,               0 },
	{ "ADMIN",	"ADMIN",	send_comm, 		0 },
	{ "ALIAS",	"0",		alias,			0 }, /* alias.c */
	{ "ASSIGN",	"1",		alias,			0 }, /* alias.c */
	{ "AWAY",	"AWAY",		away,			0 },
	{ "BEEP",	0,		beepcmd,		0 },
	{ "BIND",	NULL,		bindcmd,		0 }, /* keys.c */
	{ "BYE",	"QUIT",		e_quit,			NONOVICEABBREV},
#ifdef WIND_STACK
	{ "CALL",	NULL,		e_call,			0 },
#endif
	{ "CD",		NULL,		cd,			0 },
	{ "CHANNEL",	"JOIN",		e_channel,		0 },
	{ "CLEAR",	NULL,		e_clear,		0 },
	{ "COMMENT",	NULL,		comment,		0 },
	{ "CONNECT",	"CONNECT",	send_comm,		0 },
	{ "CTCP",	NULL,		ctcp,			0 },
	{ "DATE",	"TIME",		send_comm,		0 },
	{ "DCC",	NULL,		dcc,			0 },
	{ "DEOP",	NULL,		deop,			0 },
	{ "DESCRIBE",	NULL,		describe,		0 },
	{ "DIE",	"DIE",		send_comm,		0 },
	{ "DIGRAPH",	NULL,		digraph,		0 }, /* translat.c */
        { "DISCON",	NULL,           disconcmd,              0 },
	{ "DISCONNECT",	NULL,		disconnectcmd,		0 }, /* server.c */
        { "DO",         NULL,           docmd,                  0 }, /* if.c */
        { "DUMP",       NULL,           flush_aliases,          0 },
	{ "ECHO",	NULL,		echo,			0 },
	{ "ENCRYPT",	NULL,		encrypt_cmd,		0 }, /* crypt.c */
	{ "EVAL",	NULL,		evalcmd,		0 },
#ifdef EXEC_COMMAND
	{ "EXEC",	NULL,		execcmd,		0 }, /* exec.c */
#else
	{ "EXEC",	NULL,		NULL,			0 },
#endif
	{ "EXIT",	"QUIT",		e_quit,			NONOVICEABBREV},
        { "FE",         "FE",           fe,		        0 }, /* if.c */
        { "FEC",        "FEC",          fe,                     0 }, /* if.c */
	{ "FLUSH",	NULL,		flush,			0 },
        { "FOR",        NULL,           forcmd,		        0 }, /* if.c */
	{ "FOREACH",	NULL,		foreach,		0 }, /* if.c */
	{ "HELP",	NULL,		help,			0 }, /* help.c */
	{ "HISTORY",	NULL,		history,		0 }, /* history.c */
	{ "HOOK",	NULL,		hook,			0 },
	{ "HOST",	"USERHOST",	userhost,		0 },
	{ "HOSTNAME",	"HOSTNAME",	e_hostname,		0 },
	{ "IF",		"IF",		ifcmd,			0 }, /* if.c */
	{ "IGNORE",	NULL,		ignore,			0 }, /* ignore.c */
	{ "INFO",	"INFO",		info,			0 },
	{ "INPUT",	"INPUT",	inputcmd,		0 },
	{ "INPUT_CHAR",	"INPUT_CHAR",	inputcmd,		0 },
	{ "INVITE",	"INVITE",	send_comm,		0 },
	{ "IRCHOST",	"HOSTNAME",	e_hostname,		0 },
	{ "IRCNAME",	NULL,		realname_cmd,		0 },
	{ "IRCUSER",	NULL,		set_username,		0 },
	{ "ISON",	"ISON",		ison,			0 },
	{ "JOIN",	"JOIN",		e_channel,		0 },
	{ "KICK",	"KICK",		send_kick,		0 },
	{ "KILL",	"KILL",		send_2comm,		0 },
	{ "LASTLOG",	NULL,		lastlog,		0 }, /* lastlog.c */
	{ "LEAVE",	"PART",		send_2comm,		0 },
	{ "LINKS",	"LINKS",	send_comm,		NONOVICEABBREV},
	{ "LIST",	"LIST",		funny_stuff,		0 },
	{ "LOAD",	"LOAD",		load,			0 },
	{ "LUSERS",	"LUSERS",	send_comm,		0 },
	{ "MAP",	"MAP",		send_comm,		0 },
	{ "ME",		NULL,		me,			0 },
	{ "MESG",	NULL,		extern_write,		0 },
	{ "MLIST",	NULL,		mlist,			0 },
	{ "MLOAD",	NULL,		mload,			0 },
	{ "MODE",	"MODE",		send_channel_com,	0 },
	{ "MOTD",	"MOTD",		send_comm,		0 },
	{ "MSG",	"PRIVMSG",	e_privmsg,		0 },
	{ "NAMES",	"NAMES",	funny_stuff,		0 },
	{ "NICK",	"NICK",		e_nick,			0 },
	{ "NOTE",	"NOTE",		send_comm,		0 },
	{ "NOTICE",	"NOTICE",	e_privmsg,		0 },
	{ "NOTIFY",	NULL,		notify,			0 }, /* notify.c */
	{ "ON",		NULL,		on,			0 }, /* hook.c */
	{ "OPER",	"OPER",		oper,			0 },
	{ "PARSEKEY",	NULL,		parsekeycmd,		0 },
	{ "PART",	"PART",		send_2comm,		0 },
	{ "PAUSE",	NULL,		e_pause,		0 },
	{ "PING",	NULL, 		pingcmd,		0 },
	{ "POP",	NULL,		pop_cmd,		0 },
	{ "PRETEND",	NULL,		pretend_cmd,		0 },
	{ "PUSH",	NULL,		push_cmd,		0 },
	{ "QUERY",	NULL,		query,			0 },
        { "QUEUE",      NULL,           queuecmd,               0 }, /* queue.c */
	{ "QUIT",	"QUIT",		e_quit,			NONOVICEABBREV},
	{ "QUOTE",	NULL,		quote,			0 },
	{ "RBIND",	"0",		rbindcmd,		0 }, /* keys.c */
        { "REALNAME",   NULL,           realname_cmd,           0 },
        { "RECONNECT",  NULL,           reconnect_cmd,          0 },
	{ "REDIRECT",	NULL,		redirect,		0 },
	{ "REHASH",	"REHASH",	send_comm,		0 },
	{ "REPEAT",	"REPEAT",	repeatcmd,		0 },
	{ "RESTART",	"RESTART",	send_comm,		0 },
	{ "RPING",	"RPING",	send_comm,		0 },
	{ "SAVE",	NULL,		save_settings,		0 },
	{ "SAY",	empty_string,	do_send_text,		0 },
	{ "SEND",	NULL,		do_send_text,		0 },
	{ "SENDLINE",	empty_string,	sendlinecmd,		0 },
	{ "SERVER",	NULL,		server,			0 }, /* server.c */
	{ "SET",	NULL,		set_variable,		0 }, /* vars.c */
	{ "SHIFT",	NULL,		shift_cmd,		0 },
	{ "SIGNOFF",	"QUIT",		e_quit,			NONOVICEABBREV},
	{ "SILENCE",	"SILENCE",	send_comm,		0 },
	{ "SLEEP",	NULL,		sleepcmd,		0 },
	{ "SQUIT",	"SQUIT",	send_2comm,		0 },
	{ "STACK",	NULL,		stackcmd,		0 }, /* stack.c */
	{ "STATS",	"STATS",	send_comm,		0 },
	{ "STUB",	"STUB",		stubcmd,		0 }, /* alias.c */
	{ "SUMMON",	"SUMMON",	send_comm,		0 },
	{ "SWITCH",	"SWITCH",	switchcmd,		0 }, /* if.c */
	{ "TIME",	"TIME",		send_comm,		0 },
	{ "TIMER",	"TIMER",	timercmd,		0 },
	{ "TOPIC",	"TOPIC",	send_topic,		0 },
	{ "TRACE",	"TRACE",	send_comm,		0 },
	{ "TYPE",	NULL,		type,			0 }, /* keys.c */
	{ "UNLESS",	"UNLESS",	ifcmd,			0 }, /* if.c */
	{ "UNSHIFT",	NULL,		unshift_cmd,		0 },
	{ "UNTIL",	"UNTIL",	whilecmd,		0 },
	{ "UPING",	"UPING",	send_comm,		0 },
	{ "USERHOST",	NULL,		userhost,		0 },
	{ "USERS",	"USERS",	send_comm,		0 },
	{ "VERSION",	"VERSION",	version,		0 },
	{ "WAIT",	NULL,		waitcmd,		0 },
	{ "WALLOPS",	"WALLOPS",	e_wallop,		0 },
	{ "WHICH",	"WHICH",	load,			0 },
	{ "WHILE",	"WHILE",	whilecmd,		0 }, /* if.c */
	{ "WHO",	"WHO",		who,			0 },
	{ "WHOIS",	"WHOIS",	whois,			0 },
	{ "WHOWAS",	"WHOWAS",	whois,			0 },
	{ "WINDOW",	NULL,		window,			0 }, /* window.c */
	{ "XECHO",	"XECHO",	echo,			0 },
	{ "XTYPE",	NULL,		xtypecmd,		0 },
	{ NULL,		NULL,		comment,		0 }
};

/* number of entries in irc_command array */
#define	NUMBER_OF_COMMANDS (sizeof(irc_command) / sizeof(IrcCommand)) - 2

#ifdef __STDC__
#define BUILT_IN_COMMAND(x) \
	void x (char *command, char *args, char *subargs)
#else
#define BUILT_IN_COMMAND(x) \
	void x (command, args, subargs) char *command, *args, *subargs;
#endif

/* 
 * Full scale abort.  Does a "save" into the filename in line, and
 * then does a coredump
 */
BUILT_IN_COMMAND(abortcmd)
{
	static void really_save _((char *, char *));
	char	*filename = next_arg(args, &args);

        filename = filename ? filename : "irc.aborted";
	save_which = SFLAG_ALIAS | SFLAG_BIND | SFLAG_ON | SFLAG_SET |
			     SFLAG_NOTIFY | SFLAG_DIGRAPH;
        really_save(filename, "y");
        abort();
}
 
/*
 * away: the /AWAY command.  Keeps track of the away message locally, and
 * sends the command on to the server.
 */
BUILT_IN_COMMAND(away)
{
	char	*arg = NULL;
	int	flag = AWAY_ONE;
	int	i;

	if (*args)
	{
		if ((*args == '-') || (*args == '/'))
		{
			arg = index(args, ' ');
			if (arg)
				*arg++ = '\0';
			else
				arg = empty_string;

			if (0 == my_strnicmp(args+1, "A", 1))	/* all */
			{
				flag = AWAY_ALL;
				args = arg;
			}
			else if (0 == my_strnicmp(args+1, "O", 1)) /* one */
			{
				flag = AWAY_ONE;
				args = arg;
			}
			else
			{
				say("%s: %s unknown flag", command, args);
				return;
			}
		}
	}
	if (flag == AWAY_ALL)
	{
		if (*args)
		{
			away_set = 1;
			MarkAllAway(command, args, subargs);
		}
		else
		{
			away_set = 0;
			for(i = 0; (i < number_of_servers); i++)
				if (server_list[i].whois_stuff.away)
					new_free(&(server_list[i].away));
		}
	}
	else
	{
		send_to_server("%s :%s", command, args);
		if (*args)
		{
			away_set = 1;
			malloc_strcpy(&(server_list[curr_scr_win->server].away), args);
		}
		else
		{
			new_free(&(server_list[curr_scr_win->server].away));
			away_set = 0;
			for(i = 0; (i < number_of_servers) && !away_set ; i++)
				if (server_list[i].read != -1 && server_list[i].away)
					away_set = 1;
		}
	}
	update_all_status();
}

BUILT_IN_COMMAND(beepcmd)
{
	term_beep();
}

BUILT_IN_COMMAND(cd)
{
	char	*arg,
		*expand;
	char buffer[BIG_BUFFER_SIZE + 1];

	if ((arg = next_arg(args, &args)) != NULL)
	{
		if ((expand = expand_twiddle(arg)) != NULL)
		{
			if (chdir(expand))
				say("CD: %s", sys_errlist[errno]);
			new_free(&expand);
		}
		else
			say("CD: No such user");
	}
	getcwd(buffer, BIG_BUFFER_SIZE+1);
	say("Current directory: %s", buffer);
}

/* clear: the CLEAR command.  Figure it out */
BUILT_IN_COMMAND(e_clear)
{
	char	*arg;
	int	all = 0,
		unhold = 0;

	while ((arg = next_arg(args, &args)) != NULL)
	{
	/* -ALL and ALL here becuase the help files used to be wrong */
		if (!my_strnicmp(arg, "A", 1) || !my_strnicmp(arg+1, "A", 1))
			all = 1;
	/* UNHOLD */
		else if (!my_strnicmp(arg+1, "U", 1))
			unhold = 1;
		else
			say("Unknown flag: %s", arg);
	}
	if (all)
		clear_all_windows(unhold);
	else
	{
		if (unhold)
			hold_mode((Window *) 0, OFF, 1);
		clear_window_by_refnum(0);
	}
	update_input(UPDATE_JUST_CURSOR);
}

/* comment: does the /COMMENT command, useful in .ircrc */
BUILT_IN_COMMAND(comment)
{
	/* nothing to do... */
}

BUILT_IN_COMMAND(ctcp)
{
	char	*to;
	char	*stag;
	int	tag;
	int	type;

	if ((to = next_arg(args, &args)) != NULL)
	{
		if (!strcmp(to, "*"))
			if ((to = get_channel_by_refnum(0)) == NULL)
				to = "0";

		if ((stag = next_arg(args, &args)) != NULL)
			tag = get_ctcp_val(upper(stag));
		else
			tag = CTCP_VERSION;

		if ((type = in_ctcp()) == -1)
			say("You may not use the CTCP command from an ON CTCP_REPLY!");
		else
		{
			if (args && *args)
				send_ctcp(type, to, tag, "%s", args);
			else
				send_ctcp(type, to, tag, NULL);
		}
	}
	else
		say("Usage: /CTCP [<nickname>|*] (<request>)");
}

BUILT_IN_COMMAND(dcc)
{
	if (*args)
		process_dcc(args);
	else
		dcc_list((char *) NULL);
}

BUILT_IN_COMMAND(deop)
{
	send_to_server("MODE %s -o", get_server_nickname(from_server));
}

BUILT_IN_COMMAND(describe)
{
	char	*target;

	target = next_arg(args, &args);
	if (target && args && *args)
	{
		int	old;
		int	from_level;
		char	*message;

		message = args;
		send_ctcp(CTCP_PRIVMSG, target, CTCP_ACTION, "%s", message);

		old = set_lastlog_msg_level(LOG_ACTION);
		from_level = message_from_level(LOG_ACTION);
		if (do_hook(SEND_ACTION_LIST, "%s %s", target, message))
			put_it("* -> %s: %s %s", target, get_server_nickname(from_server), message);
		set_lastlog_msg_level(old);
		message_from_level(from_level);
	}
	else
		say("Usage: /DESCRIBE <target> <action description>");
}

BUILT_IN_COMMAND(do_send_text)
{
	char	*tmp;

	if (command)
		tmp = get_channel_by_refnum(0);
	else
		tmp = get_target_by_refnum(0);

	send_text(tmp, args, NULL, 1);
}

BUILT_IN_COMMAND(disconcmd)
{
	if (from_server == -1)
	{
		say("Cannot disconnect: You are not connected.");
		return;
	}

        say("Disconnecting from server %d", from_server);
	if (!args || !*args)
		args = "Reconnecting";
        close_server(from_server, args);
        clean_whois_queue();
        window_check_servers();
	if (!connected_to_server)
		say("You are not connected to a server, Use /SERVER to connect.");
}

/*
 * e_channel: does the channel command.  I just added displaying your current
 * channel if none is given 
 */
BUILT_IN_COMMAND(e_channel)
{
	char	*chan;

	message_from(NULL, LOG_CURRENT);
	if ((chan = next_arg(args, &args)) != NULL)
	{
		if (my_strnicmp(chan, "-i", 2) == 0)
		{
			if (invite_channel)
				send_to_server("%s %s",command, invite_channel);
			else
				say("You have not been invited to a channel!");
		}
		else
		{
			if (is_on_channel(chan, get_server_nickname(from_server)))
			{
				/* XXXX -
				   right here we want to check to see if the
				   channel is bound to this window.  if it is,
				   we set it as the default channel.  If it
				   is not, we warn the user that we cant do it
				 */
				if (is_bound_anywhere(chan) &&
				    !(is_bound_to_window(curr_scr_win, chan)))
					say("Channel %s is bound to another window", chan);

				else
				{
					is_current_channel(chan, 1);
					say("You are now talking to channel %s", 
						set_channel_by_refnum(0, chan));
					update_all_windows();
				}
			}
			else
			{
				send_to_server("%s %s %s", command, chan, args);
				if (!is_bound(chan, curr_scr_win->server))
					malloc_strcpy(&curr_scr_win->waiting_channel, chan);
			}
		}
	}
	else
		list_channels();

	message_from(NULL, LOG_CRAP);
}

/*
 * e_nick: does the /NICK command.  Records the users current nickname and
 * sends the command on to the server 
 */
BUILT_IN_COMMAND(e_nick)
{
	char	*nick;

	if (!(nick = next_arg(args, &args)))
	{
		say("Your nickname is %s", get_server_nickname(get_window_server(0)));
		return;
	}
	if ((nick = check_nickname(nick)) != NULL)
	{
		in_e_nick = 1;
		send_to_server("NICK %s", nick);
		/* XXXX - this is not the right thing to do here. */
                if (attempting_to_connect)
                        set_server_nickname(get_window_server(0),nick);
	}
	else
		say("The nickname you specified is not a legal nickname.");
}

/*
 * This is a quick and dirty hack (emphasis on dirty) that i whipped up
 * just for the heck of it.  I feel really dirty about using the add_timer
 * call (bletch!) to fake a timeout for io().  The better answer would be
 * for io() to take an argument specifying the maximum threshold for a
 * timeout, but i didnt want to deal with that here.  So i just add a
 * dummy timer event that does nothing (wasting two function calls and
 * about 20 bytes of memory), and call io() until the whole thing blows
 * over.  Nice and painless.  You might want to try this instead of /sleep,
 * since this is (obviously) non-blocking.  This also calls time() for every
 * io event, so that might also start adding up.  Oh well, TIOLI.
 *
 * Without an argument, it waits for the user to press a key.  Any key.
 * and the key is accepted.  Thats probably not right, ill work on that.
 */
BUILT_IN_COMMAND(e_pause)
{
	char *sec;
	int seconds;
	time_t start;

	if (!(sec = next_arg(args, &args)))
	{
		cursor_to_input();
		get_a_char();
		input_backspace(0, NULL);	/* XXXX */
		return;
	}

	seconds = atoi(sec);
	time(&start);

	/* 
	 * I use comment here simply becuase its not going to mess
	 * with the arguments.
	 */
	add_timer("", seconds, (int (*)(void *))comment, NULL, NULL);
	while (time(NULL) < start + seconds)
		io();
}

/*
 * e_privmsg: The MSG command, displaying a message on the screen indicating
 * the message was sent.  Also, this works for the NOTICE command. 
 */
BUILT_IN_COMMAND(e_privmsg)
{
	char	*nick;

	if ((nick = next_arg(args, &args)) != NULL)
	{
		if (!strcmp(nick, "."))
		{
			if (!(nick = sent_nick))
			{
				say("You have not sent a message to anyone yet");
				return;
			}
		}
		else if (!strcmp(nick, ",")) 
		{
			if (!(nick = recv_nick))
			{
				say("You have not received a message from anyone yet");
				return;
			}
		}
		else if (!strcmp(nick, "*") && (!(nick = get_channel_by_refnum(0))))
			nick = "0";
		send_text(nick, args, command, 1);
		malloc_strcpy(&sent_body, args);
	}
	else
		say("Usage: %s <nickname|channel|*> <text>", command);
}

/* e_quit: The /QUIT, /EXIT, etc command */
BUILT_IN_COMMAND(e_quit)
{
	int	max;
	int	i;
	char	*Reason,
 	 	temp[256];
 
#ifdef WINTRHAWK
  	/* New default signoff message >=============::
  	   where %s is irc_version      ::===========''
  	   and the whole thing gets     ||
	   copied to temp which in turn ||
	   gets copied to Reason in the ||
	   event the user doesn't supply||
	   a signoff message            ||
		   	       v=============================v
	*/
	sprintf(temp, "ircII %s didn't want to be here!", irc_version);
#else
	sprintf(temp, "ircII%s --- Bloatware at its finest.", irc_version);
#endif
	Reason = (*args ? args : temp); 
	max = server_list_size();
	for (i = 0; i < max; i++)
	{
		if (is_server_connected(i))
		{
			from_server = i;
			send_to_server("QUIT :%s", Reason);
		}
	}
	irc_exit(1, Reason);
}

/* e_wallop: used for WALLOPS (undernet only command) */
BUILT_IN_COMMAND(e_wallop)
{
	message_from(NULL, LOG_WALLOP);

	/* 
	 * I took out the totaly rediculous lamage i had in here before.
	 * As i was realized after being rebuked, EPIC's goal is to trust
	 * the user, even if we have a pretty good idea that they are doing
	 * something wrong -- "good intentions" lead to anal decisions that
	 * users end up resenting.  So here it is.  You can send /WALLOPS
	 * till your ears bleed... but all you'll get is a warning telling
	 * you you're probably not doing what you expect.
	 */
#ifdef ENFORCE_STRICTER_PROTOCOL
	if (!in_on_who)
#endif
		send_to_server("%s :%s", command, args);

	if (!get_server_operator(current_screen->current_window->server))
	{
		static int warned = 0;
		if (!warned)
		{
			say("WARNING: Using the /WALLOPS command as a non-oper probably will not do what you expect.");
			warned = 1;
		}
	}

	message_from(NULL, LOG_CRAP);
}

/*
 * echo: simply displays the args to the screen, or, if it's XECHO,
 * processes the flags first, then displays the text on the screen
 */
BUILT_IN_COMMAND(echo)
{
	unsigned int	display;
	int	lastlog_level = 0;
	int	from_level = 0;
	char	*flag_arg;
	int	temp;
	Window *old_to_window;

	old_to_window = to_window;
	if (command && *command == 'X')
	{
		while (args && (*args == '-' || *args == '/'))
		{
			flag_arg = next_arg(args, &args);
			switch (toupper(flag_arg[1]))
			{
				case 'C':
				{
					to_window = curr_scr_win;
					break;
				}
				case 'L':
				{
					if (!(flag_arg = next_arg(args, &args)))
						break;
					if ((temp = parse_lastlog_level(flag_arg)) != 0)
					{
						lastlog_level = set_lastlog_msg_level(temp);
						from_level = message_from_level(temp);
					}
					break;
				}
				case 'W':
				{
					if (!(flag_arg = next_arg(args, &args)))
						break;
					if (isdigit(*flag_arg))
						to_window = get_window_by_refnum(atoi(flag_arg));
					else
						to_window = get_window_by_name(flag_arg);
					break;
				}
			}
			if (!args)
				args = empty_string;
		}
	}
	display = window_display;
	window_display = 1;
	put_it("%s", args);
	window_display = display;
	if (lastlog_level)
	{
		set_lastlog_msg_level(lastlog_level);
		message_from_level(from_level);
	}
	to_window = old_to_window;
}

BUILT_IN_COMMAND(evalcmd)
{
	parse_line(NULL, args, subargs ? subargs : empty_string, 0, 0);
}

/* flush: flushes all pending stuff coming from the server */
BUILT_IN_COMMAND(flush)
{
	if (get_int_var(HOLD_MODE_VAR))
	{
		while (curr_scr_win->held_lines)
			remove_from_hold_list(curr_scr_win);
		hold_mode((Window *) 0, OFF, 1);
	}
	say("Standby, Flushing server output...");
	flush_server();
	say("Done");
}

BUILT_IN_COMMAND(funny_stuff)
{
	char	*arg,
		*stuff;
	int	min = 0,
		max = 0,
		flags = 0;

	stuff = empty_string;
	while ((arg = next_arg(args, &args)) != NULL)
	{
		if (*arg == '/' || *arg == '-')
		{
			if (my_strnicmp(arg+1, "MA", 2) == 0)	/* MAX */
			{
				if ((arg = next_arg(args, &args)) != NULL)
					max = atoi(arg);
			}
			else if (my_strnicmp(arg+1, "MI", 2) == 0) /* MIN */
			{
				if ((arg = next_arg(args, &args)) != NULL)
					min = atoi(arg);
			}
			else if (my_strnicmp(arg+1, "A", 1) == 0) /* ALL */
				flags &= ~(FUNNY_PUBLIC | FUNNY_PRIVATE);
			else if (my_strnicmp(arg+1, "PU", 2) == 0) /* PUBLIC */
			{
				flags |= FUNNY_PUBLIC;
				flags &= ~FUNNY_PRIVATE;
			}
			else if (my_strnicmp(arg+1, "PR", 2) == 0) /* PRIVATE */
			{
				flags |= FUNNY_PRIVATE;
				flags &= ~FUNNY_PUBLIC;
			}
			else if (my_strnicmp(arg+1, "T", 1) == 0)	/* TOPIC */
				flags |= FUNNY_TOPIC;
			else if (my_strnicmp(arg+1, "W", 1) == 0)	/* WIDE */
				flags |= FUNNY_WIDE;
			else if (my_strnicmp(arg+1, "U", 1) == 0)	/* USERS */
				flags |= FUNNY_USERS;
			else if (my_strnicmp(arg+1, "N", 1) == 0)	/* NAME */
				flags |= FUNNY_NAME;
			else
				stuff = arg;
		}
		else stuff = arg;
	}
	set_funny_flags(min, max, flags);
	if (strcmp(stuff, "*") == 0)
		if (!(stuff = get_channel_by_refnum(0)))
			stuff = empty_string;
	if (index(stuff, '*'))
	{
		funny_match(stuff);
		send_to_server("%s %s", command, empty_string);
	}
	else
	{
		funny_match(NULL);
		send_to_server("%s %s", command, stuff);
	}
}

BUILT_IN_COMMAND(hook)
{
	if (*args)
		do_hook(HOOK_LIST, "%s", args);
	else
		say("Usage: /HOOK [text]");
}

/*
 * Modified /hostname by Thomas Morgan (tmorgan@pobox.com)
 */
BUILT_IN_COMMAND(e_hostname)
{
	if (args && *args)
	{
		struct hostent *hp;

		malloc_strcpy(&LocalHostName, args);

		if ((hp = gethostbyname(LocalHostName)))
			bcopy(hp->h_addr, (void *)&LocalHostAddr, sizeof(LocalHostAddr));

		say("Local host name is now %s", LocalHostName);
	}
	else
		say("Local Host name is %s",
			(LocalHostName) ? LocalHostName : hostname);
}




/*
 * info: does the /INFO command.  I just added some credits
 * I updated most of the text -phone, feb 1993.
 */
BUILT_IN_COMMAND(info)
{
	if (!args || !*args)
	{
		say("ircii: Originally written by Michael Sandrof");
		say("       Versions 2.1 to 2.2pre7 by Troy Rollo");
		say("       Development continued by Matthew Green");
		say("       e-mail: mrg@mame.mu.oz.au  irc: phone");
		say("       copyright (c) 1990-1994, 1995");
		say("");
		say("	    Contact the EPIC project (%s)", EMAIL_CONTACT);
		say("	    for problems with this or any other EPIC client");
		say("");	
		say("       do a /help ircii copyright for the full copyright");
		say("       ircii includes software developed by the university");
		say("       of california, berkeley and its contributors");
		say("");
		say("The EPIC Project:");
		say("	    \tJeremy Nelson        <nelson@cs.uwp.edu>");
		say("	    \tJake Khuon           <khuon@merit.net>");
		say("       \tRobert Chady         <chady@concentric.net>");
		say("	    \tJames Sneeringer     <jvs@ocslink.com>");
		say("	    \tMurple               <murple@k-rad.erols.com>");
		say("       \tScott Kilau	   <shiek@chipsworld.bridge.net>");
		say("       \tColten Edwards       <edwac@sk.sympatico.ca>");
		say("       \tBrian Hauber         <bhauber@frenzy.com>");
		say("       \tDoug McLaren         <dougmc@comco.com>");
		say("	    \tDennis Moore         <fis4477@tamaix.tamu.edu>");
		say("       \tRon Ritzman          <crimedog@cdc3.cdc.com>");
		say("       \tScott Conway         <sconway@silver.ucs.indiana.edu>");
		say("       \tBrian Edgington      <edge@wasatch.com>");
		say("       \tGenesisK		   <genesisk@eden.com>");
		say("");
		say("A special thank you to all who rabidly use and support");
		say("the EPIC client and what it stands for");
		say("");
		say("ircii contributors");
		say("");
		say("       \tMichael Sandrof       Mark T. Dameu");
		say("       \tStellan Klebom        Carl v. Loesch");
		say("       \tTroy Rollo            Martin  Friedrich");
		say("       \tMichael Weber         Bill Wisner");
		say("       \tRiccardo Facchetti    Stephen van den Berg");
		say("       \tVolker Paulsen        Kare Pettersson");
		say("       \tIan Frechette         Charles Hannum");
		say("       \tMatthew Green         Christopher Williams");
		say("       \tJonathan Lemon        Brian Koehmstedt");
		say("       \tNicolas Pioch         Brian Fehdrau");
		say("       \tDarren Reed           Jeff Grills");
		say("	    \tChris Williams");
	}
	send_to_server("%s %s", command, args?args:empty_string);
}

/*
 * inputcmd:  the INPUT command.   Takes a couple of arguements...
 * the first surrounded in double quotes, and the rest makes up
 * a normal ircII command.  The command is evalutated, with $*
 * being the line that you input.  Used add_wait_prompt() to prompt
 * the user...  -phone, jan 1993.
 */
BUILT_IN_COMMAND(inputcmd)
{
	char	*prompt;
	int	wait_type;

	if (!args || !*args)
		return;
	
	if (*args++ != '\"')
	{
		say("Need \" to begin prompt for INPUT");
		return;
	}

	prompt = args;
	if ((args = index(prompt, '"')) != NULL)
		*args++ = '\0';
	else
	{
		say("Missing \" in INPUT");
		return;
	}

	for (; *args == ' '; args++)
		;

	if (!strcmp(command, "INPUT"))
		wait_type = WAIT_PROMPT_LINE;
	else
		wait_type = WAIT_PROMPT_KEY;

	add_wait_prompt(prompt, eval_inputlist, args, wait_type);
}

#ifdef __STDC__
void ison_ison(char *notused, char *nicklist)
#else
void ison_ison(notused, nicklist)
char *notused, nicklist;
#endif
{
	if (do_hook(current_numeric, "%s", nicklist))
		put_it("%s Currently online: %s", numeric_banner(), nicklist);
}

BUILT_IN_COMMAND(ison)
{
	if (!args[strspn(args, " ")])
		args = get_server_nickname(from_server);
	add_ison_to_whois(args, ison_ison);

}

/*
 * load: the /LOAD command.  Reads the named file, parsing each line as
 * though it were typed in (passes each line to parse_line). 
 */
BUILT_IN_COMMAND(load)
{
	FILE	*fp;
	char	*filename,
		*expanded = NULL;
	int	flag = 0;
        int     paste_level = 0;
	char	*start,
		*current_row = NULL,
		buffer[BIG_BUFFER_SIZE + 1];
	int	no_semicolon = 1;
	char	*irc_path;
	int	display;
        int     ack = 0;

	irc_path = get_string_var(LOAD_PATH_VAR);
	if (!irc_path)
	{
		say("LOAD_PATH has not been set");
		return;
	}

	if (load_depth == MAX_LOAD_DEPTH)
	{
		say("No more than %d levels of LOADs allowed", MAX_LOAD_DEPTH);
		return;
	}
	load_depth++;
	status_update(0);

	/* 
	 * We iterate over the whole list -- if we use the -args flag, the
	 * we will make a note to exit the loop at the bottom after we've
	 * gone through it once...
	 */
	while (args && *args && (filename = next_arg(args, &args)))
	{
		/* 
		   If we use the args flag, then we will get the next
		   filename (via continue) but at the bottom of the loop
		   we will exit the loop 
		 */
		if (my_strnicmp(filename, "-args", strlen(filename)) == 0)
		{
			flag = 1;
			continue;
		}
		else if ((expanded = expand_twiddle(filename)))
		{
			if (!(fp = uzfopen (&expanded, irc_path)))
			{
				/* uzfopen emits an error if the file
				 * is not found, so we dont have to. */
				status_update(1);
				load_depth--;
				new_free(&expanded);
				return;
			}
			if (command && *command == 'W')
			{
				say ("%s", expanded);
				if (fp)
					fclose (fp);
				status_update(1);
				load_depth--;
				new_free(&expanded);
				return;
			}
/* Reformatted by jfn */
/* *_NOT_* attached, so dont "fix" it */
{
	int	in_comment 	= 0;
	int	comment_line 	= -1;
	int 	line 		= 1;
	int 	paste_line	= -1;

	display = window_display;
	window_display = 0;
	current_row = NULL;

	for (;;line++)
	{
	       if (fgets(buffer,BIG_BUFFER_SIZE / 2,fp))
	       {
			int     len;
			char    *ptr;

			for (start = buffer; my_isspace(*start); start++)
				;
			if (!*start || *start == '#')
				continue;

			len = strlen(start);
			/*
			 * this line from stargazer to allow \'s in scripts for continued
			 * lines <spz@specklec.mpifr-bonn.mpg.de>
			 */
			/* 
			   If we have \\ at the end of the line, that
			   should indicate that we DONT want the slash to 
			   escape the newline (hop)

			   We cant just do start[len-2] because we cant say
			   what will happen if len = 1... (a blank line)

			   SO.... 
			   If the line ends in a newline, and
			   If there are at least 2 characters in the line
				and the 2nd to the last one is a \ and,
			   If there are EITHER 2 characters on the line or
				the 3rd to the last character is NOT a \ and,
			   If the line isnt too big yet and,
			   If we can read more from the file,
			   THEN -- adjust the length of the string
			*/
			while ( (start[len-1] == '\n') && 
				(len >= 2 && start[len-2] == '\\') &&
				(len < 3 || start[len-3] != '\\') && 
				(len < BIG_BUFFER_SIZE / 2) && 
				(fgets(&(start[len-2]), BIG_BUFFER_SIZE / 2 - len, fp)))
			{
				len = strlen(start);
				line++;
			}

			if (start[len - 1] == '\n')
				start[--len] = '\0';

			while (start && *start)
			{
				char    *optr = start;

				/* Skip slashed brackets */
				while ((ptr = sindex(optr, "{};/")) && 
				      ptr != optr && ptr[-1] == '\\')
					optr = ptr+1;

				/* if no_semicolon is set, we will not attempt
				 * to parse this line, but will continue
				 * grabbing text
				 */
				if (no_semicolon)
					no_semicolon = 0;
				else if ((!ptr || (ptr != start || *ptr == '/')) && current_row)
				{
					if (!paste_level)
					{
						parse_line(NULL, current_row, flag ? args : NULL, 0, 0);
						new_free(&current_row);
					}
					else if (!in_comment)
						malloc_strcat(&current_row, ";");
				}

				if (ptr)
				{
					char    c = *ptr;

					*ptr = '\0';
					if (!in_comment)
						malloc_strcat(&current_row, start);
					*ptr = c;

					switch (c)
					{
		/* switch statement tabbed back */
case '/' :
{
	/* If we're in a comment, any slashes that arent preceeded by
	   a star is just ignored (cause its in a comment, after all >;) */
	if (in_comment)
	{
		/* ooops! cant do ptr[-1] if ptr == optr... doh! */
		if ((ptr > start) && (ptr[-1] == '*'))
		{
			in_comment = 0;
			comment_line = -1;
		}
		else
			break;
	}
	/* We're not in a comment... should we start one? */
	else
	{
		if ((ptr[1] == '*')
#if 0
#ifdef COMMENT_HACK
			/* This hack (at the request of Kanan) makes it
			   so you can only have comments at the BEGINNING
			   of a line, and any midline comments are ignored.
			   This is required to run lame script packs that
			   do ascii art crap (ie, phoenix, textbox, etc) */
			&& (ptr == start)
#endif
#endif
			&& (!get_int_var(COMMENT_HACK_VAR) || ptr == start)
					)
		{
			/* Yep. its the start of a comment. */
			in_comment = 1;
			comment_line = line;
		}
		else
		{
			/* Its NOT a comment. Tack it on the end */
			malloc_strcat(&current_row, "/");

			/* Is the / is at the EOL? */
			if (ptr[1] == '\0')
			{
				/* If we are NOT in a block alias, */
				if (!paste_level)
				{
					/* Send the line off to parse_line */
					parse_line(NULL, current_row, flag ? args : NULL, 0, 0);
					new_free(&current_row);
					ack = 0; /* no semicolon.. */
				}
				else
					/* Just add a semicolon and keep going */
					ack = 1; /* tack on a semicolon */
			}
		}
	}
	no_semicolon = 1 - ack;
	ack = 0;
	break;
}
case '{' :
{
	if (in_comment)
		break;

	/* If we are opening a brand new {} pair, remember the line */
	if (!paste_level)
		paste_line = line;

	paste_level++;
	if (ptr == start)
		malloc_strcat(&current_row, " {"); /* } */
	else
		malloc_strcat(&current_row, "{"); /* } */
	no_semicolon = 1;
	break;
}
case '}' :
{
	if (in_comment)
		break;
	if (!paste_level)
		yell("Unexpected } in %s, line %d",expanded,line);
	else
	{
		--paste_level;
		/* If we're back to "level 0", then reset the paste line */
		if (!paste_level)
			paste_line = -1;
		malloc_strcat(&current_row, "}");
		no_semicolon = ptr[1] ? 1 : 0;
	}
	break;
}
case ';' :
{
	if (in_comment)
		break;
	if ((*(ptr+1) == '\0') && (!paste_level))
	{
		parse_line(NULL, current_row, flag ? args : NULL, 0, 0);
		new_free(&current_row);
	}
	else
		malloc_strcat(&current_row, ";");
	no_semicolon = 1;
	break;
}
	/* End of reformatting */
					}
					start = ptr+1;
				}
				else
				{
					if (!in_comment)
						malloc_strcat(&current_row, start);
					start = NULL;
				}
			}
		}
		else
			break;
	}
	if (in_comment)
		yell("File %s ended with an unterminated comment in line %d", expanded, comment_line);
	if (current_row)
	{
		if (paste_level)
			yell("Unexpected EOF in %s trying to match '{' at line %d",
					expanded, paste_line);
		else
			parse_line(NULL,current_row, flag ? args: NULL,0,0);
		new_free(&current_row);
	}
	new_free(&expanded);
	fclose(fp);
	if (get_int_var(DISPLAY_VAR))
	       window_display = display;
}
/* End of reformatting */
		}
		else
			say("Unknown user");
		/* 
		 * brute force -- if we are using -args, load ONLY one file
		 * then exit the while loop.  We could have done this
		 * by assigning args to NULL, but that would only waste
		 * cpu cycles by relooping...
		 */
		if (flag)
			break;
	}	/* end of the while loop that allows you to load
		   more then one file at a time.. */
	status_update(1);
	load_depth--;
}


/*
 * The /me command.  Does CTCP ACTION.  Dont ask me why this isnt the
 * same as /describe...
 */
BUILT_IN_COMMAND(me)
{
	if (args && *args)
	{
		char	*target;

		if ((target = get_target_by_refnum(0)) != NULL)
		{
/*			int	from_level; */
			int	old;
			char	*message;

			if (*target == '=' || !strncmp(target, get_string_var(CMDCHARS_VAR), 1))
			{
				if (!(target = get_channel_by_refnum(0)))
				{
					say("No target, neither channel nor query");
					return;
				}
			}
			message = args;
			send_ctcp(CTCP_PRIVMSG, target, CTCP_ACTION, "%s", message);

			message_from(target, LOG_ACTION);
			old = set_lastlog_msg_level(LOG_ACTION);
/*			from_level = message_from_level(LOG_ACTION);*/
			if (do_hook(SEND_ACTION_LIST, "%s %s", target, message))
				put_it("* %s %s", get_server_nickname(from_server), message);
			set_lastlog_msg_level(old);
			message_from(NULL, LOG_CRAP);
/*			message_from_level(from_level);*/
		}
		else
			say("No target, neither channel nor query");
	}
	else
		say("Usage: /ME <action description>");
}

BUILT_IN_COMMAND(mlist)
{
	char	*menu;

	while ((menu = new_next_arg(args, &args)) != NULL)
		(void) ListMenu(menu);
}

BUILT_IN_COMMAND(mload)
{
	char	*file;

	while ((file = next_arg(args, &args)) != NULL)
		load_menu(file);
}

/*
   This isnt a command, its used by the wait command.  Since its extern,
   and it doesnt use anything static in this file, im sure it doesnt
   belong here.
 */
extern void oh_my_wait (void)
{
	int w_server = from_server;
	if (w_server == -1)
		w_server = primary_server;

	if (is_server_connected(w_server))
	{
		int local_waiting = ++waiting;
		send_to_server("%s", lame_wait_nick, waiting);
		/*set_input(empty_string);*/
		while (local_waiting <= waiting)
			io();
	}
}


#ifdef __STDC__
static 	void	oper_password_received (char *data, char *line)
#else
static	void 	oper_password_received(data, line)
	char	*data, *line;
#endif
{
	send_to_server("OPER %s %s", data, line);
}

/* oper: the OPER command.  */
BUILT_IN_COMMAND(oper)
{
	char	*password;
	char	*nick;

	oper_command = 1;
	if (!(nick = next_arg(args, &args)))
		nick = nickname;
	if (!(password = next_arg(args, &args)))
	{
		add_wait_prompt("Operator Password:",
			oper_password_received, nick, WAIT_PROMPT_LINE);
		return;
	}
	send_to_server("OPER %s %s", nick, password);
}

/* pingcmd: ctcp ping, duh - phone, jan 1993. */
BUILT_IN_COMMAND(pingcmd)
{
	struct timeval t;
	char buffer[64];

	gettimeofday(&t, NULL);
	sprintf(buffer, "%s PING %ld %ld", args, t.tv_sec, t.tv_usec);
	ctcp(NULL, buffer, empty_string);
}

BUILT_IN_COMMAND(pop_cmd)
{
        char *blah = function_pop(args);
        new_free(&blah);
}

BUILT_IN_COMMAND(pretend_cmd)
{
	parse_server(args);
}


BUILT_IN_COMMAND(push_cmd)
{
        char *blah = function_push(args);
        new_free(&blah);
}

/*
 * query: the /QUERY command.  Works much like the /MSG, I'll let you figure
 * it out.
 */
BUILT_IN_COMMAND(query)
{
	char	*nick,
		*rest;

	message_from(NULL, LOG_CURRENT);
	if ((nick = next_arg(args, &rest)) != NULL)
	{
		if (strcmp(nick, ".") == 0)
		{
			if (!(nick = sent_nick))
			{
				say("You have not messaged anyone yet");
				return;
			}
		}
		else if (strcmp(nick, ",") == 0)
		{
			if (!(nick = recv_nick))
			{
				say("You have not recieved a message from \
						anyone yet");
				return;
			}
		}
		else if ((strcmp(nick, "*") == 0) && !(nick = get_channel_by_refnum(0)))
		{
			say("You are not on a channel");
			return;
		}

		if (*nick == '%')
		{
			if (is_process(nick) == 0)
			{
				say("Invalid processes specification");
				message_from(NULL, LOG_CRAP);
				return;
			}
		}
		say("Starting conversation with %s", nick);
		set_query_nick(nick);
	}
	else
	{
		if (query_nick())
		{
			say("Ending conversation with %s", query_nick());
			set_query_nick(NULL);
		}
		else
			say("Usage: /QUERY <nickname>");
	}
	update_input(UPDATE_ALL);
	message_from(NULL, LOG_CRAP);
}

/*
 * quote: handles the QUOTE command.  args are a direct server command which
 * is simply send directly to the server 
 */
BUILT_IN_COMMAND(quote)
{
	if (
#ifdef ENFORCE_STRICTER_PROTOCOL
	    !in_on_who && !doing_privmsg &&
#endif
					    args && *args)
		send_to_server("%s", args);
}

/* This code is courtesy of Richie B. (richie@morra.et.tudelft.nl) */
/*
 * REALNAME command. Changes the current realname. This will only be parsed
 * to the server when the client is connected again.
 */
BUILT_IN_COMMAND(realname_cmd)
{
        if (*args)
	{
                strmcpy(realname, args, REALNAME_LEN);
		say("Realname at next server connnection: %s", realname);
	}
	else
		say("Usage: /REALNAME [text of realname]");
}

/*
 * RECONNECT command. Closes the server, and then reconnects again.
 * Works also while connected to multiple servers. It only reconnects to the
 * current server number (which is stored in from_server). 
 * This command puts the REALNAME command in effect.
 */
BUILT_IN_COMMAND(reconnect_cmd)
{
        char scommnd[4];

	if (from_server == -1)
	{
		say("Reconnect to what server? (You're not connected)");
		return;
	}
        say("Reconnecting to server %d", from_server);
        sprintf(scommnd, "+%i", from_server);
	if (!args || !*args)
		args = "Reconnecting";
        close_server(from_server, args);
        clean_whois_queue();
        window_check_servers();
        server(NULL, scommnd, empty_string);
}
/* End of contributed code */

BUILT_IN_COMMAND(redirect)
{
	char	*who;

	if ((who = next_arg(args, &args)) != NULL)
	{
		if (!strcmp(who, "*") && !(who = get_channel_by_refnum(0)))
		{
			say("Must be on a channel to redirect to '*'");
			return;
		}
		if (!my_stricmp(who, get_server_nickname(from_server)))
		{
			say("You may not redirect output to yourself");
			return;
		}

		/*
		 * Added by Chaos: Fixes the problem with /redirect when 
		 * redirecting to a dcc chat sessions that isn't active or 
		 * doesn't exist.
		 */
		if ((*who == '=') && !dcc_active(who + 1)) 
		{
			say("You don't have an active DCC CHAT to %s",who + 1);
			return;
		}

		/*
		 * Turn on redirect, and do the thing.
		 */
		window_redirect(who, from_server);
		server_list[from_server].sent = 0;
		parse_line((char *) 0, args, (char *) 0, 0, 0);

		/*
		 * If we've queried the server, then we wait for it to
		 * reply, otherwise we're done.
		 */
		if (server_list[from_server].sent)
			send_to_server("%s%d", current_screen->redirect_token, current_screen->screennum);
		else
			window_redirect(NULL, from_server);
	}
	else
		say("%s", "Usage: REDIRECT <nick|channel|%process> <cmd>");
}

/* This generates a file of your ircII setup */
#ifdef __STDC__
static void really_save (char *ircrc_file, char *line)
#else
static	void really_save(ircrc_file, line)
	char	*ircrc_file, *line;
#endif
{
	FILE	*fp;
	int	save_do_all = 0;

	if (*line != 'y' && *line != 'Y')
		return;
	if ((fp = fopen(ircrc_file, "w")) != NULL)
	{
		if (save_which & SFLAG_BIND)
			save_bindings(fp, save_do_all);
		if (save_which & SFLAG_ON)
			save_hooks(fp, save_do_all);
		if (save_which & SFLAG_NOTIFY)
			save_notify(fp);
		if (save_which & SFLAG_SET)
			save_variables(fp, save_do_all);
		if (save_which & SFLAG_ALIAS)
			save_aliases(fp, save_do_all);
		if (save_which & SFLAG_DIGRAPH)
			save_digraphs(fp);
		fclose(fp);
		say("IRCII settings saved to %s", ircrc_file);
	}
	else
		say("Error opening %s: %s", ircrc_file, sys_errlist[errno]);
}

/* save_settings: saves the current state of IRCII to a file */
BUILT_IN_COMMAND(save_settings)
{
	char	*arg;
	int	all = 1;
	char	buffer[BIG_BUFFER_SIZE+1];

	save_which = save_do_all = 0;
	while ((arg = next_arg(args, &args)) != NULL)
	{
		if ('-' == *arg || '/' == *arg)
		{
			all = 0;
			arg++;
			if (0 == my_strnicmp("ALI", arg, 3))	/* ALIAS */
				save_which |= SFLAG_ALIAS;
			else if (0 == my_strnicmp("AS", arg, 2)) /* ASSIGN */
				save_which |= SFLAG_ALIAS;
			else if (0 == my_strnicmp("B", arg, 1)) /* BIND */
				save_which |= SFLAG_BIND;
			else if (0 == my_strnicmp("O", arg, 1)) /* ON */
				save_which |= SFLAG_ON;
			else if (0 == my_strnicmp("S", arg, 1)) /* SET */
				save_which |= SFLAG_SET;
			else if (0 == my_strnicmp("N", arg, 1)) /* NOTIFY */
				save_which |= SFLAG_NOTIFY;
			else if (0 == my_strnicmp("D", arg, 1)) /* DIGRAPH */
				save_which |= SFLAG_DIGRAPH;
			else if (0 == my_strnicmp("ALL", arg, 3)) /* ALL */
				save_do_all = 1;
			else
			{
				say("%s: unknown argument", arg);
				return;
			}
			continue;
		}
		if (!(ircrc_file = expand_twiddle(arg)))
		{
			say("Unknown user");
			return;
		}
	}
	if (all)
		save_which = SFLAG_ALIAS | SFLAG_BIND | SFLAG_ON | SFLAG_SET |
			     SFLAG_NOTIFY | SFLAG_DIGRAPH;
	if (dumb)
		really_save(ircrc_file, "y"); /* REAL dumb!  -lynx */
	else
	{
		sprintf(buffer, "Really write %s? ", ircrc_file);
		add_wait_prompt(buffer, really_save, ircrc_file, WAIT_PROMPT_LINE);
	}
}

BUILT_IN_COMMAND(send_2comm)
{
	char	*reason;

	if ((reason = index(args, ' ')) != NULL)
		*reason++ = '\0';
	else
		reason = empty_string;

	if (!strcmp(args, "*"))
		args = get_channel_by_refnum(0);
	if (!args || !*args)
		args = "*";	/* what-EVER */

	if (reason && *reason)
		send_to_server("%s %s :%s", command, args, reason);
	else
		send_to_server("%s %s", command, args);
}

/*
 * send_comm: the generic command function.  Uses the full command name found
 * in 'command', combines it with the 'args', and sends it to the server 
 */
BUILT_IN_COMMAND(send_comm)
{
	if (args && *args)
		send_to_server("%s %s", command, args);
	else
		send_to_server("%s", command);
}

BUILT_IN_COMMAND(send_topic)
{
	char *channel;
	char *arg;

	if ((arg = next_arg(args, &args)))
	{
		if (is_channel(arg))
		{
			if (args && *args)
				send_to_server("%s %s :%s", command, arg, args);
			else
				send_to_server("%s %s", command, arg);
		}
		else
		{
			channel = get_channel_by_refnum(0);
			if (!strcmp(arg, "*"))
				send_to_server("%s %s :%s", command, channel, args);
			else
				send_to_server("%s %s :%s %s", command, channel, arg, args);
		}
	}
	else
		send_to_server("%s", command);
}

/*
 * send_kick: sends a kick message to the server.  Works properly with
 * kick comments.
 */
BUILT_IN_COMMAND(send_kick)
{
	char	*kickee,
		*comment;
	char	*channel;

#ifdef ENFORCE_STRICTER_PROTOCOL
	if (is_on_who) return;
#endif

	char usage[] = "Usage: %s <channel|*> <nickname> [comment]";

	if (!(channel = next_arg(args, &args)))
	{
		yell(usage, command);
		return;
	}

	if (!(kickee = next_arg(args, &args)))
	{
		yell(usage, command);
                return;
	}

	comment = args?args:empty_string;
	if (!strcmp(channel, "*"))
		channel = get_channel_by_refnum(0);

	send_to_server("%s %s %s :%s", command, channel, kickee, comment);
}

/*
 * send_channel_com: does the same as send_com for command where the first
 * argument is a channel name.  If the first argument is *, it is expanded
 * to the current channel (a la /WHO).
 */
BUILT_IN_COMMAND(send_channel_com)
{
	char	*ptr,
		*s;

	char usage[] = "Usage: %s <*|#channel> [arguments]";

        ptr = next_arg(args, &args);

	if (ptr && !strcmp(ptr, "*"))
	{
		if ((s = get_channel_by_refnum(0)) != NULL)
			send_to_server("%s %s %s", command, s, args?args:empty_string);
		else
			say("%s * is not valid since you are not on a channel", command);
	}
	else if (ptr)
		send_to_server("%s %s %s", command, ptr, args?args:empty_string);
	else
		yell(usage, command);
}

/* The SENDLINE command.. */
BUILT_IN_COMMAND(sendlinecmd)
{
	int	server;
	int	display;

	server = from_server;
	display = window_display;
	window_display = 1;
	parse_line(NULL, args, get_int_var(INPUT_ALIASES_VAR) ? empty_string : NULL, 1, 0);
	update_input(UPDATE_ALL);
	window_display = display;
	from_server = server;
}

/* 
 * IRCUSER command. Changes your userhost on the fly.  Takes effect
 * the next time you connect to a server 
 */
BUILT_IN_COMMAND(set_username)
{
        char *blah = next_arg(args, &args);
	if (blah && *blah)
	{
		if (!strcmp(blah, "-"))
			strmcpy(username, empty_string, NAME_LEN);
		else 
			strmcpy(username, blah, NAME_LEN);
		say("Username has been changed to '%s'",username);
	}
	else
		say ("Usage: /IRCUSER text");
}

BUILT_IN_COMMAND(shift_cmd)
{
        char *blah = function_shift(args);
        new_free(&blah);
}

BUILT_IN_COMMAND(sleepcmd)
{
	char	*arg;

	if ((arg = next_arg(args, &args)) != NULL)
		sleep(atoi(arg));
	else
		say("Usage: SLEEP <seconds>");
}

/* timercmd moved to 'timer.c' */

BUILT_IN_COMMAND(unshift_cmd)
{
        char *blah = function_unshift(args);
        new_free(&blah);
}

/*
 * userhost: Does the USERHOST command.  Need to split up the queries,
 * since the server will only reply to 5 at a time.
 */
BUILT_IN_COMMAND(userhost)
{
	int	n = 0,
		total = 0,
		userhost_cmd = 0;
	char	*nick;
	char	buffer[BIG_BUFFER_SIZE + 1];

	while ((nick = next_arg(args, &args)) != NULL)
	{
		char	*body;

		++total;
		if ((*nick == '-' || *nick == '/') && !my_strnicmp(nick+1, "C", 1))
		{
			if (total < 2)
			{
				yell("userhost -cmd with no nick!");
				return;
			}
			userhost_cmd = 1;
			while (my_isspace(*args))
				args++;
			body = next_expr_failok(&args, '{');
			if (body)
				args = body;
			break;
		}
		else
		{
			if (n++)
				strmcat(buffer, " ", BIG_BUFFER_SIZE);
			else
				*buffer = '\0';
			strmcat(buffer, nick, BIG_BUFFER_SIZE);
		}
	}
	if (n)
	{
		char	*the_list = (char *) 0;
		char	*s, *t;
		int	i;

		malloc_strcpy(&the_list, buffer);
		s = t = the_list;
		while (n)
		{
			for (i = 5; i && *s; s++)
				if (isspace(*s))
					i--, n--;

			if (isspace(s[-1]))
				s[-1] = 0;
			else
				n--;

			strcpy(buffer, t);
			t = s;

			if (userhost_cmd)
				add_to_whois_queue(buffer, userhost_cmd_returned, "%s", args);
			else
				add_to_whois_queue(buffer, USERHOST_USERHOST, "%s", empty_string);
		}
		new_free(&the_list);
	}
	else if (!total)
		/* Default to yourself.  */
		add_to_whois_queue(get_server_nickname(from_server), USERHOST_USERHOST, "%s", get_server_nickname(from_server));
}

/* version: does the /VERSION command with some IRCII version stuff */
BUILT_IN_COMMAND(version)
{
	char	*host;

	if ((host = next_arg(args, &args)) != NULL)
		send_to_server("%s %s", command, host);
	else
	{ 
#ifdef WINTRHAWK
		/* What can I say?  I changed it for the cosmetic value. |8^)	-- Jake [WinterHawk] Khuon */
		say("Client: ircII %s [Internal Release Code: %s]", irc_version, internal_version);
#else
		say ("Client: ircII %s (Internal Version: %s)", irc_version,
internal_version);
#endif
		send_to_server("%s", command);
	}
}

BUILT_IN_COMMAND(waitcmd)
{
	int	w_index;
	char	*flag;
	char	*procindex;
	int	cmd = 0,
		len;
	char	buffer[BIG_BUFFER_SIZE + 1];

	while (args && (*args == '-' || *args == '/'))
	{
		flag = next_arg(args, &args);
		len = strlen(++flag);
		if (!my_strnicmp("C", flag, 1))
		{
			cmd = 1;
			break;
		}
		else
			yell("Unknown argument to WAIT: %s", flag);
	}
	if ((procindex = next_arg(args, &args)) && *procindex == '%' &&
			(w_index = get_process_index(&procindex)) != -1)
	{
		if (is_process_running(w_index))
		{
			if (cmd)
			{
				add_process_wait(w_index, args?args:empty_string);
				return;
			}
			else
			{
				set_input(empty_string);
				while (is_process_running(w_index))
					io();
			}
		}
		else
		{
			say("Not a valid process!");
			return;
		}
	}
	else if (cmd)
	{
		WaitCmd	*new;

		sprintf(buffer, "%s %s", procindex, args);
		new = (WaitCmd *) new_malloc(sizeof(WaitCmd));
		new->stuff = NULL;
		malloc_strcpy(&new->stuff, buffer);
		new->next = NULL;
		if (end_wait_list)
			end_wait_list->next = new;
		end_wait_list = new;
		if (!start_wait_list)
			start_wait_list = new;
		send_to_server("%s", wait_nick);
		return;
	}

	oh_my_wait();
}

/*
 * who: the /WHO command. Parses the who switches and sets the who_mask and
 * whoo_stuff accordingly.  Who_mask and who_stuff are used in whoreply() in
 * parse.c 
 */
int doing_who = 0;

BUILT_IN_COMMAND(who)
{
	char	*arg,
		*channel = NULL;
	int	no_args = 1,
		len;

	if (doing_who)
		oh_my_wait();

	doing_who = 1;

	who_mask = 0;
	new_free(&who_name);
	new_free(&who_host);
	new_free(&who_server);
	new_free(&who_file);
	new_free(&who_nick);
	new_free(&who_real);

	while ((arg = next_arg(args, &args)) != NULL)
	{
		lower(arg);
		no_args = 0;
		if ((*arg == '-' || *arg == '/') && (!isdigit(*(arg + 1))))
		{
			arg++;
			if ((len = strlen(arg)) == 0)
			{
				say("Unknown or missing flag");
				return;
			}

			     if (!strncmp(arg, "o", 1))	/* OPS */
				who_mask |= WHO_OPS;
			else if (!strncmp(arg, "l", 1))	/* LUSERS */
				who_mask |= WHO_LUSERS;
			else if (!strncmp(arg, "c", 1))	/* CHOPS */
				who_mask |= WHO_CHOPS;
			else if (!strncmp(arg, "ho", 2))	/* HOSTS */
			{
				if ((arg = next_arg(args, &args)) != NULL)
				{
					who_mask |= WHO_HOST;
					malloc_strcpy(&who_host, arg);
					channel = who_host;
				}
				else
				{
					say("WHO -HOSTS: missing arguement");
					return;
				}
			}
		 	else if (!strncmp(arg, "he", 2))	/* here */
				who_mask |= WHO_HERE;
			else if (!strncmp(arg, "a", 1))	/* away */
				who_mask |= WHO_AWAY;
			else if (!strncmp(arg, "s", 1)) 	/* servers */
			{
				if ((arg = next_arg(args, &args)) != NULL)
				{
					who_mask |= WHO_SERVER;
					malloc_strcpy(&who_server, arg);
					channel = who_server;
				}
				else
				{
					say("WHO -SERVERS: missing arguement");
					return;
				}
			}
			else if (!strncmp(arg, "na", 2))
			{
				if ((arg = next_arg(args, &args)) != NULL)
				{
					who_mask |= WHO_NAME;
					malloc_strcpy(&who_name, arg);
					channel = who_name;
				}
				else
				{
					say("WHO -NAME: missing arguement");
					return;
				}
			}
			else if (!strncmp(arg, "r", 1))
			{
				if ((arg = next_arg(args, &args)) != NULL)
				{
					who_mask |= WHO_REAL;
					malloc_strcpy(&who_real, arg);
					channel = who_real;
				}
				else
				{
					say("WHO -REALNAME: missing arguement");
					return;
				}
			}
			else if (!strncmp(arg, "ni", 2))
			{
				if ((arg = next_arg(args, &args)) != NULL)
				{
					who_mask |= WHO_NICK;
					malloc_strcpy(&who_nick, arg);
					channel = who_nick;
				}
				else
				{
					say("WHO -NICK: missing arguement");
					return;
				}
			}
			/* WHO -FILE by Martin 'Efchen' Friedrich */
			else if (!strncmp(arg, "f", 1))
			{
				who_mask |= WHO_FILE;
				if ((arg = next_arg(args, &args)) != NULL)
					malloc_strcpy(&who_file, arg);
				else
				{
					say("WHO -FILE: missing arguement");
					return;
				}
			}
			else
			{
				say("Unknown or missing flag");
				return;
			}
		}
		else if (strcmp(arg, "*") == 0)
		{
			channel = get_channel_by_refnum(0);
			if (!channel || *channel == '0')
			{
				say("I wouldn't do that if I were you");
				return;
			}
		}
		else
			channel = arg;
	}

	if (no_args)
		say("No argument specified");
	else
	{
		if (!channel && (who_mask&WHO_OPS))
			channel = "*.*";
		send_to_server("%s %s %c", command, channel ? channel :
				empty_string, (who_mask & WHO_OPS) ?  'o' : '\0');
	}
}

/*
 * whois: the WHOIS and WHOWAS commands. 
 */
BUILT_IN_COMMAND(whois)
{
	char *stuff = NULL;

	if (!strcmp(command, "WHOWAS"))
	{
		char *word_one = next_arg (args, &args);
		if (args && *args)
			malloc_sprintf(&stuff, "%s %s", word_one, args);
		else if (word_one && *word_one)
			malloc_sprintf(&stuff, "%s %d", word_one, get_int_var(NUM_OF_WHOWAS_VAR));
		else
			malloc_sprintf(&stuff, "%s %d", get_server_nickname(from_server), get_int_var(NUM_OF_WHOWAS_VAR));

		send_to_server("WHOWAS %s", stuff);
		new_free(&stuff);
	}
	else /* whois command */
		send_to_server("WHOIS %s", args && *args ? args : get_server_nickname(from_server));
}

BUILT_IN_COMMAND(xtypecmd)
{
	char	*arg;

	if (*args == '-' || *args == '/')
	{
		char saved = *args;
		args++;
		if ((arg = next_arg(args, &args)) != NULL)
		{
			if (!my_strnicmp(arg, "L", 1))
			{
				for (; *args; args++)
					input_add_character(*args, empty_string);
			}
			else
				say ("Unknown flag -%s to XTYPE", arg);
			return;
		}
		input_add_character(saved, empty_string);
	}
	else
		type(command, args, empty_string);
	return;
}

/*
 * 
 * The rest of this file is the stuff that is not a command but is used
 * by the commands in this file.  These are considered to be "overhead".
 *
 */

/*
 * find_command: looks for the given name in the command list, returning a
 * pointer to the first match and the number of matches in cnt.  If no
 * matches are found, null is returned (as well as cnt being 0). The command
 * list is sorted, so we do a binary search.  The returned commands always
 * points to the first match in the list.  If the match is exact, it is
 * returned and cnt is set to the number of matches * -1.  Thus is 4 commands
 * matched, but the first was as exact match, cnt is -4.
 */
#ifdef __STDC__
static IrcCommand *find_command (char *com, int *cnt)
#else
static	IrcCommand *find_command(com, cnt)
	char	*com;
	int	*cnt;
#endif
{
	int	len;

	if (com && (len = strlen(com)))
	{
		int	min,
		max,
		pos,
		old_pos = -1,
		c;

		min = 1;
		max = NUMBER_OF_COMMANDS + 1;
		while (1)
		{
			pos = (max + min) / 2;
			if (pos == old_pos)
			{
				*cnt = 0;
				return ((IrcCommand *) 0);
			}
			old_pos = pos;
			c = strncmp(com, irc_command[pos].name, len);
			if (c == 0)
				break;
			else if (c > 0)
				min = pos;
			else
				max = pos;
		}
		*cnt = 0;
		(*cnt)++;
		min = pos - 1;
		while ((min > 0) && (strncmp(com, irc_command[min].name, len) == 0))
		{
			(*cnt)++;
			min--;
		}
		min++;
		max = pos + 1;
		while ((max < NUMBER_OF_COMMANDS + 1) && (strncmp(com, irc_command[max].name, len) == 0))
		{
			(*cnt)++;
			max++;
		}
		if (*cnt)
		{
			if (strlen(irc_command[min].name) == len)
				*cnt *= -1;
			else if (*cnt == 1 && irc_command[min].flags&NONOVICEABBREV && get_int_var(NOVICE_VAR))
			{
				say("As a novice you may not abbreviate the %s command", irc_command[min].name);
				*cnt=0;
				return ((IrcCommand *) 0);
			}
			return (&(irc_command[min]));
		}
		else
			return ((IrcCommand *) 0);
	}
	else
	{
		*cnt = -1;
		return (irc_command);
	}
}


struct target_type
{
	char *nick_list;
	char *message;
	int  hook_type;
	char *command;
	char *format;
	int  level;
};


/*
 * The whole shebang.
 *
 * The message targets are parsed and collected into one of 4 buckets.
 * This is not too dissimilar to what was done before, except now i 
 * feel more comfortable about how the code works.
 *
 * Bucket 0 -- Unencrypted PRIVMSGs to nicknames
 * Bucket 1 -- Unencrypted PRIVMSGs to channels
 * Bucket 2 -- Unencrypted NOTICEs to nicknames
 * Bucket 3 -- Unencrypted NOTICEs to channels
 *
 * All other messages (encrypted, and DCC CHATs) are dispatched 
 * immediately, and seperately from all others.  All messages that
 * end up in one of the above mentioned buckets get sent out all
 * at once.
 *
 * XXXX --- Its super super important that you *never* call yell(),
 * put_it(), or anything like that which would end up calling add_to_window().
 * add_to_window() can get in an infinite cycle() with send_text() if the
 * user is /redirect'ing.  send_text() tries to prevent the user from being
 * able to send stuff to the window by never hooking /ONs if we're already
 * recursing -- so whoever is hacking on this code, its also up to you to 
 * make sure that YOU dont send anything to the screen without checking first!
 */
#ifdef __STDC__
extern	void 	send_text (char *nick_list, char *text, char *command, int hook)
#else
extern	void 	send_text (nick_list, text, command, hook)
	char 	*nick_list, 
		*text, 
		*command;
	int 	hook;
#endif
{
	int i, af, old_server;
	int lastlog_level;
	char *current_nick, *next_nick, *free_nick;
	char *line, *key;
	int old_window_display = window_display;
static	int recursion = 0;

	/*
	 * XXXX - Heaven help us.
	 */
struct target_type target[4] = 
{	
	{NULL, NULL, SEND_MSG_LIST,     "PRIVMSG", "*%s*> %s" , LOG_MSG }, 
	{NULL, NULL, SEND_PUBLIC_LIST,  "PRIVMSG", "%s> %s"   , LOG_PUBLIC },
	{NULL, NULL, SEND_NOTICE_LIST,  "NOTICE",  "-%s-> %s" , LOG_NOTICE }, 
	{NULL, NULL, SEND_NOTICE_LIST,  "NOTICE",  "-%s-> %s" , LOG_NOTICE }
};

	if (!nick_list || !text)
		return;

	/*
	 * If we're being called recursively, that is because the user
	 * is redirect'ing, or the user has attempted to send text from
	 * within an /on send_*.  In both of these cases, we break the 
	 * would-be infinite cycle by refusing to put anything to the
	 * screen and also by refusing to hook any more /on send_*'s.
	 */
	if (recursion)
		hook = 0;

	window_display = hook;
	recursion++;
	free_nick = next_nick = m_strdup(nick_list);

	while ((current_nick = next_nick))
	{
		if ((next_nick = index(current_nick, ',')))
			*next_nick++ = 0;

		if (!*current_nick)
			continue;

		if (*current_nick == '%')
		{
			if ((i = get_process_index(&current_nick)) == -1)
				say("Invalid process specification");
			else
				text_to_process(i, text, 1);
		}
		/*
		 * This test has to be here because it is legal to
		 * send an empty line to a process, but not legal
		 * to send an empty line anywhere else.
		 */
		else if (!text || !*text)
			;
		else if (*current_nick == '@')
			say("DCC TALK not supported.");
		else if (*current_nick == '"')
			send_to_server("%s", text);
		else if (*current_nick == '/')
		{
			line = new_malloc(strlen(current_nick) + strlen(text) + 2);
			strcpy(line, current_nick);
			strcat(line, " ");
			strcat(line, text);
			parse_inline(line, NULL, &af);
			new_free(&line);
		}
		else if (*current_nick == '=')
		{
			char *copy;

			if (!dcc_active(current_nick + 1))
			{
				yell("No DCC CHAT connection open to %s", current_nick + 1);
				continue;
			}

			/* XXXX */
			copy = m_strdup(text);
			if ((key = is_crypted(current_nick)) != 0)
				line = crypt_msg(text, key);
			else
				line = m_strdup(text);

			old_server = from_server;
			from_server = -1;
			if (hook && do_hook(SEND_DCC_CHAT_LIST, "%s %s", current_nick + 1, copy))
				put_it("=> =%s= %s", current_nick + 1, copy);

			dcc_chat_transmit(current_nick + 1, line, command);
			from_server = old_server;
			malloc_strcpy(&sent_nick, current_nick);
			new_free(&line);
			new_free(&copy);
		}
		else
		{
			char *copy;

			if (doing_notice)
			{
				say("You cannot send a message from within ON NOTICE");
				continue;
			}

			i = is_channel(current_nick);
			if (doing_notice || (command && !strcmp(command, "NOTICE")))
				i += 2;

			if ((key = is_crypted(current_nick)))
			{
				copy = m_strdup(text);
				lastlog_level = set_lastlog_msg_level(target[i].level);
				message_from(current_nick, target[i].level);

				line = crypt_msg(text, key);
				if (hook && do_hook(target[i].hook_type, "%s %s", current_nick, copy))
					put_it(target[i].format, current_nick, copy);
				send_to_server("%s %s :%s", target[i].command, current_nick, line);
				malloc_strcpy(&sent_nick, current_nick);

				new_free(&line);
				new_free(&copy);
				set_lastlog_msg_level(lastlog_level);
			}
			else
			{
				if (i == 0)
					malloc_strcpy(&sent_nick, current_nick);
				if (target[i].nick_list)
					malloc_strcat(&target[i].nick_list, ",");
				malloc_strcat(&target[i].nick_list, current_nick);
				if (!target[i].message)
					target[i].message = text;
			}
		}
	}

	for (i = 0; i < 4; i++)
	{
		if (!target[i].message)
			continue;

		lastlog_level = set_lastlog_msg_level(target[i].level);
		message_from(target[i].nick_list, target[i].level);

		if (hook && do_hook(target[i].hook_type, "%s %s", target[i].nick_list, target[i].message))
			put_it(target[i].format, target[i].nick_list, target[i].message);
		send_to_server("%s %s :%s", target[i].command, target[i].nick_list, target[i].message);
		new_free(&target[i].nick_list);
		target[i].message = NULL;

		set_lastlog_msg_level(lastlog_level);
	}

	/*
	 * If the user didnt explicitly send the text (hook == 1), then 
	 * it makes no sense to presume that theyre not still /AWAY.
	 * This also makes sure that the "no longer away" message doesnt
	 * get munched if window_display is 0.
	 */
	if (hook && server_list[curr_scr_win->server].away && get_int_var(AUTO_UNMARK_AWAY_VAR))
		parse_line(NULL, "AWAY", empty_string, 0, 0);

	message_from(NULL, LOG_CRAP);
	new_free(&free_nick);
	window_display = old_window_display;
	recursion--;
}



/*
 * eval_inputlist:  Cute little wrapper that calls parse_line() when we
 * get an input prompt ..
 */
#ifdef __STDC__
static void	eval_inputlist (char *args, char *line)
#else
static void	eval_inputlist(args, line)
	char	*args,
		*line;
#endif
{
	parse_line(NULL, args, line ? line : empty_string, 0, 0);
}



/* 
 * This is a key binding and has to be here because it looks at the
 * command array which is static to this file.
 */
/*
 * command_completion: builds lists of commands and aliases that match the
 * given command and displays them for the user's lookseeing 
 */
#ifdef __STDC__
extern	void 	command_completion (char unused, char *not_used)
#else
extern 	void 	command_completion (unused, not_used)
	char 	unused, 
		*not_used;
#endif
{
	int	do_aliases;
	int	cmd_cnt,
		alias_cnt,
		i,
		c,
		len;
	char	**aliases = NULL;
	char	*line = NULL,
		*com,
		*cmdchars,
		*rest,
		firstcmdchar = '/';
	IrcCommand	*command;
	char	buffer[BIG_BUFFER_SIZE + 1];

	malloc_strcpy(&line, get_input());
	if ((com = next_arg(line, &rest)) != NULL)
	{
		if (!(cmdchars = get_string_var(CMDCHARS_VAR)))
			cmdchars = DEFAULT_CMDCHARS;
		if (index(cmdchars, *com))
		{
			firstcmdchar = *cmdchars;
			com++;
			if (*com && index(cmdchars, *com))
			{
				do_aliases = 0;
				alias_cnt = 0;
				com++;
			}
			else
				do_aliases = 1;
			upper(com);
			if (do_aliases)
				aliases = match_alias(com, &alias_cnt, COMMAND_ALIAS);

			if ((command = find_command(com, &cmd_cnt)) != NULL)
			{
				if (cmd_cnt < 0)
					cmd_cnt *= -1;
				/* special case for the empty string */

				if (*(command[0].name) == (char) 0)
				{
					command++;
					cmd_cnt = NUMBER_OF_COMMANDS;
				}
			}

			if ((alias_cnt == 1) && (cmd_cnt == 0))
			{
				sprintf(buffer, "%c%s %s", firstcmdchar, aliases[0], rest);
				set_input(buffer);
				new_free((char **)&(aliases[0]));
				new_free((char **)&aliases);
				update_input(UPDATE_ALL);
			}
			else if (((cmd_cnt == 1) && (alias_cnt == 0)) ||
			    ((cmd_cnt == 1) && (alias_cnt == 1) &&
			    (strcmp(aliases[0], command[0].name) == 0)))
			{
				sprintf(buffer, "%c%s%s %s", firstcmdchar,
					do_aliases ? empty_string : &firstcmdchar,
					command[0].name, rest);
				set_input(buffer);
				update_input(UPDATE_ALL);
			}
			else
			{
				*buffer = (char) 0;
				if (command)
				{
					say("Commands:");
					strmcpy(buffer, "\t", BIG_BUFFER_SIZE);
					c = 0;
					for (i = 0; i < cmd_cnt; i++)
					{
						strmcat(buffer, command[i].name,
							BIG_BUFFER_SIZE);
						for (len = strlen(command[i].name); len < 15; len++)
							strmcat(buffer, " ", BIG_BUFFER_SIZE);
						if (++c == 4)
						{
							say("%s", buffer);
							strmcpy(buffer, "\t", BIG_BUFFER_SIZE);
							c = 0;
						}
					}
					if (c)
						say("%s", buffer);
				}
				if (aliases)
				{
					say("Aliases:");
					strmcpy(buffer, "\t", BIG_BUFFER_SIZE);
					c = 0;
					for (i = 0; i < alias_cnt; i++)
					{
						strmcat(buffer, aliases[i], BIG_BUFFER_SIZE);
						for (len = strlen(aliases[i]); len < 15; len++)
							strmcat(buffer, " ", BIG_BUFFER_SIZE);
						if (++c == 4)
						{
							say("%s", buffer);
							strmcpy(buffer, "\t", BIG_BUFFER_SIZE);
							c = 0;
						}
						new_free(&(aliases[i]));
					}
					if (strlen(buffer) > 1)
						say("%s", buffer);
					new_free((char **)&aliases);
				}
				if (!*buffer)
					term_beep();
			}
		}
		else
			term_beep();
	}
	else
		term_beep();

	new_free(&line);
}


/* 
 * parse_line: This is the main parsing routine.  It should be called in
 * almost all circumstances over parse_command().
 *
 * parse_line breaks up the line into commands separated by unescaped
 * semicolons if we are in non interactive mode. Otherwise it tries to leave
 * the line untouched.
 *
 * Currently, a carriage return or newline breaks the line into multiple
 * commands too. This is expected to stop at some point when parse_command
 * will check for such things and escape them using the ^P convention.
 * We'll also try to check before we get to this stage and escape them before
 * they become a problem.
 *
 * Other than these two conventions the line is left basically untouched.
 *
 * Ideas on parsing: Why should the calling function be responsible
 *  for removing {} blocks?  Why cant this parser cope with and {}s
 *  that come up?
 */
#ifdef __STDC__
extern 	void	parse_line (char *name, char *org_line, char *args, int hist_flag, int append_flag)
#else
extern 	void	parse_line(name, org_line, args, hist_flag, append_flag)
	char	*name,
		*org_line,
		*args;
	int	hist_flag,
		append_flag;
#endif
{
	char	*line = NULL,
		*free_line,
		*stuff,
		*buffer = (char *) 0,
		*s,
		*t;
	int	args_flag = 0;

	malloc_strcpy(&line, org_line);
	free_line = line;

	if (!*line)
		do_send_text(NULL, empty_string, empty_string);

	else if (args) do 
        {
                while (*line == '{') 
                {
                        if ((stuff = next_expr(&line, '{'))==(char *) 0) 
			{
                                yell("Unmatched {");
                                new_free(&free_line);
                                return;
                        }
                        parse_line(name, stuff, args, hist_flag, append_flag);
			while (line && *line && ((*line == ';') || (my_isspace(*line))))
				*line++ = '\0';
		}

		if (!line || !*line)
			return;

		if (get_int_var(SECURITY_VAR) & SECURITY_NO_VARIABLE_COMMAND)
		{
			if (*line == '$')
			{
				yell("WARNING: The command '%s' (%s) was not executed due to a security violation", line, args);
				return;
			}
		}

		stuff = expand_alias(name, line, args, &args_flag, &line);
		if (!line && append_flag && !args_flag && args && *args)
		{
                        malloc_sprintf(&buffer, "%s %s",stuff, args);
			new_free(&stuff);
			stuff = buffer;
		};
		parse_command(stuff, hist_flag, args);
		if (!line) new_free(&free_line);
                new_free(&stuff);
	} while (line && *line);
        else
	{
		if (load_depth)
			parse_command(line, hist_flag, args);
		else
		{
			while ((s = line))
			{
				if ((t = sindex(line, "\r\n")))
				{
					*t++ = '\0';
					line = t;
				}
				else
					line = NULL;
				parse_command(s, hist_flag, args);
			}
		}
	}
	new_free(&free_line);
	return;
}

/* XXXX - im sure this doesnt belong here, but for some reason i dont care. */
#ifdef WIND_STACK
char **call_stack = NULL;
int max_wind = -1;
int wind_index = 0;
void dump_call_stack _((void));

#ifdef __STDC__
void wind_stack (char *line)
#else
void wind_stack (line)
char *line;
#endif
{
	if (wind_index >= max_wind)
	{
		if (max_wind == -1)
			max_wind = get_int_var(MAX_RECURSIONS_VAR);
		else
			max_wind *= 2;

		call_stack = (char **)new_realloc((char *)call_stack, sizeof(char *) * max_wind);
	}

	call_stack[wind_index] = line;
	wind_index++;
}

void unwind_stack (void)
{
	wind_index--;
	if (wind_index < 0)
		wind_index = 0;
}

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

/* XXXX */
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]);
	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.
 */
void destroy_call_stack (void)
{
	wind_index = 0;
	new_free((char **)&call_stack);
}

#ifdef __STDC__
void e_call (char *cmd, char *args, char *subargs)
#else
void e_call (cmd, args, subargs)
char *cmd, *args, *subargs;
#endif
{
	dump_call_stack();
}

#endif


/*
 * parse_command: parses a line of input from the user.  If the first
 * character of the line is equal to irc_variable[CMDCHAR_VAR].value, the
 * line is used as an irc command and parsed appropriately.  If the first
 * character is anything else, the line is sent to the current channel or to
 * the current query user.  If hist_flag is true, commands will be added to
 * the command history as appropriate.  Otherwise, parsed commands will not
 * be added. 
 *
 * Parse_command() only parses a single command.In general, you will want to 
 * use parse_line() to execute things.Parse command recognizes no quoted
 * characters or anything (beyond those specific for a given command being
 * executed). 
 *
 * Only Three functions have any business calling us:
 *	- call_function (in alias.c)
 *	- parse_line	(in edit.c)
 *	- parse_command (right here)
 *
 * Everyone else must call parse_line.  No exceptions.
 */
#ifdef __STDC__
extern 	int	parse_command (char *line, int hist_flag, char *sub_args)
#else
extern 	int	parse_command(line, hist_flag, sub_args)
	char	*line;
	int	hist_flag;
	char	*sub_args;
#endif
{
static	unsigned int	level = 0;
	unsigned int	display,
			old_display_var;
		char	*cmdchars,
			*com;
		int	args_flag,
			add_to_hist,
			cmdchar_used = 0;
		int	noisy = 1;
		char	*this_cmd = NULL;

	if (!line || !*line) 
		return 0;

	if (get_int_var(DEBUG_VAR) & DEBUG_COMMANDS)
		yell("Executing [%d] %s", level, line);
	level++;
	if (!(cmdchars = get_string_var(CMDCHARS_VAR)))
		cmdchars = DEFAULT_CMDCHARS;
	this_cmd = m_strdup(line);
#ifdef WIND_STACK
	wind_stack(this_cmd);
#endif
	add_to_hist = 1;
	display = window_display;
	old_display_var = (unsigned) get_int_var(DISPLAY_VAR);

	/* 
	 * Once and for all i hope i fixed this.  What does this do?
	 * well, at the beginning of your input line, it looks to see
	 * if youve used any ^s or /s.  You can use up to one ^ and up
	 * to two /s.  When any character is found that is not one of
	 * these characters, it stops looking.
	 */
	for (;*line;line++)
	{
		/* Fix to allow you to do ^foo at the input line. */
		if (*line == '^' && (!hist_flag || cmdchar_used))
		{
			if (!noisy)
				break;
			noisy = 0;
		}
		else if (index(cmdchars, *line))
		{
			cmdchar_used++;
			if (cmdchar_used > 2)
				break;
		}
		else
			break;
	}

	if (!noisy)
		window_display = 0;
	com = line;

	/*
	 * always consider input a command unless we are in interactive mode
	 * and command_mode is off.   -lynx
	 */
	if (hist_flag && !cmdchar_used && !get_int_var(COMMAND_MODE_VAR))
	{
		do_send_text(NULL, line, empty_string);
		if (hist_flag && add_to_hist)
		{
			add_to_history(this_cmd);
			set_input(empty_string);
		}
		/* Special handling for ' and : */
	}
	else if (*com == '\'' && get_int_var(COMMAND_MODE_VAR))
	{
		do_send_text(NULL, line+1, empty_string);
		if (hist_flag && add_to_hist)
		{
			add_to_history(this_cmd);
			set_input(empty_string);
		}
	}
	else if ((*com == '@') || (*com == '('))
	{
		/* This kludge fixes a memory leak */
		char	*tmp, *l_ptr;

		/*
		 * This new "feature" masks a weakness in the underlying
		 * grammar that allowed variable names to begin with an
		 * lparen, which inhibited the expansion of $s inside its
		 * name, which caused icky messes with array subscripts.
		 *
		 * Anyhow, we now define any commands that start with an
		 * lparen as being "expressions", same as @ lines.
		 */
		if (*com == '(')
		{
			if ((l_ptr = MatchingBracket(line + 1, '(', ')')))
				*l_ptr++ = 0;
		}

		if ((tmp = parse_inline(line + 1, sub_args, &args_flag)))
			new_free(&tmp);

		if (hist_flag && add_to_hist)
		{
			add_to_history(this_cmd);
			set_input(empty_string);
		}
	}
	else
	{
		char		*rest,
				*alias = NULL,
				*alias_name;
		int		cmd_cnt,
				alias_cnt = 0;
		IrcCommand	*command; /* = (IrcCommand *) 0 */

		if ((rest = (char *) index(com, ' ')) != NULL)
			*(rest++) = (char) 0;
		else
			rest = empty_string;
		upper(com);

		if (cmdchar_used < 2)
			alias = get_alias(COMMAND_ALIAS, com, &alias_cnt, &alias_name);

		if (alias && (alias_cnt == 0))
		{
			if (hist_flag && add_to_hist)
			{
				add_to_history(this_cmd);
				set_input(empty_string);
			}
			execute_alias(alias_name, alias, rest);
			new_free(&alias_name);
		}
		else
		{
			/* History */
			if (*com == '!')
			{
				if ((com = do_history(com + 1, rest)) != NULL)
				{
					if (level == 1)
					{
						set_input(com);
						update_input(UPDATE_ALL);
					}
					else
						parse_command(com, 0, sub_args);
					new_free(&com);
				}
				else
					set_input(empty_string);
			}
			else
			{
				if (hist_flag && add_to_hist)
				{
					add_to_history(this_cmd);
					set_input(empty_string);
				}
				command = find_command(com, &cmd_cnt);
				if ((command && (cmd_cnt < 0)) || ((alias_cnt == 0) && (cmd_cnt == 1)))
				{
					/* i should make a function to do this */
					if (!strcmp(command->name, "EXEC") && get_int_var(SECURITY_VAR) & SECURITY_NO_NONINTERACTIVE_EXEC)
						yell("Warning: the command '%s %s' was not executed due to a security violation", command->name, rest);
					else if (!strcmp(command->name, "SET") && get_int_var(SECURITY_VAR) & SECURITY_NO_NONINTERACTIVE_SET)
						yell("Warning: the command '%s %s' was not executed due to a security violation", command->name, rest);
					else if (command->func)
						command->func(command->server_func, rest, sub_args);
					else
						say("%s: command disabled", command->name);
				}
				else if (alias && (alias_cnt == 1) &&
				    (cmd_cnt == 1) &&
				    (strcmp(alias_name, command[0].name) == 0))
					execute_alias(alias_name, alias, rest);
				else if ((alias_cnt + cmd_cnt) > 1)
					say("Ambiguous command: %s", com);
				else if (alias && (alias_cnt == 1))
					execute_alias(alias_name, alias, rest);
				else if (!my_stricmp(com, nickname)) /* nick = /me  -lynx */
					me(NULL, rest, empty_string);
				else
					say("Unknown command: %s", com);
			}
			if (alias)
				new_free(&alias_name);
		}
	}
	if (old_display_var != get_int_var(DISPLAY_VAR))
		window_display = get_int_var(DISPLAY_VAR);
	else
		window_display = display;

	new_free(&this_cmd);
	level--;
#ifdef WIND_STACK
	unwind_stack();
#endif
        return 0;
}

/* 
 * How does this work?  Well, when we issue the /wait command
 * it increments a variable "waiting" which is the number of 
 * times wait has been called so far.  If we get a wait
 * token, we reduce the wait level by one, and if the wait
 * level is zero, then we are free to clear irc_io_loop which
 * will cause the waits to just fall out.
 */
#ifdef __STDC__
extern int check_wait_command (char *nick)
#else
extern int check_wait_command(nick)
	char 	*nick;
#endif
{
	if (waiting && !strcmp(nick, lame_wait_nick))
	{
		waiting--;
	        return 1;
	}
	if (start_wait_list && !strcmp(nick, wait_nick))
	{
		if (start_wait_list->stuff)
		{
			parse_line(NULL, start_wait_list->stuff, empty_string, 0, 0);
			new_free(&start_wait_list->stuff);
		}
		start_wait_list = start_wait_list->next;
		return 1;
	}
	return 0;
}

