/*
 * edit.c: This is really a mishmash of function and such that deal with IRCII
 * commands (both normal and keybinding commands) 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <ctype.h>
#include <stdio.h>
#ifdef ESIX
#include <lan/net_types.h>
#endif
#include <netinet/in.h>
#include "irc.h"
#include "term.h"
#include "server.h"
#include "edit.h"
#include "crypt.h"
#include "vars.h"
#include "ircaux.h"
#include "lastlog.h"
#include "window.h"
#include "whois.h"
#include "hook.h"
#include "input.h"
#include "ignore.h"
#include "keys.h"
#include "names.h"
#include "alias.h"
#include "history.h"
#include "funny.h"
#include "ctcp.h"
#include "dcc.h"
#include "translat.h"

/*
 * current_exec_timer - used to make sure we don't remove a timer
 * from within itself.
 */
static	int	current_exec_timer = -1;

static	void	show_timer();
static	int	create_timer_ref();

TimerList *PendingTimers = null(TimerList *);

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

/* 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(char *);

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

/* 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;

/* declared in parse.c */
extern	int	doing_privmsg;

/* Used to prevent global messaging */
extern	int	in_on_who;

/* a few advance declarations */
static	void	sendlinecmd();
extern	void	ifcmd();
extern	void	do_send_text();
static	void	funny_stuff();
extern	void	parse_command();
static	void	cd();
extern	void	edit_char();
extern	void	foreach();
extern	void	whilecmd();
static	void	quit();
static	void	wall();
static	void	send_com();
static	void	send_topic();
static	void	send_kick();
static	void	send_channel_com();
static	void	clear();
static	void	quote();
static	void	privmsg();
static	void	flush();
extern	void	server();
static	void	away();
extern	void	query();
static	void	oper();
static	void	channel();
extern	void	execcmd();
extern	void	ignore();
static	void	who();
static	void	whois();
static	void	ison();
static	void	userhost();
static	void	info();
static	void	nick();
static	void	comment();
extern	void	bindcmd();
extern	void	parsekeycmd();
static	void	sleepcmd();
extern	void	history();
static	void	version();
extern	void	on();
static	void	ctcp();
static	void	dcc();
static	void	deop();
extern	void	echo();
static	void	save_settings();
static	void	redirect();
extern	void	type();
extern	void	notify();
extern	void	waitcmd();
extern	void	parse_line();
static	void	describe();
static	void	me();
static	void	mload();
static	void	mlist();
static	void	evalcmd();
extern	void	direct_expr();
static	void	hook();
static	void	timercmd();
static	void	inputcmd();
static	void	pingcmd();

#ifdef ALLOC_DEBUG
extern	void	alloc_cmd();
#endif
/* 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) */
	void	(*func) ();		/* function that is the command */
	unsigned	flags;
}	IrcCommand;

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

/* number of entries in irc_command array */
#ifdef ALLOC_DEBUG
# define	NUMBER_OF_COMMANDS 103
#else
# define	NUMBER_OF_COMMANDS 102
#endif

/*
 * 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
 * make "/ blah blah blah" always get sent to a channel, bypassing queries,
 * etc.  Neato.  This list MUST be sorted and the NUMBER_OF_COMMANDS define
 * must represent the number of commands in this list 
 */
static	IrcCommand irc_command[NUMBER_OF_COMMANDS * 2] =
{
	"",		empty_string,	do_send_text,		NOSIMPLESCRIPT|
	NOCOMPLEXSCRIPT,
/*
 * I really want to remove #, but it will break a lot of scripts.  - mycroft
 *
 * I agree - it should be converted to a special character in parse_line.
 *								- Troy
 */
	"#",		NULL,		comment, 		0,
	":",		NULL,		comment, 		0,
	"ADMIN",	"ADMIN",	send_com, 		0,
	"ALIAS",	"0",		alias,			0,
#ifdef ALLOC_DEBUG
	"ALLOC",	NULL,		alloc_cmd,		0,
#endif
	"ASSIGN",	"1",		alias,			0,
	"AWAY",		"AWAY",		away,			0,
	"BIND",		NULL,		bindcmd,		0,
	"BYE",		"QUIT",		quit,			NONOVICEABBREV,
	"CD",		NULL,		cd,			0,
	"CHANNEL",	"JOIN",		channel,		0,
	"CLEAR",	NULL,		clear,			0,
	"COMMENT",	NULL,		comment,		0,
	"CONNECT",	"CONNECT",	send_com,		0,
	"CTCC",		NULL,		dcc,			0,
	"CTCP",		NULL,		ctcp,			0,
	"DATE",		"TIME",		send_com,		0,
	"DCC",		NULL,		dcc,			0,
	"DEOP",		NULL,		deop,			0,
	"DESCRIBE",	NULL,		describe,		0,
	"DIE",		"DIE",		send_com,		0,
	"DIGRAPH",	NULL,		digraph,		0,
	"ECHO",		NULL,		echo,			0,
	"ENCRYPT",	NULL,		encrypt_cmd,		0,
	"EVAL",		NULL,		evalcmd,		0,
	"EXEC",		NULL,		execcmd,		0,
	"EXIT",		"QUIT",		quit,			NONOVICEABBREV,
	"FLUSH",	NULL,		flush,			0,
	"FOREACH",	NULL,		foreach,		0,
	"HASH",		"HASH",		send_com,		0,
	"HELP",		NULL,		help,			0,
	"HISTORY",	NULL,		history,		0,
	"HOOK",		NULL,		hook,			0,
	"HOST",		"USERHOST",	userhost,		0,
	"IF",		NULL,		ifcmd,			0,
	"IGNORE",	NULL,		ignore,			0,
	"INFO",		"INFO",		info,			0,
	"INPUT",	NULL,		inputcmd,		0,
	"INVITE",	"INVITE",	send_com,		0,
	"ISON",		"ISON",		ison,			0,
	"JOIN",		"JOIN",		channel,		0,
	"KICK",		"KICK",		send_kick,		0,
	"KILL",		"KILL",		send_com,		0,
	"LASTLOG",	NULL,		lastlog,		0,
	"LEAVE",	"PART",		send_channel_com,	0,
	"LINKS",	"LINKS",	send_com,		NONOVICEABBREV,
	"LIST",		"LIST",		funny_stuff,		0,
	"LOAD",		"LOAD",		load,			0,
	"LUSERS",	"LUSERS",	send_com,		0,
	"ME",		NULL,		me,			0,
	"MLIST",	NULL,		mlist,			0,
	"MLOAD",	NULL,		mload,			0,
	"MODE",		"MODE",		send_channel_com,	0,
	"MOTD",		"MOTD",		send_com,		0,
	"MSG",		"PRIVMSG",	privmsg,		0,
	"NAMES",	"NAMES",	funny_stuff,		0,
	"NICK",		"NICK",		nick,			0,
	"NOTE",		"NOTE",		send_com,		0,
	"NOTICE",	"NOTICE",	privmsg,		0,
	"NOTIFY",	NULL,		notify,			0,
	"ON",		NULL,		on,			0,
	"OPER",		"OPER",		oper,			0,
	"PARSEKEY",	NULL,		parsekeycmd,		0,
	"PART",		"PART",		send_channel_com,	0,
	"PING",		NULL, 		pingcmd,		0,
	"QUERY",	NULL,		query,			0,
	"QUIT",		"QUIT",		quit,			NONOVICEABBREV,
	"QUOTE",	NULL,		quote,			0,
	"REDIRECT",	NULL,		redirect,		0,
	"REHASH",	"REHASH",	send_com,		0,
	"REQUEST",	NULL,		ctcp,			0,
	"RESTART",	"RESTART",	send_com,		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,
	"SET",		NULL,		set_variable,		0,
	"SIGNOFF",	"QUIT",		quit,			NONOVICEABBREV,
	"SLEEP",	NULL,		sleepcmd,		0,
	"SQUIT",	"SQUIT",	send_com,		0,
	"STATS",	"STATS",	send_com,		0,
	"SUMMON",	"SUMMON",	send_com,		0,
	"TIME",		"TIME",		send_com,		0,
	"TIMER",	"TIMER",	timercmd,		0,
	"TOPIC",	"TOPIC",	send_topic,		0,
	"TRACE",	"TRACE",	send_com,		0,
	"TYPE",		NULL,		type,			0,
	"USERHOST",	NULL,		userhost,		0,
	"USERS",	"USERS",	send_com,		0,
	"VERSION",	"VERSION",	version,		0,
	"VOICE",	"VOICE",	privmsg,		0,
	"WAIT",		NULL,		waitcmd,		0,
	"WALL",		"WALL",		wall,			0,
	"WALLOPS",	"WALLOPS",	wall,			0,
	"WHICH",	"WHICH",	load,			0,
	"WHILE",	NULL,		whilecmd,		0,
	"WHO",		"WHO",		who,			0,
	"WHOIS",	"WHOIS",	whois,			0,
	"WHOWAS",	"WHOWAS",	whois,			0,
	"WINDOW",	NULL,		window,			0,
	"XECHO",	"XECHO",	echo,			0,
	"XTRA",		"XTRA",		privmsg,		0,
	null(char *),	null(char *),	NULL,			0
};

/*
 * 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.
 */
static	IrcCommand *find_command(com, cnt)
char	*com;
int	*cnt;
{
	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 (null(IrcCommand *));
			}
			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 (null(IrcCommand *));
			}
			return (&(irc_command[min]));
		}
		else
			return (null(IrcCommand *));
	}
	else
	{
		*cnt = -1;
		return (irc_command);
	}
}

/*ARGSUSED*/
static	void	ctcp(command, args)
char	*command,
	*args;
{
	char	*to,
		*tag;
	int	type;

	if (to = next_arg(args, &args))
	{
		if (!strcmp(to, "*"))
			if ((to=get_channel_by_refnum(0)) == null(char *))
				to = "0";
		if (tag = next_arg(args, &args))
			upper(tag);
		else
			tag = "VERSION";
		if ((type = in_ctcp()) == -1)
			echo(null(char *), "*** You may not use the CTCP \
command in an ON CTCP_REPLY!");
		else
		{
			if (args && *args)
				send_ctcp(ctcp_type[type], to, tag, "%s", args);
			else
				send_ctcp(ctcp_type[type], to, tag,
					null(char *));
		}
	}
	else
		say("Request from whom?");
}

/*ARGSUSED*/
static	void	hook(command, args)
char	*command,
	*args;
{
	if (*args)
		do_hook(HOOK_LIST, "%s", args);
	else
		say("Must supply an argument to HOOK");
}

/*ARGSUSED*/
static	void	dcc(command, args)
char	*command,
	*args;
{
	if (*args)
		process_dcc(args);
	else
		dcc_list();
}

/*ARGSUSED*/
static	void	deop(command, args)
char	*command,
	*args;
{
	send_to_server("MODE %s -o", get_server_nickname(from_server));
}

static	void	funny_stuff(command, args)
char	*command,
	*args;
{
	char	*arg,
		*stuff;
	int	min = 0,
		max = 0,
		flags = 0,
		len;

	stuff = empty_string;
	while (arg = next_arg(args, &args))
	{
		len = strlen(arg);
		if (strnicmp(arg, "-MAX", len) == 0)
		{
			if (arg = next_arg(args, &args))
				max = atoi(arg);
		}
		else if (strnicmp(arg, "-MIN", len) == 0)
		{
			if (arg = next_arg(args, &args))
				min = atoi(arg);
		}
		else if (strnicmp(arg, "-ALL", len) == 0)
		{
			flags &= ~(FUNNY_PUBLIC | FUNNY_PRIVATE);
		}
		else if (strnicmp(arg, "-PUBLIC", len) == 0)
		{
			flags |= FUNNY_PUBLIC;
			flags &= ~FUNNY_PRIVATE;
		}
		else if (strnicmp(arg, "-PRIVATE", len) == 0)
		{
			flags |= FUNNY_PRIVATE;
			flags &= ~FUNNY_PUBLIC;
		}
		else if (strnicmp(arg, "-TOPIC", len) == 0)
			flags |= FUNNY_TOPIC;
		else if (strnicmp(arg, "-WIDE", len) == 0)
			flags |= FUNNY_WIDE;
		else if (strnicmp(arg, "-USERS", len) == 0)
			flags |= FUNNY_USERS;
		else if (strnicmp(arg, "-NAME", len) == 0)
			flags |= FUNNY_NAME;
		else
			stuff = arg;
	}
	set_funny_flags(min, max, flags);
	if (strcmp(stuff, "*") == 0)
		if ((stuff = get_channel_by_refnum(0)) == null(char *))
			stuff = empty_string;
	if (index(stuff, '*'))
	{
		funny_match(stuff);
		send_to_server("%s %s", command, empty_string);
	}
	else{
		funny_match(null(char *));
		send_to_server("%s %s", command, stuff);
	}
}

/*ARGSUSED*/
void	waitcmd(command, args)
char	*command,
	*args;
{
	int	index;
	char	*flag;
	char	*procindex;
	int	cmd = 0,
		len;
#if 0
	char	*server = null(char *);
#endif

	while (args && *args == '-')
	{
		flag = next_arg(args, &args);
		len = strlen(++flag);
		if (!strnicmp("CMD", flag, len))
		{
			cmd = 1;
			break;
		}
#if 0
		else if (!strnicmp("SERVER", flag, len))
			server = next_arg(args, &args);
#endif
		else
			yell("Unknown argument to WAIT: %s\n", flag);
	}
	if ((procindex = next_arg(args, &args)) && *procindex == '%' &&
			(index = get_process_index(&procindex)) != -1)
	{
		if (is_process_running(index))
		{
			if (cmd)
			{
				add_process_wait(index, args?args:empty_string);
				return;
			}
			else
				set_wait_process(index);
		}
		else
		{
			say("Not a valid process!");
			return;
		}
	}
	else if (cmd)
	{
		char	buf[BIG_BUFFER_SIZE];

		sprintf(buf, "%s %s", procindex, args);
		typed_add_to_whois_queue(WHOIS_WHOIS, wait_nick, NULL, "%s",
			buf);
		return;
	}
	else
		send_to_server("WHOIS %s", wait_nick);
	if (waiting)
		yell("WAIT has been called recursively.");

	waiting = 1;
	irc_io(null(char *), NULL, 0, 1);
	waiting = 0;
}

/*ARGSUSED*/
static	void	redirect(command, args)
char	*command,
	*args;
{
	char	*who;

	if (who = next_arg(args, &args))
	{
		if (!strcmp(who, "*"))
			if ((who = get_channel_by_refnum(0)) == null(char *))
			{
				say("Must be on a channel to redirect to '*'");
				return;
			}
		if (!stricmp(who, get_server_nickname(from_server)))
		{
			say("You may not redirect output to yourself");
			return;
		}
		window_redirect(who, from_server);
		parse_line(null(char *), args, null(char *), 0, 0);
		send_to_server("WHOIS %s", current_screen->redirect_token,
			current_screen->screennum);
	}
	else
		say("Usage: REDIRECT <nick|channel|%process> <cmd>");
}

/*ARGSUSED*/
static	void	sleepcmd(command, args)
char	*command,
	*args;
{
	char	*arg;

	if (arg = next_arg(args, &args))
		sleep(atoi(arg));
	else
		say("SLEEP: you must specify the amount of time to sleep \
(in seconds)");
}

/*
 * echo: simply displays the args to the screen, or, if it's XECHO,
 * processes the flags first, then displays the text on
 * the screen
 */
void	echo(command, args)
char	*command,
	*args;
{
	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 == '-')
		{
			flag_arg = next_arg(args, &args);
			switch(toupper(flag_arg[1]))
			{
			case 'L':
				if (!(flag_arg = next_arg(args, &args)))
					break;
				if (temp = parse_lastlog_level(flag_arg))
				{
					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;
}

/*
 */
static	void	oper_password_received(data, line)
char	*data;
char	*line;
{
	send_to_server("OPER %s %s", data, line);
}

/* oper: the OPER command.  */
/*ARGSUSED*/
static	void	oper(command, args)
char	*command,
	*args;
{
	char	*password;
	char	*nick;

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

/* This generates a file of your ircII setup */
static	void	really_save(ircrc_file, line)
char	*ircrc_file;
char	*line;
{
	FILE *fp;

	if (*line != 'y' && *line != 'Y')
		return;
	if (fp = fopen(ircrc_file, "w"))
	{
		fprintf(fp, "SET DISPLAY OFF\n");
		save_bindings(fp);
		save_hooks(fp);
		save_notify(fp);
		save_variables(fp);
		save_aliases(fp);
		save_digraphs(fp);
		fprintf(fp, "SET DISPLAY ON\n");
		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 */
/*ARGSUSED*/
static	void	save_settings(command, args)
char	*command,
	*args;
{
	char	*file;

	if (file = next_arg(args, &args))
	{
#ifdef DAEMON_UID
		if (getuid() == DAEMON_UID)
		{
			say("You may only use the default value");
			return;
		}
#endif /* DAEMON_UID */
		if ((ircrc_file = expand_twiddle(file)) == null(char *))
		{
			say("Unknown user");
			return;
		}
	}
	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);
	}
}

/*
 * channel: does the channel command.  I just added displaying your current
 * channel if none is given 
 */
static	void	channel(command, args)
char	*command,
	*args;
{
	char	*chan;
	char	*old;
	int	len;

	if (get_server_version(from_server) > Server2_5)
		command = "JOIN";
	else
		command = "CHANNEL";
	message_from(null(char *), LOG_CURRENT);
	if (chan = next_arg(args, &args))
	{
		if (get_int_var(NOVICE_VAR))
		{
			if ((old = get_channel_by_refnum(0)) && strcmp(old,
					"0"))
				send_to_server("PART %s", old);
		}
		len = strlen(chan);
		if (strnicmp(chan, "-invite", len) == 0)
		{
			if (invite_channel)
				send_to_server("%s %s",command, invite_channel);
			else
				say("You have not been invited to a channel!");
		}
#if 0
		else if (strnicmp(chan, "-nick", len) == 0)
		{
			if (chan = next_arg(args, &args))
			{
				if (index(chan, '*'))
					say("Wildcards not allowed here!");
				else
					add_to_whois_queue(chan, whois_join,
						null(char *));
			}
			else
				say("You must specify a nickname!");
		}
#endif
		else
		{
			if (is_on_channel(chan,
					get_server_nickname(from_server)))
			{
				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);
				malloc_strcpy(&curr_scr_win->waiting_channel,
					chan);
			}
		}
	}
	else
		list_channels();
	message_from(null(char *), LOG_CRAP);
}

/* comment: does the /COMMENT command, useful in .ircrc */
/*ARGSUSED*/
static	void	comment(command, args)
char	*command,
	*args;
{
	/* nothing to do... */
}

/*
 * nick: does the /NICK command.  Records the users current nickname and
 * sends the command on to the server 
 */
/*ARGSUSED*/
static	void	nick(command, args)
char	*command,
	*args;
{
	char	*nick;

	if ((nick = next_arg(args, &args)) == null(char *))
	{
		say("Your nickname is %s",
			get_server_nickname(get_window_server(0)));
		return;
	}
	if (check_nickname(nick))
	{
		if (strcmp(nick, get_server_nickname(from_server)))
		{
			send_to_server("NICK %s", nick);
			if (get_server_version(from_server) == Server2_5)
				add_to_whois_queue(nick, whois_nickname,
					null(char *));
		}
	}
	else
		say("Bad nickname");
}

/* version: does the /VERSION command with some IRCII version stuff */
static	void	version(command, args)
char	*command,
	*args;
{
	char	*host;

	if (host = next_arg(args, &args))
		send_to_server("%s %s", command, host);
	else
	{ 
		say("Client: ircII %s", irc_version);
		send_to_server("%s", command);
	}
}

/*
 * info: does the /INFO command.  I just added some credits
 * I updated most of the text -phone, feb 1993.
 */
static	void	info(command, args)
char	*command,
	*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: phone@coombs.anu.edu.au  IRC: phone");
	say("       Copyright 1990");
	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("       Major 2.2 debugging provided by");
	say("");
	say("       \tMycroft   Charles Hannum         mycroft@gnu.ai.mit.edu");
	say("       \tDaemon    Ian Frechette          frechett@spot.colorado.edu");
	say("       \tpoxaV     Christopher Williams   cgw@unt.edu");
	say("       \tjlemon    Jonathan Lemon         jlemon@amdahl.com");
	say("       \tbpk       Brian Koehmstedt       bpk@gnu.ai.mit.edu");
	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");
	send_to_server("%s %s", command, args);
}

extern	void	ison_ison(askedfor, nicklist)
char	*askedfor;
char	*nicklist;
{
	if (do_hook(current_numeric, "%s", nicklist))
		put_it("%s Currently online: %s", numeric_banner(), nicklist);
}

static	void	ison(command, args)
char	*command;
char	*args;
{
	if (!args[strspn(args, " ")])
		args = get_server_nickname(from_server);
	add_ison_to_whois(args, ison_ison);

}

/*
 * userhost: Does the USERHOST command.  Need to split up the queries,
 * since the server will only reply to 5 at a time.
 */
static	void	userhost(command, args)
char	*command;
char	*args;
{
	int	n = 0,
		total = 0;
	char	*nick;

	while (nick = next_arg(args, &args))
	{
		int	len;

		++total;
		len = strlen(nick);
	/*
	 * Here, if total = 2, then we have only had one nick, and
	 * this second one could be the -cmd arguement.  *ONLY* allow
	 * this form USERHOST NICK -CMD COMMAND.  Don't allow more
	 * than one nick for this case... -phone, jan 1993.
	 */
		if (total == 2 && !strnicmp(nick, "-CMD", len))
		{
			add_to_whois_queue(buffer, userhost_cmd_returned, "%s",
				args);
			n = 0;
			break;
		}
		else
		{
			if (n++)
				strmcat(buffer, " ", BIG_BUFFER_SIZE);
			else
				*buffer = null(char);
			strmcat(buffer, nick, BIG_BUFFER_SIZE);
			if (n >= 5)
			{
				add_to_whois_queue(buffer, USERHOST_USERHOST,
					null(char *));
				n = 0;
			}
		}
	}
	if (n)
		add_to_whois_queue(buffer, USERHOST_USERHOST, null(char *));
	else if (!total)
		/* Default to yourself.  */
		add_to_whois_queue(get_server_nickname(from_server),
				USERHOST_USERHOST, null(char *));
}

/*
 * whois: the WHOIS and WHOWAS commands.  This translates the 
 * to the whois handlers in whois.c 
 */
static	void	whois(command, args)
char	*command,
	*args;
{
	char	*nick;

	if (nick = next_arg(args, &args))
	{
		send_to_server("%s %s %s", command, nick, args);
#if 0
		/* This part is done by ircd anyway -lynx */
		while (nick)
		{
			char	*ptr;

			if (ptr = index(nick, ','))
				*(ptr++) = null(char);
			if (*nick)
			{
				if (args) /*support /whois nick server  -lynx */
					send_to_server("%s %s %s", command,
							args, nick);
				else
					send_to_server("%s %s", command, nick);
			}
			nick = ptr;
		}
#endif
	}
	else /* Default to yourself  -lynx */
		send_to_server("%s %s", command,
			get_server_nickname(from_server));
}

/*
 * 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 
 */
static	void	who(command, args)
char	*command,
	*args;
{
	char	*arg,
		*channel = null(char *);
	int	no_args = 1,
		len;

	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))
	{
		no_args = 0;
		if ((*arg == '-') && (!isdigit(*(arg + 1))))
		{
			arg++;
			if ((len = strlen(arg)) == 0)
			{
				say("Unknown or missing flag");
				return;
			}
			if (strnicmp(arg, "operators", len) == 0)
				who_mask |= WHO_OPS;
			else if (strnicmp(arg, "lusers", len) == 0)
				who_mask |= WHO_LUSERS;
			else if (strnicmp(arg, "chops", len) == 0)
				who_mask |= WHO_CHOPS;
			else if (strnicmp(arg, "hosts", len) == 0)
			{
				if (arg = next_arg(args, &args))
				{
					who_mask |= WHO_HOST;
					malloc_strcpy(&who_host, arg);
					channel = who_host;
				}
				else
				{
					say("WHO -HOSTS: missing arguement");
					return;
				}
			}
			else if (strnicmp(arg, "here", len) ==0)
				who_mask |= WHO_HERE;
			else if (strnicmp(arg, "away", len) ==0)
				who_mask |= WHO_AWAY;
			else if (strnicmp(arg, "servers", len) == 0)
			{
				if (arg = next_arg(args, &args))
				{
					who_mask |= WHO_SERVER;
					malloc_strcpy(&who_server, arg);
					channel = who_server;
				}
				else
				{
					say("WHO -SERVERS: missing arguement");
					return;
				}
			}
			else if (strnicmp(arg, "name", len) == 0)
			{
				if (arg = next_arg(args, &args))
				{
					who_mask |= WHO_NAME;
					malloc_strcpy(&who_name, arg);
					channel = who_name;
				}
				else
				{
					say("WHO -NAME: missing arguement");
					return;
				}
			}
			else if (strnicmp(arg, "realname", len) == 0)
			{
				if (arg = next_arg(args, &args))
				{
					who_mask |= WHO_REAL;
					malloc_strcpy(&who_real, arg);
					channel = who_real;
				}
				else
				{
					say("WHO -REAL: missing arguement");
					return;
				}
			}
			else if (strnicmp(arg, "nick", len) == 0)
			{
				if (arg = next_arg(args, &args))
				{
					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 (strnicmp(arg, "file", len) == 0)
			{
				who_mask |= WHO_FILE;
				if (arg = next_arg(args, &args))
				{
					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');
	}
}

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

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

		if (*nick == '%')
		{
			if (is_process(nick) == 0)
			{
				say("Invalid processes specification");
				message_from(null(char *), 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(char *));
		}
		else
			say("You aren't querying anyone!");
	}
	update_input(UPDATE_ALL);
	message_from(null(char *), LOG_CRAP);
}

/*
 * away: the /AWAY command.  Keeps track of the away message locally, and
 * sends the command on to the server.
 */
static	void	away(command, args)
char	*command,
	*args;
{
	int	len;
	char	*arg = null(char *);
	int	flag = AWAY_ONE;
	int	i;

	if (*args)
	{
		if (*args == '-')
		{
			arg = index(args, ' ');
			if (arg)
				*arg++ = '\0';
			else
				arg = "";
			len = strlen(args);
			if (!strnicmp(args, "-ALL", len))
			{
				flag = AWAY_ALL;
				args = arg;
			}
			else if (strnicmp(args, "-ONE", len) == 0)
			{
				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);
		}
		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();
}

/* quit: The /QUIT, /EXIT, etc command */
/*ARGSUSED*/
static	void	quit(command, args)
char	*command,
	*args;
{
	int	max;
	int	i;
	char	*Reason;

	Reason = ((*args) ? args : "Leaving");
	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();
}

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

/* wall: used for WALL and WALLOPS */
static	void	wall(command, args)
char	*command,
	*args;
{
	if (strcmp(command, "WALL") == 0)
	{	/* I hate this */
		message_from(null(char *), LOG_WALL);
		if (!get_server_operator(from_server))
			put_it("## %s", args);
	}
	else
	{
		message_from(null(char *), LOG_WALLOP);
		if (!get_server_flag(from_server, USER_MODE_W))
			put_it("!! %s", args);
	}
	if (!in_on_who)
		send_to_server("%s :%s", command, args);
	message_from(null(char *), LOG_CRAP);
}

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

	if (nick = next_arg(args, &args))
	{
			/*
			 * I removed this because it just isn't valid
			 * in a world for multi-channels where you can
			 * be on a channel with more than one person
			 * phone/Daemon, feb 1993.
			 */
#if 0
		if (strnicmp(nick, "-channel", strlen(nick)) == 0)
		{
			if (nick = next_arg(args, &args))
			{
				if (index(nick, '*'))
					say("Wildcards not allowed here!");
				else
					add_to_whois_queue(nick,
						whois_privmsg, "%s %%s %s",
						command, args);
			}
			else
				say("You must specify a nickname or channel!");
		}
		else
#endif
		if (strcmp(nick, ".") == 0)
		{
			if ((nick = sent_nick) == null(char *))
			{
				say("You have not sent a \
message to anyone yet");
				return;
			}
		}

		else if (strcmp(nick, ",") == 0)
		{
			if ((nick = recv_nick) == null(char *))
			{
				say("You have not received a message \
from anyone yet");
				return;
			}
		}
		else if (!strcmp(nick, "*"))
			if ((nick = get_channel_by_refnum(0)) ==
					null(char *))
				nick = "0";
		send_text(nick, args, command);
	}
	else
		say("You must specify a nickname or channel!");
}

/*
 * quote: handles the QUOTE command.  args are a direct server command which
 * is simply send directly to the server 
 */
/*ARGSUSED*/
static	void	quote(command, args)
char	*command,
	*args;
{
	if (!in_on_who)
		send_to_server(args);
}

/* clear: the CLEAR command.  Figure it out */
/*ARGSUSED*/
static	void	clear(command, args)
char	*command,
	*args;
{
	char	*arg;

	if (arg = next_arg(args, &args))
	{
	/* -ALL and ALL here becuase the help files used to be wrong */
		if (!strnicmp(arg, "ALL", strlen(arg))
				|| !strnicmp(arg, "-ALL", strlen(arg)))
			clear_all_windows();
		else
			say("Unknown flag: %s", arg);
	}
	else
		clear_window_by_refnum(0);
	update_input(UPDATE_JUST_CURSOR);
}

/*
 * send_com: the generic command function.  Uses the full command name found
 * in 'command', combines it with the 'args', and sends it to the server 
 */
static	void	send_com(command, args)
char	*command,
	*args;
{
	send_to_server("%s %s", command, args);
}

static	void	send_topic(command, args)
char	*command,
	*args;
{
	char	*arg;
	char	*arg2;

	if (arg = next_arg(args, &args))

	{
		if (is_channel(arg))
		{
			if (arg2 = next_arg(args, &args))
				send_to_server("%s %s :%s %s", command, arg,
						arg2, args);
			else
				send_to_server("%s %s", command, arg);
		}
		else
			send_to_server("%s :%s %s", command, arg, args);
	}
	else
		send_to_server("%s", command);
}

/*
 * send_kick: sends a kick message to the server.  Works properly with
 * kick comments.
 */

static	void	send_kick(command, args)
char	*command,
	*args;
{
	char	*kickee,
		*comment;

	if (kickee = index(args, ' '))
		*(kickee++) = null(char);
	else 
		kickee = index(args, null(char));

	if (comment = index(kickee, ' '))
		*(comment++) = null(char);
	else 
		comment = index(kickee, null(char));

	if (strcmp(args, "*") == 0)
		send_to_server("%s %s %s :%s", command,
				get_channel_by_refnum(0), kickee, comment);
	else
		send_to_server("%s %s %s :%s", command, args, 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).
 */
static	void	send_channel_com(command, args)
char	*command,
	*args;
{
	char	*ptr;

	if (ptr = index(args, ' '))
		*(ptr++) = null(char);
	else
		ptr = index(args, null(char));

	if (strcmp(args, "*") == 0)
		send_to_server("%s %s %s", command, get_channel_by_refnum(0),
			ptr);
	else
		send_to_server("%s %s %s", command, args, ptr);
}

/*
 * send_text: Sends the line of text to whomever the user is currently
 * talking.  If they are quering someone, it goes to that user, otherwise
 * it's the current channel.  Some tricky parameter business going on. If
 * nick is null (as if you had typed "/ testing" on the command line) the
 * line is sent to your channel, and the command parameter is never used. If
 * nick is not null, and command is null, the line is sent as a PRIVMSG.  If
 * nick is not null and command is not null, the message is sent using
 * command.  Currently, only NOTICEs and PRIVMSGS work. 
 */
void	send_text(org_nick, line, command)
char	*org_nick;
char	*line;
char	*command;
{
	char	*key,
		*ptr,
		*free_nick,
		*nick = null(char *);
	int	lastlog_level,
		list_type;
	char	the_thing;
	char	*query_command = null(char *);

	malloc_strcpy(&nick, org_nick);
	free_nick = ptr = nick;
	while (nick = ptr)
	{
		if (ptr = index(nick, ','))
			*(ptr++) = null(char);
		if (!*nick)
			continue;
		if (is_process(nick))
		{
			int	i;

			if ((i = get_process_index(&nick)) == -1)
				say("Invalid process specification");
			else
				text_to_process(i, line);
			continue;
		}
		if (!*line)
			continue; /* lynx */
		if (in_on_who)
		{
			say("You may not send messages from ON WHO or \
ON WHOIS");
			continue;
		}
		/* stop the naughties - phone */
		if (doing_privmsg)
			command	= "NOTICE";
		/* Query quote -lynx */
		if (strcmp(nick, "\"") == 0) /* quote */
		{
			send_to_server("%s", line);
			continue;
		}
		if (*nick == '=') /* DCC chat */
		{
			dcc_chat_transmit(nick + 1, line);
			continue;
		}
		if (*nick == '@') /* DCC talk */
		{
			dcc_message_transmit(nick + 1, line, DCC_TALK);
			continue;
		}
		if (*nick == '/') /* Command */
		{
			malloc_strcpy(&query_command, nick);
			malloc_strcat(&query_command, " ");
			malloc_strcat(&query_command, line);
			parse_command(query_command, 0, empty_string);
			new_free(&query_command);
			continue;
		}
		switch (send_text_flag)
		{
		case MSG_LIST:
			command = "NOTICE";
			break;
		case NOTICE_LIST:
			echo(null(char *), "*** You cannot send a message  \
from a NOTICE");
			new_free(&free_nick);
			return;
		}
		if (is_channel(nick))
		{
			int	current;

			current = is_current_channel(nick, 0);
			if ((command == null(char *)) || strcmp(command,
					"NOTICE"))
			{
				command = "PRIVMSG";
				lastlog_level =
					set_lastlog_msg_level(LOG_PUBLIC);
				message_from(nick, LOG_PUBLIC);
				list_type = SEND_PUBLIC_LIST;
				the_thing = '>';
			}
			else
			{
				lastlog_level =
					set_lastlog_msg_level(LOG_NOTICE);
				message_from(nick, LOG_NOTICE);
				list_type = SEND_NOTICE_LIST;
				the_thing = '-';
			}
			if (do_hook(list_type, "%s %s", nick, line))
			{
				if (current)
					put_it("%c %s", the_thing, line);
				else
					put_it("%c%s> %s", the_thing, nick,
						line);
			}
			if (key = is_crypted(nick))
			{
				if ((line = crypt_msg(line, key, 1)) ==
						null(char *))
					continue;
			}
			set_lastlog_msg_level(lastlog_level);
			if (!in_on_who)
			{
				if (get_int_var(USE_OLD_MSG_VAR) &&
						(*nick != '#') && current &&
						(list_type == SEND_PUBLIC_LIST))
					send_to_server("MSG :%s", line);
				else
					send_to_server("%s %s :%s", command,
						nick, line);
			}
		}
		else
		{
			if ((command == null(char *)) || strcmp(command,
					"NOTICE"))
			{
				lastlog_level = set_lastlog_msg_level(LOG_MSG);
				command = "PRIVMSG";
				message_from(nick, LOG_MSG);
				list_type = SEND_MSG_LIST;
				the_thing = '*';
			}
			else
			{
				lastlog_level =
					set_lastlog_msg_level(LOG_NOTICE);
				message_from(nick, LOG_NOTICE);
				list_type = SEND_NOTICE_LIST;
				the_thing = '-';
			}
			if (window_display && do_hook(list_type, "%s %s",
					nick, line))
				put_it("-> %c%s%c %s", the_thing, nick,
					the_thing, line);
			if (key = is_crypted(nick))
			{
				if ((line = crypt_msg(line, key, 1)) ==
						null(char *))
					continue;
			}
			set_lastlog_msg_level(lastlog_level);
			if (!in_on_who)

			{
				if (command)
					send_to_server("%s %s :%s", command,
						nick, line);
				else
					send_to_server("PRIVMSG %s :%s", nick,
						line);
			}
			if (get_int_var(WARN_OF_IGNORES_VAR) &&
			    (is_ignored(nick, IGNORE_MSGS) == IGNORED))
				say("Warning: You are ignoring private \
messages from %s", nick);
			malloc_strcpy(&sent_nick, nick);
			malloc_strcpy(&sent_body, line);
			if (server_list[curr_scr_win->server].away
					&& get_int_var(AUTO_UNMARK_AWAY_VAR))
				away("AWAY", empty_string);
		}
	}
	message_from(null(char *), LOG_CRAP);
	new_free(&free_nick);
}

void	do_send_text(command, args)
char	*command,
	*args;
{
	char	*tmp;

	if (command)
		tmp = get_channel_by_refnum(0);
	else
		tmp = get_target_by_refnum(0);
	send_text(tmp, args, null(char *));
}

/*
 * command_completion: builds lists of commands and aliases that match the
 * given command and displays them for the user's lookseeing 
 */
void	command_completion()
{
	int	do_aliases;
	int	cmd_cnt,
		alias_cnt,
		i,
		c,
		len;
	char	**aliases = null(char **);
	char	*line = null(char *),
		*com,
		*cmdchars,
		*rest;
	IrcCommand	*command;

	malloc_strcpy(&line, get_input());
	if (com = next_arg(line, &rest))
	{
		if ((cmdchars = get_string_var(CMDCHARS_VAR)) == null(char *))
			cmdchars = DEFAULT_CMDCHARS;
		if (index(cmdchars, *com))
		{
			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))
			{
				if (cmd_cnt < 0)
					cmd_cnt *= -1;
				/* special case for the empty string */

				if (*(command[0].name) == null(char))
				{
					command++;
					cmd_cnt = NUMBER_OF_COMMANDS;
				}
			}
			if ((alias_cnt == 1) && (cmd_cnt == 0))
			{
				sprintf(buffer, "/%s %s", aliases[0], rest);
				set_input(buffer);
				new_free(&(aliases[0]));
				new_free(&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, "/%s%s %s", (do_aliases ? "" :
						"/"), command[0].name, rest);
				set_input(buffer);
				update_input(UPDATE_ALL);
			}
			else
			{
				*buffer = null(char);
				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(&aliases);
				}
				if (*buffer == null(char))
					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.
 */
void	parse_line(name, org_line, args, hist_flag, append_flag)
char	*name,
	*org_line,
	*args;
int	hist_flag,
	append_flag;
{
	char	*line = null(char *),
		*ptr,
		*start,
		*free_line,
		*stuff,
		*buffer;
	int	args_flag;

	malloc_strcpy(&line, org_line);
	start = free_line = line;
	args_flag = 0;
	ptr = line;
	if (!*line)
		do_send_text(NULL, empty_string);
	else if (args)
		do
		{
			stuff = expand_alias(name, line, args, &args_flag,
					&line);
			if (!line && append_flag && !args_flag && args && *args)
			{
				buffer = (char *) new_malloc(BIG_BUFFER_SIZE+1);
				strmcpy(buffer, stuff, BIG_BUFFER_SIZE);
				strmcat(buffer, " ", BIG_BUFFER_SIZE);
				strmcat(buffer, args, BIG_BUFFER_SIZE);
				new_free(&stuff);
				stuff = buffer;
			}
			parse_command(stuff, hist_flag, args);
			new_free(&stuff);
		} while(line);
	else
		parse_command(line, hist_flag, args);
	new_free(&free_line);
	return;
}

/*
 * 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 recognized no quoted
 * characters or anything(beyond those specific for a given command being
 * executed). 
 */
void	parse_command(line, hist_flag, sub_args)
char	*line;
int	hist_flag;
char	*sub_args;
{
	static	unsigned int	 level = 0;
	unsigned int	display,
			old_display_var;
	char	*cmdchars, *com,
		*this_cmd = null(char *);
	int	args_flag,
		add_to_hist,
		cmdchar_used;

	if (!line || !*line) return;
	if (get_int_var(DEBUG_VAR) & DEBUG_COMMANDS)
		yell("Executing [%d] %s", level, line);
	level++;
	if ((cmdchars = get_string_var(CMDCHARS_VAR)) == null(char *))
		cmdchars = DEFAULT_CMDCHARS;
	malloc_strcpy(&this_cmd, line);
	add_to_hist = 1;
	if (index(cmdchars, *line))
	{
		cmdchar_used = 1;
		com = line + 1;
	}
	else
	{
		cmdchar_used = 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(char *), line);
		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(char *), line+1);
		if (hist_flag && add_to_hist)
		{
			add_to_history(this_cmd);
			set_input(empty_string);
		}
	}
#if 0 /* ':' is the no-op command. */
	else if (*com == ':' && get_int_var(COMMAND_MODE_VAR))
	{
		me(null(char *), line+1);
		if (hist_flag && add_to_hist)
		{
			add_to_history(this_cmd);
			set_input(empty_string);
		}
	}
#endif
	else if (*com == '@')
	{
		/* This kludge fixes a memory leak */
		char	*tmp;

		tmp = parse_inline(line + 1, sub_args, &args_flag);
		if (tmp)
			new_free(&tmp);
		if (hist_flag && add_to_hist)
		{
			add_to_history(this_cmd);
			set_input(empty_string);
		}
	}
	else
	{
		char	*rest,
			*alias = null(char *),
			*alias_name;
		int	cmd_cnt,
			alias_cnt;
		IrcCommand	*command; /* = null(IrcCommand *) */

		display = window_display;
		old_display_var = (unsigned) get_int_var(DISPLAY_VAR);
		if (rest = index(com, ' '))
			*(rest++) = null(char);
		else
			rest = empty_string;
		upper(com);

		/* first, check aliases */
		if (*com && index(cmdchars, *com))
		{
			alias_cnt = 0;
			com++;
			if (*com == '^')
			{
				com++;
				window_display = 0;
			}
		}
		else{
			if (*com == '^')
			{
				com++;
				window_display = 0;
			}
			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))
				{
					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);
				/* add_to_hist = 0;   -- unused anyway */
			}
			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)))
				{
					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 (!stricmp(com, nickname))
						/* nick = /me  -lynx */
					me(null(char *), rest);
				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--;
}

/*
 * load: the /LOAD command.  Reads the named file, parsing each line as
 * though it were typed in (passes each line to parse_command). 
 */
/*ARGSUSED*/
void	load(command, args)
char	*command,
	*args;
{
	FILE	*fp;
	char	*filename,
		*expanded = null(char *);
	int	flag = 0;
	struct	stat	stat_buf;
	int	paste_level = 0;
	char	*start,
		*current_row = null(char *),
		buffer[BIG_BUFFER_SIZE + 1];
	int	no_semicolon = 1;
	char	*irc_path;
	int	display;
#ifdef ZCAT
	char	*temp;
	char	*expand_z = null(char *);
	int	exists;
#endif

	irc_path = get_string_var(LOAD_PATH_VAR);
	if (!irc_path)

	{
		yell("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);
#ifdef DAEMON_UID
	if (getuid() == DAEMON_UID)
	{
		say("You may only load your SAVED file");
		filename = ircrc_file;
	}
	else
#endif /* DAEMON_UID */
		while (filename = next_arg(args, &args))
		{
			if (strnicmp(filename, "-args", strlen(filename)) == 0)
				flag = 1;
			else
				break;
		}
	if (filename)
	{
		if (expanded = expand_twiddle(filename))
		{
#ifdef ZCAT
			/* Handle both <expanded> and <expanded>.Z */
			temp = &(expanded[strlen(expanded) - 2]);
			if (strcmp(temp, ZSUFFIX))
			{
				malloc_strcpy(&expand_z, expanded);
				malloc_strcat(&expand_z, ZSUFFIX);
			}
#endif /*ZCAT*/
			if (*expanded != '/')
			{
#ifdef ZCAT
				if ((filename = path_search(expanded, irc_path))
						== null(char *))
					filename = path_search(expand_z,
						irc_path);
				if (filename == null(char *))
#else
					if ((filename = path_search(expanded,
						irc_path)) == null(char *))
#endif /*ZCAT*/
					{
						say("%s: File not found",
							expanded);
						status_update(1);
						load_depth--;
#ifdef ZCAT
						new_free(&expand_z);
#endif /* ZCAT */
						new_free(&expanded);
						return;
					}
					else
					    malloc_strcpy(&expanded, filename);
			}
#ifdef ZCAT
			if ((exists = stat_file(expanded, &stat_buf)) == -1)
				if (!(exists = stat_file(expand_z, &stat_buf)))
					expanded = expand_z;
				else
					new_free(&expand_z);
			if (exists == 0)
#else
				if (!stat_file(expanded, &stat_buf))
#endif /*ZCAT*/
				{
					if (stat_buf.st_mode & S_IFDIR)
					{
						say("%s is a directory",
							expanded);
						status_update(1);
						load_depth--;
#ifdef ZCAT
						new_free(&expand_z);
#endif /* ZCAT */
						new_free(&expanded);
						return;
					}
					if (stat_buf.st_mode & 0111)
					{
						say("%s is executable and may \
not be loaded", expanded);
						status_update(1);
						load_depth--;
#ifdef ZCAT
						new_free(&expand_z);
#endif /* ZCAT */
						new_free(&expanded);
						return;
					}
				}
			if (command && *command == 'W')
			{
				say("%s", expanded);
				status_update(1);
				load_depth--;
				new_free(&expanded);
				return;
			}
#ifdef ZCAT
			/* Open if uncompressed, zcat if compressed */
			temp = &(expanded[strlen(expanded)-2]);
			if (!strcmp(temp, ZSUFFIX))
				fp = zcat(expanded);
			else
				fp = fopen(expanded, "r");
			if (fp != NULL)
#else
				if (fp = fopen(expanded, "r"))
#endif /*ZCAT*/
				{
					display = window_display;
					window_display = 0;
					current_row = NULL;
					if (!flag)
						args = null(char *);
					for (;;)
					{
						if (fgets(buffer,
						    BIG_BUFFER_SIZE / 2, fp))
/*
 * We move this section of code over some distance, to get the whole
 * lot under 80 columns - phone, jan 1993.
 */
	{
		int	len;
		char	*ptr;

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

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

		while (start && *start)
		{
			char	*optr = start;
			while ((ptr = sindex(optr, "{};")) &&
					ptr != optr &&
					ptr[-1] == '\\')
				optr = ptr+1;

			if (no_semicolon)
			{
#if 0
				while (isspace(*start))
					start++;
#endif
				no_semicolon = 0;
			}
			else if ((!ptr || ptr != start) && current_row)
			{
				if (!paste_level)
				{
					parse_line(null(char *), current_row,
						args, 0, 0);
					new_free(&current_row);
				}
				else
					malloc_strcat(&current_row, ";");
			}

			if (ptr)
			{
				char	c = *ptr;

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

				switch (c)
				{
				case '{' :
					paste_level++;
					if (ptr == start)
					    malloc_strcat(&current_row, " {");
					else
					    malloc_strcat(&current_row, "{");
					no_semicolon = 1;
					break;

				case '}' :
					if (!paste_level)
						yell("Unexpected }");
					else
					{
					    --paste_level;
					    malloc_strcat(&current_row, "}");
					    no_semicolon = ptr[1] ? 1 : 0;
					}
					break;

				case ';' :
					malloc_strcat(&current_row, ";");
					no_semicolon = 1;
					break;
				}

				start = ptr+1;
			}
			else
			{
				malloc_strcat(&current_row, start);
				start = null(char *);
			}
		}
	}
/*
 * Until here - phone..  now isn't that much more beautiful ?
 */
						else
							break;
					}
					if (current_row)
					{
						if (paste_level)
							yell("Unexpected EOF");
						else
							parse_line(null(char *),
								current_row, 
								args, 0, 0);
						new_free(&current_row);
					}
					window_display = display;
					fclose(fp);
				}
				else
					say("Couldn't open %s: %s", expanded,
						sys_errlist[errno]);
			new_free(&expanded);
		}
		else
			say("Unknown user");
	}
	else
		say("No filename specified");
	status_update(1);
	load_depth--;
}

/*
 * get_history: gets the next history entry, either the PREV entry or the
 * NEXT entry, and sets it to the current input string 
 */
void	get_history(which)
int	which;
{
	char	*ptr;

	if (ptr = get_from_history(which))
	{
		set_input(ptr);
		update_input(UPDATE_ALL);
	}
}

/* BIND function: */
void	forward_character() { input_move_cursor(RIGHT); }
void	backward_character() { input_move_cursor(LEFT); }
void	backward_history() { get_history(PREV); }
void	forward_history() { get_history(NEXT); }
void	toggle_insert_mode() { set_var_value(INSERT_MODE_VAR, "TOGGLE"); }

/*ARGSUSED*/
void	send_line(key, ptr)
{
	int	server;
	WaitPrompt	*OldPrompt;

	server = from_server;
	from_server = get_window_server(0);
	reset_hold(null(Window *));
	hold_mode(null(Window *), OFF, 1);
	if (current_screen->promptlist && current_screen->promptlist->type ==
			WAIT_PROMPT_LINE)
	{
		OldPrompt = current_screen->promptlist;
		(*OldPrompt->func)(OldPrompt->data, get_input());
		set_input(empty_string);
		current_screen->promptlist = OldPrompt->next;
		new_free(&OldPrompt->data);
		new_free(&OldPrompt->prompt);
		new_free(&OldPrompt);
		change_input_prompt(-1);
	}
	else
	{
		char	*line,
			*tmp = null(char *);

		line = get_input();
		malloc_strcpy(&tmp, line);

		if (do_hook(INPUT_LIST, "%s", tmp))
		{
			if (get_int_var(INPUT_ALIASES_VAR))
				parse_line(null(char *), tmp, empty_string,
					1, 0);
			else
				parse_line(null(char *), tmp, null(char *),
					1, 0);
		}
		update_input(UPDATE_ALL);
		new_free(&tmp);
	}
	from_server = server;
}

/* The SENDLINE command.. */
static	void	sendlinecmd(command, args)
char	*command,
	*args;
{
	int	server;
	int	display;

	server = from_server;
	display = window_display;
	window_display = 1;
	if (get_int_var(INPUT_ALIASES_VAR))
		parse_line(null(char *), args, empty_string, 1, 0);
	else
		parse_line(null(char *), args, null(char *), 1, 0);
	update_input(UPDATE_ALL);
	window_display = display;
	from_server = server;
}

/*ARGSUSED*/
void	meta4_char(key, ptr)
{
	current_screen->meta4_hit = 1 - current_screen->meta4_hit;
}

/*ARGSUSED*/
void	meta3_char(key, ptr)
{
	current_screen->meta3_hit = 1;
}

/*ARGSUSED*/
void	meta2_char(key, ptr)
{
	current_screen->meta2_hit = 1;
}

/*ARGSUSED*/
void	meta1_char(key, ptr)
{
	current_screen->meta1_hit = 1;
}

void	quote_char(c)
char	c;
{
	current_screen->quote_hit = 1;
}

/* type_text: the BIND function TYPE_TEXT */
/*ARGSUSED*/
void	type_text(c, str)
char	c,
	*str;
{
	for (; *str; str++)
		input_add_character(*str);
}

/*
 * clear_screen: the CLEAR_SCREEN function for BIND.  Clears the screen and
 * starts it if it is held 
 */
/*ARGSUSED*/
void	clear_screen(c, str)
char	c,
	*str;
{
	hold_mode(null(Window *), OFF, 1);
	clear(null(char *), empty_string);
}

/* parse_text: the bindable function that executes its string */
void	parse_text(c, str)
char	c,
	*str;
{
	parse_line(null(char *), str, empty_string, 0, 0);
}

/*
 * edit_char: handles each character for an input stream.  Not too difficult
 * to work out.
 */
void	edit_char(key)
char	key;
{
	void	(*func) ();
	char	*ptr;
	char	extended_key;
	WaitPrompt *oldprompt;

	if (current_screen->promptlist &&
			current_screen->promptlist->type == WAIT_PROMPT_KEY)
	{
		oldprompt = current_screen->promptlist;
		(*oldprompt->func)(oldprompt->data, &key);
		set_input(empty_string);
		current_screen->promptlist = oldprompt->next;
		new_free(&oldprompt->data);
		new_free(&oldprompt->prompt);
		new_free(&oldprompt);
		change_input_prompt(-1);
		return;
	}
	if (translation)
		extended_key = transFromClient[key];
	else
		extended_key = key;
	key &= 127;			/* mask out non-ascii crap */
	if (current_screen->meta1_hit)
	{
		func = key_names[meta1_keys[key].index].func;
		ptr = meta1_keys[key].stuff;
		current_screen->meta1_hit = 0;
	}
	else if (current_screen->meta2_hit)
	{
		func = key_names[meta2_keys[key].index].func;
		ptr = meta2_keys[key].stuff;
		current_screen->meta2_hit = 0;
	}
	else if (current_screen->meta3_hit)
	{
		func = key_names[meta3_keys[key].index].func;
		ptr = meta3_keys[key].stuff;
		current_screen->meta3_hit = 0;
	}
	else if (current_screen->meta4_hit)
	{
		func = key_names[meta4_keys[key].index].func;
		ptr = meta4_keys[key].stuff;
	}
	else
	{
		func = key_names[keys[key].index].func;
		ptr = keys[key].stuff;
	}
	if (!current_screen->meta1_hit && !current_screen->meta2_hit &&
			!current_screen->meta3_hit)
	{
		if (current_screen->inside_menu == 1)
			menu_key(key);
		else if (current_screen->digraph_hit)
		{
			if (extended_key == 0x08 || extended_key == 0x7f)
				current_screen->digraph_hit = 0;
			else if (current_screen->digraph_hit == 1)
			{
				current_screen->digraph_first = extended_key;
				current_screen->digraph_hit = 2;
			}
			else if (current_screen->digraph_hit == 2)
			{
				if (extended_key = get_digraph(extended_key))
					input_add_character(extended_key);
				else
					term_beep();
			}
		}
		else if (current_screen->quote_hit)
		{
			current_screen->quote_hit = 0;
			input_add_character(extended_key);
		}
		else if (func)
			func(extended_key, ptr ? ptr : empty_string);
	}
	else
		term_beep();
}

/*ARGSUSED*/
static	void	cd(command, args)
char	*command,
	*args;
{
	char	*arg,
		*expand;

#ifdef DAEMON_UID
	if (getuid() == DAEMON_UID)
	{
		say("You are not permitted to use this command");
		return;
	}
#endif /* DAEMON_UID */
	if (arg = next_arg(args, &args))
	{
		if (expand = expand_twiddle(arg))
		{
			if (chdir(expand))
				say("CD: %s", sys_errlist[errno]);
			new_free(&expand);
		}
		else
			say("CD: No such user");
	}
#if defined(ISC22) || defined(SVR4) || defined(_IBMR2) || \
	defined(POSIX) || defined(linux)
	getcwd(buffer, BIG_BUFFER_SIZE);
#else
	getwd(buffer);
#endif
	say("Current directory: %s", buffer);
}

static	void	send_action(target, text)
char	*target, *text;
{
	send_ctcp(ctcp_type[CTCP_PRIVMSG], target, "ACTION", text);
}

#if 0
static	char*	prepare_action(string)
char*	string;
{
	short	last;
	char	*message;

	last = strlen(string) - 1;
	while(string[last] == ' ')
		if (--last < 0) return NULL;

	if ((string[last] > 'a' && string[last] < 'z') ||
			(string[last] > 'A' && string[last] < 'Z'))
	{
		message = new_malloc(last + 2);
		strmcpy (message, string, last+1);
		message[last + 1] = '.';
		message[last + 2] = '\0';
		return message;
	}
	else
		return NULL;
}
#endif

static	void	describe(command, args)
char	*command,
	*args;
{
	char	*target;

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

#if 0
		if (result = prepare_action(args))
			message = result;
		else
#endif
			message = args;
		send_action(target, 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(SHOW_ACTION_TO, target,
				get_server_nickname(from_server), message);
		set_lastlog_msg_level(old);
		message_from_level(from_level);

#if 0
		if (result)
			new_free(&result);
#endif
	}
	else
		say("Usage: /DESCRIBE <target> <action description>");
}

/*
 * New 'me' command - now automatically appends period.
 * Necessary for new 'action' script.   -lynx'92
 * Hardly, removed this as it was generally considered offensive
 */
static	void	me(command, args)
char	*command,
	*args;
{
	if (args && *args)
	{
		char	*target;

		if (target = get_target_by_refnum(0))
		{
			int	old;
			int	from_level;
#if 0
			char	*result;
#endif
			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;
				}
#if 0
			if (result = prepare_action(args))
				message = result;
			else
#endif
				message = args;
			send_action(target, 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(SHOW_ACTION_OUT,
					get_server_nickname(from_server),
						message);
			set_lastlog_msg_level(old);
			message_from_level(from_level);

#if 0
			if (result)
				new_free(&result);
#endif
		}
		else
			say("No target, neither channel nor query");
	}
	else
		say("Usage: /ME <action description>");
}

static	void	mload(command, args)
char	*command,
	*args;
{
	char	*file;

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

static	void	mlist(command, args)
char	*command,
	*args;
{
	char	*menu;

	while (menu = new_next_arg(args, &args))
		ShowMenu(menu, 0);
}

static	void	evalcmd(command, args, subargs)
char	*command,
	*args,
	*subargs;
{
	parse_line(null(char *), args, subargs ? subargs : empty_string, 0, 0);
}

/*
 * ExecuteTimers:  checks to see if any currently pending timers have
 * gone off, and if so, execute them, delete them, etc, setting the
 * current_exec_timer, so that we can't remove the timer while its
 * still executing.
 */
extern	void	ExecuteTimers()
{
	long	current;
	TimerList *next;

	time(&current);
	while (PendingTimers && PendingTimers->time <= current)
	{
		current_exec_timer = PendingTimers->ref;
		parse_command(PendingTimers->command, 0, empty_string);
		current_exec_timer = -1;
		new_free(&PendingTimers->command);
		next = PendingTimers->next;
		new_free(&PendingTimers);
		PendingTimers = next;
	}
}

/*
 * timercmd: the bit that handles the TIMER command.  If there are no
 * arguements, then just list the currently pending timers, if we are
 * give a -DELETE flag, attempt to delete the timer from the list.  Else
 * consider it to be a timer to add, and add it.
 */
static	void	timercmd(command, args)
char	*command;
char	*args;
{
	char	*waittime,
		*flag;
	long	current;
	TimerList	**slot,
			*ntimer;
	int	want = -1,
		refnum;;

	if (*args == '-')
	{
		int	len;

		flag = next_arg(args, &args);
		len = strlen(flag);

		/* first check for the -DELETE flag */

		if (!strnicmp(flag, "-DELETE", len))
		{
			char	*ptr;
			int	ref;
			TimerList	*tmp,
					*prev;

			if (current_exec_timer != -1)
			{
				say("You may not remove a TIMER from itself");
				return;
			}
			if (!(ptr = next_arg(args, &args)))
			{
				say("%s: Need a timer reference number for \
-DELETE", command);
				return;
			}
			ref = atoi(ptr);
			for (prev = tmp = PendingTimers; tmp; prev = tmp,
					tmp = tmp->next)
			{
				if (tmp->ref == ref)
				{
					if (tmp == prev)
						PendingTimers =
							PendingTimers->next;
					else
						prev->next = tmp->next;
					new_free(&tmp->command);
					new_free(&tmp);
					return;
				}
			}
			say("%s: Can't delete %d, no such refnum",
				command, ref);
			return;
		}
		else if (!strnicmp(flag, "-REFNUM", len))
		{
			char	*ptr;

			ptr = next_arg(args, &args);
			want = atoi(ptr);
			if (want < 0)
			{
				say("%s: Illegal refnum %d", command, want);
				return;
			}
		}
		else
		{
			say("%s: %s no such flag", command, flag);
			return;
		}
	}

	/* else check to see if we have no args -> list */

	if (!(waittime = next_arg(args, &args)))
	{
		show_timer(command);
		return;
	}

	/* must be something to add */

	if ((refnum = create_timer_ref(want)) == -1)
	{
		say("%s: Refnum %d already exists", command, want);
		return;
	}
	time(&current);
	ntimer = (TimerList *) new_malloc(sizeof(TimerList));
	ntimer->time = current + atol(waittime);
	ntimer->ref = refnum;
	ntimer->command = null(char *);
	malloc_strcpy(&ntimer->command, args);

	/* we've created it, now put it in order */

	for (slot = &PendingTimers; *slot; slot = &(*slot)->next)
	{
		if ((*slot)->time > ntimer->time)
			break;
	}
	ntimer->next = *slot;
	*slot = ntimer;
}

/*
 * show_timer:  Display a list of all the TIMER commands that are
 * pending to be executed.
 */
static	void	show_timer(command)
char	*command;
{
	TimerList	*tmp;
	long	current,
		time_left;

	if (!PendingTimers)
	{
		say("%s: No commands pending to be executed", command);
		return;
	}

	time(&current);
	say("Timer Seconds   Command");
	for (tmp = PendingTimers; tmp; tmp = tmp->next)
	{
		time_left = tmp->time - current;
		if (time_left < 0)
			time_left = 0;
		say("%-5d %-10d %s", tmp->ref, time_left, tmp->command);
	}
}

/*
 * create_timer_ref:  returns the lowest unused reference number for
 * a timer
 */
static	int	create_timer_ref(want)
int	want;
{
	TimerList	*tmp;
	int	ref = 0;
	int	done = 0;

	if (want == -1)
		while (!done)
		{
			done = 1;
			for (tmp = PendingTimers; tmp; tmp = tmp->next)
				if (ref == tmp->ref)
				{
					ref++;
					done = 0;
					break;
				}
		}
	else
	{
		ref = want;
		for (tmp = PendingTimers; tmp; tmp = tmp->next)
			if (ref == tmp->ref)
			{
				ref = -1;
				break;
			}
	}

	return (ref);
}
/*
 * 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.
 */

static	void	inputcmd(command, args)
char	*command,
	*args;
{
	char	*prompt;

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

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

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

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

/*
 * eval_inputlist:  Cute little wrapper that calls parse_line() when we
 * get an input prompt ..
 */

void	eval_inputlist(args, line)
char	*args,
	*line;
{
	parse_line(null(char *), args, line ? line : empty_string, 0, 0);
}


static	void    pingcmd(command, args)
char    *command,
	*args;
{
	char	buf[40];

	sprintf(buf, "%s PING %ld", args, time(NULL));
	ctcp(command, buf);
}
