/*
 * This is panasync's tcl code for BitchX.
 *  (c) 1996 Colten Edwards
 *
 * Released under the 3-clause BSD license in 2012.  See the COPYRIGHT file.
 */

#include "irc.h"
#include "struct.h"
#include "parse.h"
#include "server.h"
#include "chelp.h"
#include "commands.h"
#include "vars.h"
#include "ircaux.h"
#include "lastlog.h"
#include "window.h"
#include "screen.h"
#include "who.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 "output.h"
#include "exec.h"
#include "notify.h"
#include "status.h"
#include "if.h"
#include "help.h"
#include "stack.h"
#include "queue.h"
#include "timer.h"
#include "list.h"
#include "userlist.h"
#include "misc.h"
#include "who.h"
#include "whowas.h"
#include "hash2.h"
#include "tcl_bx.h"
#define MAIN_SOURCE
#include "modval.h"


char tcl_versionstr[] = "Tcl 2.1";

#ifdef WANT_TCL

Tcl_HashTable *gethashtable (int, int *, char *);

TimerList *tcl_Pending_timers = NULL;
TimerList *tcl_Pending_utimers = NULL;

Tcl_HashTable	H_msg, H_dcc, H_fil, H_pub, H_msgm, H_pubm, H_join, H_part,
		H_sign, H_kick, H_topc, H_mode, H_ctcp, H_ctcr, H_nick, H_raw, H_bot,
		H_chon, H_chof, H_sent, H_rcvd, H_chat, H_link, H_disc, H_splt, H_rejn,
		H_filt, H_cmd, H_input, H_notice, H_alias, H_help, H_functions, H_hook;

#define CMD_MSG   0
#define CMD_DCC   1
#define CMD_FIL   2
#define CMD_PUB   3
#define CMD_MSGM  4
#define CMD_PUBM  5
#define CMD_JOIN  6
#define CMD_PART  7
#define CMD_SIGN  8
#define CMD_KICK  9
#define CMD_TOPC  10
#define CMD_MODE  11
#define CMD_CTCP  12
#define CMD_CTCR  13
#define CMD_NICK  14
#define CMD_RAW   15
#define CMD_BOT   16
#define CMD_CHON  17
#define CMD_CHOF  18
#define CMD_SENT  19
#define CMD_RCVD  20
#define CMD_CHAT  21
#define CMD_LINK  22
#define CMD_DISC  23
#define CMD_SPLT  24
#define CMD_REJN  25
#define CMD_FILT  26
#define CMD_CMD   27
#define CMD_INPUT 28
#define CMD_ALIAS 29
#define CMD_HELP 30
#define CMD_HOOK 31
#define CMD_FUNCTION 32
#define BINDS 33

/* extra commands are stored in Tcl hash tables (one hash table for each type
   of command: msg, dcc, etc) */

typedef struct tct {
	int flags_needed;
	char *func_name;
	char *(*func) (char *, char *);
	struct tct *next;
} tcl_cmd_t;


typedef struct
{
	char	name;
	char	*(*func) (void);
}	BuiltIns;
                


BuiltInFunctions 	*find_func_alias (char *);
BuiltIns 		*find_func_aliasvar(char *);
IrcVariable 		*get_var_address(char *);
IrcVariable 		*fget_var_address(char *);
void 			add_tcl_fset(Tcl_Interp *);

extern			cmd_t C_msg[], C_ctcp[], C_notice[], C_dcc[];


int in_add_to_tcl = 0;

int tcl_bots STDVAR
{
char buff[BIG_BUFFER_SIZE];
SocketList *s;
int count = 0;
int i;
	BADARGS(1, 1, "");
	
	for (i = 0; i < get_max_fd()+1; i++)
	{
		if (!check_dcc_socket(i))
			continue;
		s = get_socketinfo(i);
		if ((s->flags & DCC_TYPES) == DCC_BOTMODE)
		{
			DCC_int *n;
			n = (DCC_int *)s->info;
			snprintf(buff, sizeof buff, "%u %s", n->dccnum, s->server);
			Tcl_AppendElement(irp, buff);
			count++;
		}
	}
	if (count)
		return TCL_OK;
	Tcl_AppendResult(irp, "No bots linked", NULL);
	return TCL_ERROR;
}

int tcl_clients STDVAR
{
SocketList *s;
DCC_int *n;
int i;
char buff[BIG_BUFFER_SIZE];
int count = 0;

	BADARGS(1, 1, "");
	
	for (i = 0; i < get_max_fd()+1; i++)
	{
		if (!check_dcc_socket(i))
			continue;
		s = get_socket(i);
		if ((s->flags & DCC_TYPES) == DCC_CHAT)
		{
			n = (DCC_int *) s->info;
			snprintf(buff, sizeof buff, "%u %s", n->dccnum, s->server);
			Tcl_AppendElement(irp, buff);
			count++;
		}
	}
	if (count)
		return TCL_OK;

	Tcl_AppendResult(irp, "No dcc chats", NULL);
	return TCL_ERROR;
}

void add_to_tcl(Window *tmp, char *buf)
{
	Tcl_AppendElement(tcl_interp, stripansicodes(buf));
}

int tcl_alias STDVAR
{
BuiltInFunctions *tmp;
char *buffer = NULL;
char *ret = NULL;
int i;
char *p;
	if (argc == 1)
	{
		Tcl_AppendResult(irp, " ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 1; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);

	p = argv[0];
	p++;
	if (p && (tmp = find_func_alias(p)))
	{
		ret = (char *)(*tmp->func)(tmp->name, buffer);
		Tcl_AppendResult(irp, ret, NULL);
		new_free(&ret);
	}
	new_free(&buffer);
	return TCL_OK;
}

int tcl_aliasvar STDVAR
{
BuiltIns *tmp;
char *ret = NULL;
char *p;
	p = argv[0];
	p++;
	if (*p && (tmp = find_func_aliasvar(p)))
	{
		ret = (char *)(*tmp->func)();
		Tcl_AppendResult(irp, ret, NULL);
		new_free(&ret);
	}
	return TCL_OK;
}


int tcl_get_var STDVAR
{
char *s = NULL;
	BADARGS(2,2," ?variable?");
	if (argv[1] && (s = make_string_var(argv[1])))
	{
		Tcl_AppendResult(irp, s, NULL);
		new_free(&s);
		return TCL_OK;
	}
	Tcl_AppendResult(irp, " No such variable", NULL);
	return TCL_ERROR;
}

int tcl_set_var STDVAR
{
int ret = TCL_ERROR, i;
char *buffer = NULL;
IrcVariable *tmp = NULL;
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 2; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	if ((tmp = get_var_address(argv[1])))
	{
		switch (tmp->type)
		{
			case STR_TYPE_VAR:
			{
				ret = TCL_OK;
				if (buffer)
					malloc_strcpy(&tmp->string, buffer);
				else
					new_free(&tmp->string);
				break;
			}
			case BOOL_TYPE_VAR:
			case INT_TYPE_VAR:
			{
				ret = TCL_OK;
				if (buffer)
				{
					int j;
					j = my_atol(buffer);
					if (j && (tmp->type == BOOL_TYPE_VAR))
						tmp->integer = 1;
					else
						tmp->integer = j;
				} else
					tmp->integer = 0;
				break;
			}
		}
	}
	new_free(&buffer);
	return ret;
}

int tcl_fget_var STDVAR
{
char *s = NULL;
	BADARGS(2,2," ?variable?");
	
	if (argv[1] && (s = make_fstring_var(argv[1])))
	{
		Tcl_AppendResult(irp, s, NULL);
		new_free(&s);
		return TCL_OK;
	}
	Tcl_AppendResult(irp, " No such variable", NULL);
	return TCL_ERROR;
}

int tcl_fset_var STDVAR
{
int ret = TCL_ERROR, i;
char *buffer = NULL;
IrcVariable *tmp = NULL;
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 2; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	if ((tmp = fget_var_address(argv[1])))
	{
		switch (tmp->type)
		{
			case STR_TYPE_VAR:
			{
				ret = TCL_OK;
				if (buffer)
					malloc_strcpy(&tmp->string, buffer);
				else
					new_free(&tmp->string);
				break;
			}
			case BOOL_TYPE_VAR:
			case INT_TYPE_VAR:
			{
				ret = TCL_OK;
				if (buffer)
				{
					int j;
					j = my_atol(buffer);
					if (j && (tmp->type == BOOL_TYPE_VAR))
						tmp->integer = 1;
					else
						tmp->integer = j;
				} else
					tmp->integer = 0;
				break;
			}
		}
	}
	new_free(&buffer);
	return ret;
}



int tcl_cparse STDVAR
{
char *buf;
char *buffer = NULL;
int i;
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 1; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	buf = convert_output_format(buffer, NULL, NULL);
	Tcl_AppendResult(irp, buf, NULL);
	new_free(&buffer);
	return TCL_OK;
}

extern void got_tcluserhost (UserhostItem *stuff, char *nick, char *userhost)
{
char *buffer = NULL;
	if (stuff && strcmp(stuff->host, "<UNKNOWN>") && strcmp(stuff->user, "<UNKNOWN>"))
	{
		malloc_sprintf(&buffer, "%s %s %s", nick, stuff->user, stuff->host);
		Tcl_SetVar(tcl_interp, "userhost", buffer, TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
		new_free(&buffer);
		return;
	}	
	Tcl_SetVar(tcl_interp, "un-userhost", nick, TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
	return;
}

int tcl_userhost STDVAR
{
char *buffer = NULL;
int i;
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " ?args?", NULL);
		return TCL_ERROR;
	}
	Tcl_UnsetVar(irp, "userhost", TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT);
	Tcl_UnsetVar(irp, "un-userhost", TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT);
	for (i = 1; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	userhostbase(buffer, got_tcluserhost, 1, "%s", buffer);
	new_free(&buffer);
	return TCL_OK;
}

int tcl_msg STDVAR
{
char *buffer = NULL;
int i;
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " channel|nick ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 2; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	send_text(argv[1], buffer, (!strcmp(argv[0], "notice") ? STXT_NOTICE : 0) | STXT_LOG);
	new_free(&buffer);
	return TCL_OK;
}

int tcl_say STDVAR
{
char *buffer = NULL;
int i;
	BADARGS(1,999," ?args?");
	if (argc == 1)
	{
		Tcl_AppendResult(irp, " ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 1; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	send_text(get_current_channel_by_refnum(0), buffer, STXT_LOG);
	new_free(&buffer);
	return TCL_OK;
}


int tcl_desc STDVAR
{
char *buffer = NULL;
int i;
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " target ?args?", NULL);
		return TCL_ERROR;
	}
	for (i = 2; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);
	send_ctcp(CTCP_PRIVMSG, argv[1], CTCP_ACTION, "%s", buffer);
	new_free(&buffer);
	return TCL_OK;
}


int tcl_getchanmode STDVAR
{
ChannelList *chan;
	BADARGS(2,2, " channel");
	chan = lookup_channel(argv[1], from_server, 0);
	if (chan)
	{
		Tcl_AppendResult(irp, recreate_mode(chan), NULL);
		return TCL_OK;
	}
	Tcl_AppendResult(irp, " not on that channel", NULL);
	return TCL_ERROR;
}

int tcl_setcomment STDVAR
{
UserList *tmp;
void *tmp2 = NULL;
int i = 0;
int size = -1;

	BADARGS(2,999, " nick");
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " nick args", NULL);
		return TCL_ERROR;
	}
	for (tmp = next_userlist(NULL, &size, &tmp2); tmp; tmp = next_userlist(tmp, &size, &tmp2))
	{
		if (!my_stricmp(tmp->nick, argv[1]))
		{
			for (i = 2; i < argc; i++)
				m_s3cat(&tmp->comment, space, argv[i]);
		}
	}
	return TCL_OK;
}

int tcl_getcomment STDVAR
{
UserList *tmp;
void *tmp2 = NULL;
int size = -1;
	BADARGS(2,2, " nick");
	Tcl_AppendResult(irp, "", NULL);
	for (tmp = next_userlist(NULL, &size, &tmp2); tmp; tmp = next_userlist(tmp, &size, &tmp2))
	{
		if (tmp->comment)
		{
			Tcl_AppendResult(irp, tmp->comment, NULL);
			break;
		}
	}
	return TCL_OK;
}

int tcl_strftime STDVAR
{
	char buf[BIG_BUFFER_SIZE];
	struct tm *tm1;
	time_t t;	
	BADARGS(3,3, " format time");
	t = atol(argv[2]);
	tm1 = localtime(&t);
	if (strftime(buf, sizeof(buf)-1, argv[1], tm1))
	{
		Tcl_AppendResult(irp, buf, NULL);
		return TCL_OK;
	}
	Tcl_AppendResult(irp, " error with strftime" , NULL);
	return TCL_ERROR;
}


int tcl_ircii STDVAR
{
char *buffer = NULL;
int i;
	
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " command args", NULL);
		return TCL_ERROR;
	}
	for (i = 1; i < argc; i++)
		m_s3cat(&buffer, space, argv[i]);

	in_add_to_tcl++;
	parse_line(NULL, buffer, NULL, 0, 0, 1);
	in_add_to_tcl--;
	new_free(&buffer);
	if (get_string_var(BOT_RETURN_VAR))
	{
		char *arg = NULL;
		malloc_sprintf(&arg, "set ircii %s", get_string_var(BOT_RETURN_VAR));
		Tcl_Eval(tcl_interp, arg);
		new_free(&arg);
	}
	return TCL_OK;
}

int tcl_pushmode STDVAR
{
ChannelList *chan = NULL;
char *mode;
char plus;
int num_modes = 3;
	
	BADARGS(3,4," channel mode ?arg?");
	if (!(chan = lookup_channel(argv[1], from_server, CHAN_NOUNLINK)))
	{
		Tcl_AppendResult(irp,"invalid channel: ",argv[1],NULL);
		return TCL_ERROR;
	}
	plus=argv[2][0]; 
	mode= (char *)&argv[2][1];
	
	if ((plus!='+') && (plus!='-')) 
	{ 
		mode=argv[2]; 
		plus='+'; 
	}
	if ((*mode < 'a') || (*mode > 'z')) 
	{
		Tcl_AppendResult(irp,"invalid mode: ",argv[2],NULL);
		return TCL_ERROR;
	}
	if ((argc<4) && (!strchr(mode, 'o') || !strchr(mode, 'v') || !strchr(mode,'b')))
	{
		Tcl_AppendResult(irp,"modes b/v/o require an argument",NULL);
		return TCL_ERROR;
	}
	num_modes = (*mode == 'b') ? get_int_var(NUM_BANMODES_VAR) : get_int_var(NUM_OPMODES_VAR);
	if (argc==4) 
		add_mode(chan,(char *)mode,plus == '+'?1:0, argv[3], NULL, num_modes);
	else 
		add_mode(chan,(char *)mode,plus == '+'?1:0, NULL, NULL, num_modes);
	
	return TCL_OK;
}

int tcl_flushmode STDVAR
{
ChannelList *chan;
	
	BADARGS(2,2," channel");
	if (!(chan = lookup_channel(argv[1], from_server, CHAN_NOUNLINK)))
	{
		Tcl_AppendResult(irp,"invalid channel: ",argv[1],NULL);
		return TCL_ERROR;
	}
	flush_mode_all(chan);
	return TCL_OK;              
}

int tcl_maskhost STDVAR
{
char buffer[BIG_BUFFER_SIZE+1];
char *uh, *h;
	
	BADARGS(2, 2, " nick!user@host");
	strcpy(buffer, argv[1]);
	if (!(uh = strchr(buffer, '!')) || !(h = strchr(buffer, '@')))
		return TCL_ERROR;
	*uh++ = '\0';
	Tcl_AppendResult(irp, cluster(uh), NULL);
	return TCL_OK;
}

int tcl_onchansplit STDVAR
{
WhowasList *tmp;
int found = 0;
	
	BADARGS(3, 3, " nickname channel");
	for (tmp = next_userhost(&whowas_splitin_list, NULL); tmp; tmp = next_userhost(&whowas_splitin_list, tmp))
	{
		if (!my_stricmp(tmp->nicklist->nick, argv[1]) && !my_stricmp(tmp->channel, argv[2]))
		{
			found = 1;
			break;
		}	
	}
	Tcl_AppendResult(irp, found?one:zero, NULL);
	return TCL_OK;
}

int tcl_timers STDVAR
{
	
	BADARGS(1, 1, "");
	tcl_list_timer(irp, tcl_Pending_timers);
	return TCL_OK;
}

int tcl_utimers STDVAR
{
	
	BADARGS(1, 1, "");
	tcl_list_timer(irp, tcl_Pending_utimers);
	return TCL_OK;
}

int tcl_servers STDVAR
{
char *s = NULL;
int i;
	
	for (i = 0; i < server_list_size(); i++)
	{
		malloc_sprintf(&s, "%d %s %d %d", i, get_server_name(i), get_server_port(i), is_server_connected(i));
		Tcl_AppendElement(irp, s);
	}
	new_free(&s);
	return TCL_OK;
}

extern char *get_dcc_info(SocketList *, DCC_int *, int);

int tcl_dcc_stat STDVAR
{
int i;
	for (i = 0; i < get_max_fd()+1; i++)
	{
		if (check_dcc_socket(i))
		{
			SocketList *s;
			char *str;
			s = get_socket(i);
			str = get_dcc_info(s, s->info, i);
			Tcl_AppendElement(irp, str);
			new_free(&str);
		}
	}
	return TCL_OK;
}

int tcl_dcc_close STDVAR
{
int i;
	BADARGS(2, 2, " number");
	i = atol(argv[1]);
	if (i)
		close_dcc_number(i);	
	return TCL_OK;
}

void chan_struct(Tcl_Interp *irp, ChannelList *c)
{
char *s = NULL;
	
	malloc_sprintf(&s, "%s %d %lu %s %d %d %d %lu %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %lu %d %lu %d %lu %d %lu %d %lu %d %d %d %d %d %d %d %lu %d %d %lu %d %d", 
		c->channel, c->server, c->mode, c->s_mode, c->limit, 
		(int)c->have_op, (int)c->voice, c->channel_create.tv_sec, c->join_time.tv_sec, c->stats_ops, 
		c->stats_dops, c->stats_bans, c->stats_unbans, c->stats_sops, 
		c->stats_sdops, c->stats_sbans, c->stats_sunbans, c->stats_topics, 
		c->stats_kicks, c->stats_pubs, c->stats_parts, c->stats_signoffs, 
		c->stats_joins, c->csets->set_ainv, c->csets->set_auto_rejoin, 
		c->csets->set_deop_on_deopflood, c->csets->set_deop_on_kickflood, 
		c->csets->set_deopflood, c->csets->set_deopflood_time, c->csets->set_hacking, 
		c->csets->set_kick_on_deopflood, c->csets->set_kick_on_joinflood, 
		c->csets->set_kick_on_kickflood, c->csets->set_kick_on_nickflood, 
		c->csets->set_kick_on_pubflood, c->csets->set_kickflood, c->csets->set_kickflood_time, 
		c->csets->set_nickflood, c->csets->set_nickflood_time, c->csets->set_joinflood, 
		c->csets->set_joinflood_time, c->csets->set_pubflood, c->csets->set_pubflood_time, 
		c->csets->set_pubflood_ignore, c->csets->set_userlist, c->csets->set_shitlist, c->csets->set_lamelist, 
		c->csets->set_kick_if_banned, c->totalnicks, c->maxnicks, c->maxnickstime, 
		c->totalbans, c->maxbans, c->maxbanstime, c->csets->set_aop, c->csets->bitch_mode);
	Tcl_AppendElement(irp, s);
	new_free(&s);
}

int tcl_chanstruct STDVAR
{
ChannelList *c;
	
	BADARGS(1, 1, "");
	for (c = get_server_channels(from_server); c; c = c->next)
		chan_struct(irp, c);
	return TCL_OK;
}

int tcl_channels STDVAR
{
ChannelList *c;
	
	BADARGS(1, 1, "");
	for (c = get_server_channels(from_server); c; c = c->next)
		Tcl_AppendElement(irp, c->channel);
	return TCL_OK;
}

int tcl_channel_modify(Tcl_Interp *irp, char *channel, ChannelList *chan, int argc, char **argv)
{
	
	return TCL_OK;
}

ChannelList *tcl_channel_add(Tcl_Interp *irp, char *chan, char *options)
{
ChannelList *tmp = NULL;
int argc = 0; char **argv = NULL;

	
	if (Tcl_SplitList(irp, options, &argc, &argv) != TCL_OK)
		return NULL;
	
	if (!(tmp = lookup_channel(chan, from_server, CHAN_NOUNLINK)))
		tmp = add_channel(chan, from_server, 0);
	if (tmp)
		tcl_channel_modify(irp, chan, tmp, argc, argv);
	return tmp ? tmp : NULL;
}

int tcl_channel STDVAR
{
ChannelList *chan = NULL; 
int server = from_server;
	
	if (argc <= 2)
	{
		Tcl_AppendResult(irp, " command ?options?", NULL);
		return TCL_ERROR;
	}
	if (!my_stricmp(argv[1], "add"))
	{
		BADARGS(3, 999, " add channel-name ?options?");
		if (!(chan = tcl_channel_add(irp, argv[2], argc==3?"":argv[3])))
		{
			Tcl_AppendResult(irp, " channel error in [channel]", NULL);
			return TCL_ERROR;
		}
		send_to_server("JOIN %s %s", chan->channel, chan->key?chan->key:"");
		return TCL_OK;
	}
	if (!my_stricmp(argv[1], "set"))
	{
		BADARGS(3, 3, " set channel-name ?options?");
		if (!(chan = prepare_command(&server, argv[2], 3)))
		{
	        	WhowasChanList *whowaschan;
			if ((whowaschan = check_whowas_chan_buffer(argv[2], 0, 0)) == NULL)
			{
				Tcl_AppendResult(irp, " no such channel", NULL);
				return TCL_ERROR;
			} else
				chan = whowaschan->channellist;
		}
		return tcl_channel_modify(irp, chan->channel, chan, argc-3, &argv[3]);
	}
	if (!my_stricmp(argv[1], "info"))
	{
		BADARGS(3, 3, " info channel-name");
		if (!(chan = prepare_command(&server, argv[2], 3)))
		{
			Tcl_AppendResult(irp, " no such channel", NULL);
			return TCL_ERROR;
		}
		chan_struct(irp, chan);
		return TCL_OK;
	}
	if (!my_stricmp(argv[1], "remove"))
	{
		BADARGS(3, 3, " remove channel-name");
		if (!(chan = prepare_command(&server, argv[2], 3)))
		{
			Tcl_AppendResult(irp, " no such channel", NULL);
			return TCL_ERROR;
		}
		send_to_server("PART %s", chan->channel);
		return TCL_OK;
	}
	Tcl_AppendResult(irp,"unknown channel command: should be one of: ", "add, set, info, remove",NULL);
	return TCL_ERROR;
}

int tcl_getchanhost STDVAR
{
ChannelList *chan;
NickList *nick;
	
	BADARGS(3,3," nickname channel");
	if ((chan = lookup_channel(argv[2], from_server, 0)))
	{
		if ((nick = find_nicklist_in_channellist(argv[1], chan, 0)))
		{
			Tcl_AppendResult(irp, nick->host, NULL);
			return TCL_OK;
		}
		Tcl_AppendResult(irp, "no such nick: ", NULL);
	} else
		Tcl_AppendResult(irp, "illegal channel: ", NULL);
	return TCL_ERROR;
}

int is_user(char *nick)
{
UserList *tmp = NULL;
void *tmp2 = NULL;
int size = -1;
	for (tmp = next_userlist(NULL, &size, &tmp2); tmp; tmp = next_userlist(tmp, &size, &tmp2))
		if (!my_stricmp(nick, tmp->nick))
			break;
	return tmp?1:0;
}

int tcl_validuser STDVAR
{
	BADARGS(2,2," handle");
	if (is_user(argv[1])) 
		Tcl_AppendResult(irp,one,NULL);
	else 
		Tcl_AppendResult(irp,zero,NULL);
	return TCL_OK;
}
        
int tcl_finduser STDVAR
{
char *n, *host, *s = NULL;
UserList *nick;

	
	BADARGS(2, 2, " nick!user@host");
	n = argv[1];
	if ((host = strchr(argv[1], '!')))
		*host++ = '\0';
	if ((nick = lookup_userlevelc("*", host, "*", NULL)))
		malloc_sprintf(&s, "%d %s%s%s", nick->flags, nick->channels,nick->password?" ":"",nick->password?nick->password:"");
	else
		malloc_strcat(&s, "*");
	Tcl_AppendResult(irp, s, NULL);
	new_free(&s);
	return TCL_OK;
}

int tcl_findshit STDVAR
{
char *n, *host, *s = NULL;
ShitList *nick;

	
	BADARGS(2, 2, " nick!user@host");
	n = argv[1];
	if ((host = strchr(argv[1], '!')))
		*host++ = '\0';
	nick = nickinshit(n, host);
	if (nick)
		malloc_sprintf(&s, "%d %s %s", nick->level, nick->channels, nick->reason);
	else
		malloc_strcat(&s, "*");
	Tcl_AppendResult(irp, s, NULL);
	new_free(&s);
	return TCL_OK;
}

int matchattr STDVAR
{
int is_match = 0;
unsigned int flags = 0;
NickList *nick = NULL;
ChannelList *chan;

	
	BADARGS(3, 3, " nickname level");
	for (chan = get_server_channels(from_server); chan; chan = chan->next)
	{
		for (nick = next_nicklist(chan, NULL); nick; nick = next_nicklist(chan, nick))
			if (wild_match(argv[1], nick->nick))
				goto found;
	}
found:
	if (!nick)
	{
		Tcl_AppendResult(irp, "no such nick: ", NULL);
		return TCL_ERROR;
	}

	flags = convert_str_to_flags(argv[2]);
	if (nick->userlist && (nick->userlist->flags & flags))
		is_match = 1;
	Tcl_AppendResult(irp, is_match ? one:zero, NULL);
	return TCL_OK;
}

int tcl_isop STDVAR
{
ChannelList *chan;
NickList *nick;

	
	BADARGS(3, 3, " nick channel");
	if ((chan = lookup_channel(argv[2], from_server, 0)))
	{
		if ((nick = find_nicklist_in_channellist(argv[1], chan, 0)))
		{
			if (nick_isop(nick))
				Tcl_AppendResult(irp, one, NULL);
			else
				Tcl_AppendResult(irp, zero, NULL);
			return TCL_OK;
		}
		Tcl_AppendResult(irp, "no such nick: ", NULL);
		return TCL_ERROR;
	} 
	Tcl_AppendResult(irp, "illegal channel: ", NULL);
	return TCL_ERROR;
}

int tcl_date STDVAR
{
char s[81]; time_t t;

	
	BADARGS(1,1,"");
	t = now; 
	strcpy(s,my_ctime(t));
	s[10]=s[24]=0; 
	strcpy(s,&s[8]); 
	strcpy(&s[8],&s[20]);
	strcpy(&s[2],&s[3]);
	Tcl_AppendResult(irp,s,NULL);
	return TCL_OK;
}

int tcl_time STDVAR
{
char s[81]; time_t t;

	
	BADARGS(1,1,"");
	t = now; 
	strcpy(s,my_ctime(t));
	strcpy(s,&s[11]); 
	s[5]=0;
	Tcl_AppendResult(irp,s,NULL);
	return TCL_OK;
}

int tcl_ctime STDVAR
{

	
	BADARGS(2, 2, " unixtime");
	Tcl_AppendResult(irp, my_ctime((time_t)atol(argv[1])), NULL);
	return TCL_OK;
}

int tcl_chanlist STDVAR
{
ChannelList *chan;
NickList *nick;
int level = 0;

	
	BADARGS(2, 3, " channel <flags>");
	if (!(chan = lookup_channel(argv[1], from_server, 0)))
	{
		Tcl_AppendResult(irp, "invalid channel:", argv[1], NULL);
		return TCL_ERROR;
	}
	if (argc == 2)
	{
		for (nick = next_nicklist(chan, NULL); nick; nick = next_nicklist(chan, nick))
			Tcl_AppendElement(irp, nick->nick);
		return TCL_OK;
	}
	if (isdigit(*argv[2]))
	{
		level = atoi(argv[2]);
		for (nick = next_nicklist(chan, NULL); nick; nick = next_nicklist(chan, nick))
		{
			if ((level <= 0) || (level && nick->userlist && nick->userlist->flags >= level))
				Tcl_AppendElement(irp, nick->nick);
		}
	} 
	else if (tolower(*argv[2]) == 'o' || tolower(*argv[2]) =='v')
	{
		for (nick = next_nicklist(chan, NULL); nick; nick = next_nicklist(chan, nick))
		{
			if (!nick_isop(nick) && !nick_isvoice(nick))
				continue;
			switch(tolower(*argv[2]))
			{
				case 'o':
					if (nick_isop(nick))
						Tcl_AppendElement(irp, nick->nick);
					break;
				case 'v':
					if (nick_isvoice(nick))
						Tcl_AppendElement(irp, nick->nick);
					break;
			}
		}
	}
	return TCL_OK;
}

int tcl_onchan STDVAR
{
ChannelList *chan;
NickList *nick;

	
	BADARGS(3, 3, " nickname channel");
	if (!(chan = lookup_channel(argv[2], from_server, 0)))
	{
		Tcl_AppendResult(irp, "invalid channel:", argv[2], NULL);
		return TCL_ERROR;
	}
	if ((nick = find_nicklist_in_channellist(argv[1], chan, 0)))
		Tcl_AppendResult(irp, one, NULL);
	else
		Tcl_AppendResult(irp, zero, NULL);
	return TCL_OK; 
}

int tcl_putlog STDVAR
{
char logtext[BIG_BUFFER_SIZE+1];

	
	BADARGS(2,2," text");
	strlcpy(logtext, argv[1], sizeof logtext);
	putlog(LOG_CRAP,"*","%s",logtext);
	return TCL_OK;
}

int tcl_putcmdlog STDVAR
{
char logtext[BIG_BUFFER_SIZE+1];

	
	BADARGS(2,2," text");
	strlcpy(logtext, argv[1], sizeof logtext);
	putlog(LOG_ALL,"*","%s",logtext);
	return TCL_OK;
}

int tcl_putloglev STDVAR
{
int i,lev=0; 
char logtext[BIG_BUFFER_SIZE + 1];

	
	BADARGS(4,4," level channel text");
	i=atoi(argv[1]); 
	if (i < 1 || i > 5) 
	{
		Tcl_AppendResult(irp,"level out of range (must be 1-5)",NULL);
		return TCL_ERROR;
	}
	switch(i) 
	{
		case 1: 
			lev=LOG_USER1; 
			break;
		case 2: 
			lev=LOG_USER2; 
			break;
		case 3: 
			lev=LOG_USER3; 
			break;
		case 4: 
			lev=LOG_USER4; 
			break;
		case 5: 
			lev=LOG_USER5; 
			break;
	}
	strlcpy(logtext, argv[3], sizeof logtext);
	putlog(lev,argv[2],"%s",logtext);
	return TCL_OK;
}

int tcl_unixtime STDVAR
{
	char s[60]; 

	
	BADARGS(1,1,"");
	snprintf(s, sizeof s, "%lu", (unsigned long)now);
	Tcl_AppendResult(irp,s,NULL);
	return TCL_OK;
}

int tcl_putserv STDVAR
{
char s[BIG_BUFFER_SIZE+1],*p;

	
	BADARGS(2,2," \"text\"");
	strlcpy(s, argv[1], sizeof s);
	if ((p=strchr(s,'\n')))
		*p = 0;
	if ((p=strchr(s,'\r')))
		*p=0;
	send_to_server("%s",s); 
	return TCL_OK;
}

int tcl_putscr STDVAR
{
char s[BIG_BUFFER_SIZE+1],*p;

	
	BADARGS(2,2," \"text\"");
	strlcpy(s, argv[1], sizeof s);
	if ((p=strchr(s,'\n')))
		*p = 0;
	if ((p=strchr(s,'\r')))
		*p=0;
	put_it("%s",s); 
	return TCL_OK;
}

int tcl_putdcc STDVAR
{
char s[BIG_BUFFER_SIZE+1];
int idx = 0;
	BADARGS(3,3 ," hand idx text");

	strlcpy(s, argv[2], sizeof s - 1);
	strlcat(s, "\n", sizeof s);
	idx = atoi(argv[1]);
	send(idx, s, strlen(s), 0);
	return TCL_OK;
}

int tcl_putbot STDVAR
{
char *msg = NULL;
SocketList *s = NULL;	
	BADARGS(3,3," botnick message");
	s = find_dcc(argv[1], "chat", NULL, DCC_BOTMODE, 0, 1, -1);
	if (!s) 
	{
		Tcl_AppendResult(irp,"user is not connected",NULL);
		return TCL_ERROR;
	}
	malloc_sprintf(&msg, "zapf %s %s %s\n", get_server_nickname(from_server), argv[1], argv[2]);
	send(s->is_read, msg, strlen(msg), 0);
	new_free(&msg);
	return TCL_OK;
}

int tcl_putallbots STDVAR
{
char *msg = NULL;
int i;
SocketList *s;
	BADARGS(2,2," message");
	malloc_sprintf(&msg, "zapf-broad %s %s\n", get_server_nickname(from_server), argv[1]); 
	for (i = 0; i < get_max_fd()+1; i++)
	{
		if (!check_dcc_socket(i))
			continue;
		s = get_socketinfo(i);
		if (((s->flags & DCC_TYPES) == DCC_BOTMODE) && (s->is_read != -1))
			send(s->is_read, msg, strlen(msg), 0);
	}
	new_free(&msg);
	return TCL_OK;
}

/* initialize hash tables */
void init_hash()
{

	
	Tcl_InitHashTable(&H_msg,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_dcc,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_fil,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_pub,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_msgm,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_pubm,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_join,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_part,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_sign,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_kick,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_topc,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_mode,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_ctcp,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_ctcr,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_nick,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_raw,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_bot,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_chon,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_chof,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_sent,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_rcvd,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_chat,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_link,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_disc,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_splt,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_rejn,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_filt,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_cmd,TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_input, TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_notice, TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_alias, TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_help, TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_functions, TCL_STRING_KEYS);
	Tcl_InitHashTable(&H_hook, TCL_STRING_KEYS);
}


/* remove command */
int cmd_unbind(int typ,int flags,char *cmd, char *proc)
{
tcl_cmd_t *tt,*last; 
Tcl_HashEntry *he; Tcl_HashTable *ht;


	
	ht=gethashtable(typ,NULL,NULL);
	he=Tcl_FindHashEntry(ht,cmd);

	if (he==NULL) 
		return 0;   /* no such binding */
	tt=(tcl_cmd_t *)Tcl_GetHashValue(he); 
	last=NULL;

	while (tt!=NULL) 
	{
		/* if procs are same, erase regardless of flags */
		if (!my_stricmp(tt->func_name,proc)) {
			/* erase it */
			if (last!=NULL) 
				last->next=tt->next;
			else 
			{
				if (tt->next==NULL) 
					Tcl_DeleteHashEntry(he);
				else 
					Tcl_SetHashValue(he,tt->next);
			}
			new_free((char **)&tt->func_name);
			new_free((char **)&tt); 
			return 1;
		}
		last=tt; 
		tt=tt->next;
	}
	return 0;   /* no match */
}

/* add command (remove old one if necessary) */
int cmd_bind(int typ,int flags,char *cmd, char *proc, char *(*func)(char *, char *))
{
tcl_cmd_t *tt; 
int new; 
Tcl_HashEntry *he; 
Tcl_HashTable *ht; 
int stk;

	
	if (proc[0]=='#') 
		return 0;

	cmd_unbind(typ,flags,cmd,proc);    /* make sure we don't dup */
	tt=(tcl_cmd_t *)new_malloc(sizeof(tcl_cmd_t)); 
	
	tt->flags_needed=flags; 
	tt->next=NULL;
	malloc_strcpy(&(tt->func_name), proc);
	tt->func = func;
	ht=gethashtable(typ,&stk,NULL);
	he=Tcl_CreateHashEntry(ht,cmd,&new);
	
	if (!new) 
	{
		tt->next=(tcl_cmd_t *)Tcl_GetHashValue(he);
		if (!stk) 
		{
			/* remove old one -- these are not stackable */
			new_free(&tt->next->func_name); 
			new_free((char **)&tt->next); 
			tt->next=NULL;
		}
	}
	Tcl_SetHashValue(he,tt);
	return 1;
}

/* match types for check_tcl_bind */
#define MATCH_PARTIAL       0
#define MATCH_EXACT         1
#define MATCH_MASK          2
/* bitwise 'or' these: */
#define BIND_USE_ATTR       4
#define BIND_STACKABLE      8
#define BIND_HAS_BUILTINS   16
#define BIND_WANTRET        32

/* return values */
#define BIND_NOMATCH    0
#define BIND_AMBIGUOUS  1
#define BIND_MATCHED    2    /* but the proc couldn't be found */
#define BIND_EXECUTED   3
#define BIND_EXEC_LOG   4    /* proc returned 1 -> wants to be logged */

/* 10+  -->  matches builtin # (retval-10)  */

/* trigger (execute) a proc */

int trigger_bind(char *proc, char *param, char *(*func)(char *, char *))
{
	char *result = NULL;
	int err;
	
	if (internal_debug & DEBUG_TCL)
		debugyell("Tcl exec [%s] with [%s]", proc, param);

	if (func)
	{
		result = (*func)(proc, param);
		Tcl_AppendResult(tcl_interp,result);
		new_free(&result);
		if (internal_debug & DEBUG_TCL)
			debugyell("Tcl return from [%s] with [%s]", proc, result);
		return BIND_EXECUTED;
	}

	err = Tcl_VarEval(tcl_interp, proc, param, NULL);
	result = Tcl_GetStringResult(tcl_interp);

	if (internal_debug & DEBUG_TCL)
		debugyell("Tcl return from [%s] with [%s]", proc, result);
	if (err == TCL_ERROR) 
	{
		putlog(LOG_ALL,"*","Tcl error [%s]: %s", proc, result);
		return BIND_EXECUTED;
	}
	return (atoi(result)>0)?BIND_EXEC_LOG:BIND_EXECUTED;
}

void init_builtins()
{
int i, j, new; 
Tcl_HashTable *ht=NULL; 
Tcl_HashEntry *he;
tcl_cmd_t *tt; 
cmd_t *cc=NULL;


	
	for (j = 0; j < 4; j++ )
	{	
		switch(j)
		{
			case 0:
				ht = &H_msg; cc = C_msg; break;
			case 1:
				ht = &H_dcc; cc = C_dcc; break;
			case 2:
				ht = &H_ctcp; cc = C_ctcp; break;
			case 3:
				ht = &H_notice; cc = C_notice; break;
		}
		i = 0;
		while (cc[i].access != -1) 
		{
			tt = (tcl_cmd_t *) new_malloc(sizeof(tcl_cmd_t));
			tt->func_name = m_sprintf("*%s", cc[i].name);  
			tt->flags_needed=cc[i].access; 
			tt->next=NULL;
			he=Tcl_CreateHashEntry(ht,cc[i].name,&new);
			if (!new) 
			{
				/* append old entry */
				tcl_cmd_t *ttx=(tcl_cmd_t *)Tcl_GetHashValue(he);
				Tcl_DeleteHashEntry(he);
				tt->next=ttx;
			}
			Tcl_SetHashValue(he,tt);
			i++;
		}
	}	
}


int flags_needed(unsigned long atr, unsigned long needed)
{
#if 0
	if (atr & ADD_OWNER) return 1;
	if (atr & ADD_MASTER) return 1;
#endif
	if ((atr & needed) == needed) return 1;
	return 0;
}

/* check a tcl binding and execute the procs necessary */
int check_tcl_bind(Tcl_HashTable *hash, char *mat, int atr, char *param, 
		int match_type, cmd_t *builtin)
{
Tcl_HashSearch srch; 
Tcl_HashEntry *he; 
int cnt=0; 
char *proc=NULL;
tcl_cmd_t *tt; 
int f=0,atrok,x;


	
	for (he=Tcl_FirstHashEntry(hash,&srch); (he!=NULL) && (!f); he=Tcl_NextHashEntry(&srch)) 
	{
		int ok=0;
		switch (match_type&0x03) {
			case MATCH_PARTIAL:
				ok=(my_strnicmp(mat,Tcl_GetHashKey(hash,he),strlen(mat))==0); 
				break;
			case MATCH_EXACT:
				ok=(my_stricmp(mat,Tcl_GetHashKey(hash,he))==0);
				break;
			case MATCH_MASK:
				ok=wild_match(Tcl_GetHashKey(hash,he), mat);
				break;
		}
		if (ok) {
			tt=(tcl_cmd_t *)Tcl_GetHashValue(he);
			switch (match_type&0x03) 
			{
				case MATCH_MASK:
					/* could be multiple triggers */
					while (tt!=NULL) 
					{
						if (match_type & BIND_HAS_BUILTINS)
							atrok = flags_needed(atr, tt->flags_needed);
						else
							atrok = ((atr & tt->flags_needed) == tt->flags_needed);
#if 0
						if (match_type & BIND_HAS_BUILTINS)
							atrok= (atr > tt->flags_needed);
						else 
							atrok= (atr >= tt->flags_needed);
#endif
						if ((!(match_type&BIND_USE_ATTR)) || atrok)
						{
							cnt++; 
							x=trigger_bind(tt->func_name,param, tt->func);
							if ((match_type & BIND_WANTRET) && (x==BIND_EXEC_LOG))
								return x;
						}
						tt=tt->next;
					}
					break;
				default:
					if (match_type & BIND_HAS_BUILTINS)   
						atrok = flags_needed(atr, tt->flags_needed);
					else
						atrok = ((atr & tt->flags_needed) == tt->flags_needed);
#if 0
					else 
						atrok=(atr >= tt->flags_needed);
#endif
					if ((!(match_type&BIND_USE_ATTR)) || atrok) 
					{
						cnt++; 
						proc=tt->func_name;
						if (my_stricmp(mat,Tcl_GetHashKey(hash,he))==0) 
						{
							cnt=1; 
							f=1;  /* perfect match */
						}
					}
					break;
			}
		}
	}
	if (cnt==0) return BIND_NOMATCH;
	if ((match_type&0x03)==MATCH_MASK) return BIND_EXECUTED;
	if (cnt>1) return BIND_AMBIGUOUS;

	if ((match_type&BIND_HAS_BUILTINS) && (proc[0]=='*')) 
	{
		/* calling builtin function */
		int top=0,bot=0,try=0,xx; 
		char pr[81];
		
		strcpy(pr,&proc[1]); 
		f=0;
		while (builtin[bot].access!=(-1)) bot++;  /* find bottom */
		
		/* binary search: */
		while (!f) 
		{
			try=(top+bot)/2; 
			xx=my_stricmp(builtin[try].name,pr);
			if (xx==0) f=1;
			if (xx<0) top=try+1;
			if (xx>0) bot=try;
			if (top==bot)
				return BIND_MATCHED;
		}
		return try+10;
	}
	return trigger_bind(proc,param, NULL);
}

int tcl_timer STDVAR
{
char *x; char s[80];
	BADARGS(3,3," minutes command");
	if (atoi(argv[1]) <= 0) 
	{
		Tcl_AppendResult(irp,"time value must be positive, nonzero",NULL);
		return TCL_ERROR;
	}
	if (argv[2][0]!='#') 
	{
		x = tcl_add_timer(&tcl_Pending_timers, atol(argv[1])*60, argv[2], 0L);
		snprintf(s, sizeof s, "timer%s", x); 
		Tcl_AppendResult(irp,s,NULL);
	}
	return TCL_OK;
}

int tcl_rand STDVAR
{
unsigned long x; 
char s[80];

	
	BADARGS(2,2," limit");
	if (strtoul(argv[1], NULL, 10) <= 0)
	{
		Tcl_AppendResult(irp,"limit must be greater than 0",NULL);
		return TCL_ERROR;
	}
 	x = rand() % (strtoul(argv[1], NULL, 10));
	snprintf(s, sizeof s, "%lu", x);
	Tcl_AppendResult(irp,s,NULL);
	return TCL_OK;
}

int tcl_utimer STDVAR
{
char *x; 
char s[80];
	BADARGS(3,3," seconds command");
	if (atoi(argv[1]) <= 0) 
	{
		Tcl_AppendResult(irp,"time value must be positive, nonzero",NULL);
		return TCL_ERROR;
	}
	if (argv[2][0]!='#') 
	{
		x=tcl_add_timer(&tcl_Pending_utimers, atol(argv[1]), argv[2], 0L);
		snprintf(s, sizeof s, "timer%s", x); 
		Tcl_AppendResult(irp,s,NULL);
	}
	return TCL_OK;
}

int tcl_killtimer STDVAR
{
	BADARGS(2,2," timerID");
	if (!strbegins(argv[1],"timer")) 
	{
		Tcl_AppendResult(irp,"argument is not a timerID",NULL);
		return TCL_ERROR;
	}
	if (tcl_remove_timer(&tcl_Pending_timers, atol(&argv[1][5]))) 
		return TCL_OK;
	Tcl_AppendResult(irp,"invalid timerID",NULL);
	return TCL_ERROR;
}

int tcl_killutimer STDVAR
{
	BADARGS(2,2," timerID");
	if (!strbegins(argv[1],"timer")) 
	{
		Tcl_AppendResult(irp,"argument is not a timerID",NULL);
		return TCL_ERROR;
	}
	if (tcl_remove_timer(&tcl_Pending_utimers, atol(&argv[1][5]))) 
		return TCL_OK;
	Tcl_AppendResult(irp,"invalid timerID",NULL);
	return TCL_ERROR;
}

/* check for tcl-bound msg command, return 1 if found */
/* msg: proc-name <nick> <user@host> <handle> <args...> */
int check_tcl_msg(char *cmd, char *nick, char *uhost, char *hand, char *args)
{
int x, atr = 0;
UserList *n;
	
	if (!cmd)
		return 0;
	if ((n = lookup_userlevelc("*", uhost, "*", NULL)))
		atr = n->flags;
		
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",!n?hand:n->nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",args,TCL_GLOBAL_ONLY);
	x=check_tcl_bind(&H_msg,cmd,atr," $_n $_uh $_h $_a",
		MATCH_PARTIAL|BIND_HAS_BUILTINS|BIND_USE_ATTR,C_msg);
	if (x == BIND_EXEC_LOG)
		putlog(LOG_TCL, "*", "(%s!%s) !%s! %s %s",nick,uhost,hand,cmd,args);
	if (x>=10) 
	{
		if (internal_debug & DEBUG_TCL)
			debugyell("Tcl func [%s] with [%s %s %s %s]", C_msg[x-10].name , hand, nick, uhost, args);
		return (C_msg[x-10].func)(hand,nick,uhost,args);
	}
	return ((x==BIND_MATCHED)||(x==BIND_EXECUTED)||(x==BIND_EXEC_LOG));
}

/* check for tcl-bound dcc msg command, return 1 if found */
/* msg: proc-name <nick> <user@host> <idx>*/
int check_tcl_dcc(char *cmd, char *nick, char *host, int idx)
{
	int x, atr = 1;
	char *c, *args;
	UserList *n;
	DCC_int *info;
	
	info = get_socketinfo(idx);
	if (info->ul)
		atr = info->ul->flags;
#if 0		
	if ((n = lookup_userlevelc("*", host, "*", NULL)))
	{
		DCC_int *info;
		info = get_socketinfo(idx);
		atr = n->flags;
	}
#endif
	if (!cmd || !*cmd)
		return 0;
	c = next_arg(cmd, &cmd);
	if (!c || !*c)
		return 0;

	args = cmd;
	Tcl_SetVar(tcl_interp,"_i",ltoa(idx), TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",host,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",args,TCL_GLOBAL_ONLY);
	x=check_tcl_bind(&H_dcc,c,atr," $_n $_i $_a", MATCH_PARTIAL|BIND_HAS_BUILTINS|BIND_USE_ATTR, C_dcc);
	if (x==BIND_AMBIGUOUS || x==BIND_NOMATCH)
		return 0;
	if (x>=10) 
	{
		if (C_dcc[x-10].func == NULL)
			return 1;
		if (internal_debug & DEBUG_TCL)
			debugyell("Tcl func [%s] with [%d %s]", C_dcc[x-10].name , idx, args);
		(C_dcc[x-10].func)(idx,args);
		return 1;
	}
	return 1;
}

int check_tcl_ctcp(char *nick, char *uhost, char *hand, char *dest, char *keyword, char *args)
{
	int atr = 0,x;
	UserList *n;
	
	if ((n = lookup_userlevelc("*", uhost, "*", NULL)))
		atr = n->flags;
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",dest,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",keyword,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aaa",args,TCL_GLOBAL_ONLY);
	x=check_tcl_bind(&H_ctcp,keyword,atr," $_n $_uh $_h $_a $_aa $_aaa",
		MATCH_MASK|BIND_HAS_BUILTINS|BIND_USE_ATTR,NULL);
	return ((x==BIND_MATCHED)||(x==BIND_EXECUTED)||(x==BIND_EXEC_LOG));
}

int check_tcl_pub(char *nick, char *from, char *chname, char *msg)
{
int x,atr = 0; 
char *args,*cmd, *free_me;
UserList *n;
	
	free_me = args = m_strdup(msg); 

	cmd = next_arg(args, &args);
	if (!cmd || !*cmd)
	{
		new_free(&free_me);
		return 0;
	}
	if ((n = lookup_userlevelc("*",from, chname, NULL)))
		atr = n->flags;
	
	Tcl_SetVar(tcl_interp,"_n",		nick,		TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",	from,		TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",		n?n->nick:nick,	TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",		chname,		TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",	args,		TCL_GLOBAL_ONLY);
	
	x=check_tcl_bind(&H_pub,cmd,atr," $_n $_uh $_h $_a $_aa",
		MATCH_EXACT|BIND_USE_ATTR,NULL);
	new_free(&free_me);
	if (x==BIND_NOMATCH) return 0;

	return 1;
}

void check_tcl_pubm(char *nick, char *uhost, char *chname, char *msg)
{
char *args = NULL; 
int atr = 0;
UserList *n;	

	
	malloc_sprintf(&args, "%s %s", chname, msg);
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",msg,TCL_GLOBAL_ONLY);
	
	check_tcl_bind(&H_pubm,args,atr," $_n $_uh $_h $_a $_aa",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
	new_free(&args);
}

void check_tcl_msgm(char *cmd, char *nick, char *uhost, char *hand, char *arg)
{
int atr = 0; 
char *args = NULL;
UserList *n;
	
	if (!cmd)
		return;
	malloc_sprintf(&args, "%s%s%s", cmd, arg ?" ":"", arg?arg:"");
	if ((n = lookup_userlevelc("*",uhost, "*", NULL)))
		atr = n->flags;

	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",args,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_msgm,args,atr," $_n $_uh $_h $_a",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
	new_free(&args);
}

int check_on_hook(int which, char *buffer)
{
	char name[BIG_BUFFER_SIZE];

	if (!buffer || !*buffer)
		return 0;
		
	if (which > -1 && which < NUMBER_OF_LISTS)
	{
		Tcl_SetVar(tcl_interp, "_a", hook_functions[which].name, TCL_GLOBAL_ONLY);
		strcpy(name, hook_functions[which].name);
	}
	else 
	{
		strcpy(name, ltoa(which));
		Tcl_SetVar(tcl_interp, "_a", name, TCL_GLOBAL_ONLY);
	}
	strcat(name, " ");
	Tcl_SetVar(tcl_interp, "_aa", buffer, TCL_GLOBAL_ONLY);
	strlcat(name, buffer, sizeof name);
	return check_tcl_bind(&H_hook, name, -1, " $_a $_aa", MATCH_MASK | BIND_STACKABLE | BIND_WANTRET, NULL);
}

void check_tcl_join(char *nick,char *uhost, char *hand, char *channel)
{
int atr = 0; 
char args[BIG_BUFFER_SIZE+1];
UserList *n;

	
	if ((n = lookup_userlevelc("*", uhost, channel, NULL)))
		atr=n->flags;
	snprintf(args, sizeof args, "%s %s!%s", channel, nick, uhost);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",channel,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_join,args,atr," $_n $_uh $_h $_a",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

void check_tcl_part(char *nick, char *uhost, char *hand, char *chname)
{
int atr = 0; 
char args[BIG_BUFFER_SIZE+1];
UserList *n;
int x;

	snprintf(args, sizeof args, "%s %s!%s", chname, nick, uhost);
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	x = check_tcl_bind(&H_part,args,atr," $_n $_uh $_h $_a",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

void check_tcl_sign(char *nick, char *uhost,char *hand, char *chname,char *reason)
{
int atr = 0; 
char args[BIG_BUFFER_SIZE+1];
UserList *n;
	
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	snprintf(args, sizeof args, "%s %s!%s", chname, nick, uhost);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",reason,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_sign,args,atr," $_n $_uh $_h $_a $_aa",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

void check_tcl_topc(char *nick,char *uhost,char *hand,char *chname,char *topic)
{
int atr = 0;
char args[BIG_BUFFER_SIZE+1];
UserList *n;

	snprintf(args, sizeof args, "%s %s", chname, topic);
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",topic,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_topc,args,atr," $_n $_uh $_h $_a $_aa",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

void check_tcl_nick(char *nick,char *uhost,char *hand,char *chname, char *newnick)
{
int atr = 0; 
char args[BIG_BUFFER_SIZE];
UserList *n;
	
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	snprintf(args, sizeof args, "%s %s", chname, newnick);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",newnick,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_nick,args,atr," $_n $_uh $_h $_a $_aa",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

void check_tcl_kick(char *nick,char *uhost,char *hand,char *chname,char *dest,char *reason)
{
char args[BIG_BUFFER_SIZE];
int atr = 0;
UserList *n;
	
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	snprintf(args, sizeof args, "%s %s", chname, dest);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",dest,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aaa",reason,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_kick,args,atr," $_n $_uh $_h $_a $_aa $_aaa",
		MATCH_MASK|BIND_STACKABLE,NULL);
}

int check_tcl_raw(char *raw, char *comm)
{
	int ret = 0;
	if (raw && *raw)
	{
		Tcl_SetVar(tcl_interp,"_a",raw,TCL_GLOBAL_ONLY);
		ret = check_tcl_bind(&H_raw,comm, -1," $_a",MATCH_MASK|BIND_STACKABLE|BIND_WANTRET,NULL);
	}
	return (ret == BIND_EXEC_LOG)? 1 : 0;
}

char *check_tcl_alias(char *command, char *args)
{
	
	Tcl_SetVar(tcl_interp, "_a", args?args:empty_string, TCL_GLOBAL_ONLY);
	if (check_tcl_bind(&H_functions, command, -1, " $_a", MATCH_MASK|BIND_STACKABLE, NULL))
		return m_strdup(Tcl_GetStringResult(tcl_interp));
	return NULL;
}

int check_tcl_input(char *raw)
{
	int ret = 0;
	
	if (raw && *raw)
	{
		Tcl_SetVar(tcl_interp,"_a",raw,TCL_GLOBAL_ONLY);
		ret = check_tcl_bind(&H_input,raw, -1," $_a",MATCH_MASK|BIND_STACKABLE,NULL);
	}
	return (ret == BIND_EXEC_LOG)? 1 : 0;
}

int check_help_bind(char *command)
{
int x = 0;
	
	if (command && *command)
	{
		char *comm;
		comm = m_sprintf("%s_help", lower(command));
		x=check_tcl_bind(&H_help,comm,-1,NULL, MATCH_EXACT|BIND_HAS_BUILTINS,NULL);
		new_free(&comm);
	}
	return (x ? 0 : 1);
}

void check_tcl_mode(char *nick,char *uhost,char *hand,char *chname,char *mode)
{
char args[BIG_BUFFER_SIZE+1];
UserList *n;

	n = lookup_userlevelc("*",uhost, chname, NULL);
	snprintf(args, sizeof args, "%s %s", chname, mode);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",mode,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_mode,args,0," $_n $_uh $_h $_a $_aa",
		MATCH_MASK|BIND_STACKABLE,NULL);
}

int check_tcl_ctcr(char *nick,char *uhost,char *hand,char *dest,char *keyword,char *args)
{
int atr = 0;
UserList *n;
	
	if ((n = lookup_userlevelc("*",uhost, "*", NULL)))
		atr = n->flags;
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",dest,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aa",keyword,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_aaa",args,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_ctcr,keyword,atr," $_n $_uh $_h $_a $_aa $_aaa",
		MATCH_MASK|BIND_USE_ATTR,NULL);
	return 1;
}

void check_tcl_chat(char *from,int chan,char *text)
{

	
	Tcl_SetVar(tcl_interp,"_n",from,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",zero,TCL_GLOBAL_ONLY); /* chan 0 always */
	Tcl_SetVar(tcl_interp,"_aa",text,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_chat,text,0," $_n $_a $_aa",MATCH_MASK|BIND_STACKABLE, NULL);
}

#if 0
void check_tcl_link(bot,via)
char *bot,*via;
{

	
	Tcl_SetVar(tcl_interp,"_n",bot,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",via,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_link,bot,0," $_n $_a",MATCH_MASK|BIND_STACKABLE,NULL);
}

void check_tcl_disc(bot)
char *bot;
{

	
	Tcl_SetVar(tcl_interp,"_n",bot,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_disc,bot,0," $_n",MATCH_MASK|BIND_STACKABLE,NULL);
}
#endif

void check_tcl_split(char *nick,char *uhost,char *hand,char *chname)
{
int atr = 0; 
char args[BIG_BUFFER_SIZE+1];
UserList *n;

	
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	snprintf(args, sizeof args, "%s %s!%s", chname, nick, uhost);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_splt,args,atr," $_n $_uh $_h $_a",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

void check_tcl_rejoin(char *nick,char *uhost,char *hand,char *chname)
{
int atr = 0; 
char args[BIG_BUFFER_SIZE+1];
UserList *n;

	
	if ((n = lookup_userlevelc("*",uhost, chname, NULL)))
		atr = n->flags;
	snprintf(args, sizeof args, "%s %s!%s", chname, nick, uhost);
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_uh",uhost,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",n?n->nick:hand,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",chname,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_rejn,args,atr," $_n $_uh $_h $_a",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE,NULL);
}

#if 0
int check_tcl_filt(idx,text)
int idx; char *text;
{
  char s[10]; int x,atr;

	
	atr=get_attr_handle(dcc[idx].nick); snprintf(s, sizeof s, "%d", dcc[idx].sock);
	Tcl_SetVar(tcl_interp,"_n",s,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",text,TCL_GLOBAL_ONLY);
	x=check_tcl_bind(&H_filt,text,atr," $_n $_a",
		MATCH_MASK|BIND_USE_ATTR|BIND_STACKABLE|BIND_WANTRET,NULL);
	return (x==BIND_EXEC_LOG);
}
#endif

void check_tcl_tand(char *nick, char *opcode, char *param)
{
	
	Tcl_SetVar(tcl_interp,"_n",nick,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_h",opcode,TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp,"_a",param,TCL_GLOBAL_ONLY);
	check_tcl_bind(&H_bot,opcode,0," $_n $_h $_a",MATCH_EXACT,NULL);
}

Tcl_HashTable *gethashtable(typ,stk,name)
int typ,*stk; char *name;
{
char *nam=NULL; 
int st=0; 
Tcl_HashTable *ht=NULL;

	
	switch(typ) 
	{
		case CMD_MSG: ht=&H_msg; nam="msg"; break;
		case CMD_DCC: ht=&H_dcc; nam="dcc"; break;
		case CMD_FIL: ht=&H_fil; nam="fil"; break;
		case CMD_PUB: ht=&H_pub; nam="pub"; break;
		case CMD_MSGM: ht=&H_msgm; nam="msgm"; st=1; break;
		case CMD_PUBM: ht=&H_pubm; nam="pubm"; st=1; break;
		case CMD_JOIN: ht=&H_join; nam="join"; st=1; break;
		case CMD_PART: ht=&H_part; nam="part"; st=1; break;
		case CMD_SIGN: ht=&H_sign; nam="sign"; st=1; break;
		case CMD_KICK: ht=&H_kick; nam="kick"; st=1; break;
		case CMD_TOPC: ht=&H_topc; nam="topc"; st=1; break;
		case CMD_MODE: ht=&H_mode; nam="mode"; st=1; break;
		case CMD_CTCP: ht=&H_ctcp; nam="ctcp"; break;
		case CMD_CTCR: ht=&H_ctcr; nam="ctcr"; break;
		case CMD_NICK: ht=&H_nick; nam="nick"; st=1; break;
		case CMD_RAW: ht=&H_raw; nam="raw"; st=1; break;
		case CMD_BOT: ht=&H_bot; nam="bot"; break;
		case CMD_CHON: ht=&H_chon; nam="chon"; st=1; break;
		case CMD_CHOF: ht=&H_chof; nam="chof"; st=1; break;
		case CMD_SENT: ht=&H_sent; nam="sent"; st=1; break;
		case CMD_RCVD: ht=&H_rcvd; nam="rcvd"; st=1; break;
		case CMD_CHAT: ht=&H_chat; nam="chat"; st=1; break;
		case CMD_LINK: ht=&H_link; nam="link"; st=1; break;
		case CMD_DISC: ht=&H_disc; nam="disc"; st=1; break;
		case CMD_SPLT: ht=&H_splt; nam="splt"; st=1; break;
		case CMD_REJN: ht=&H_rejn; nam="rejn"; st=1; break;
		case CMD_FILT: ht=&H_filt; nam="filt"; st=1; break;
		case CMD_CMD: ht = &H_cmd; nam = "cmd"; st = 1; break;
		case CMD_INPUT: ht=&H_input; nam = "input"; st = 1; break;
		case CMD_ALIAS: ht=&H_alias; nam = "alias"; st = 1; break;
		case CMD_HELP: ht=&H_help; nam = "help"; st = 1; break;
		case CMD_FUNCTION: ht=&H_functions; nam = "func"; st = 1; break;
		case CMD_HOOK: ht=&H_hook; nam = "hook"; st = 1; break;
	}
	if (name!=NULL) 
		strcpy(name,nam);
	if (stk!=NULL) 
		*stk=st;
	return ht;
}

int get_bind_type(char *name)
{
int tp = -1;

	
	if (!my_stricmp(name,"dcc")) tp=CMD_DCC;
	else if (!my_stricmp(name,"msg"))  tp=CMD_MSG;
	else if (!my_stricmp(name,"fil"))  tp=CMD_FIL;
	else if (!my_stricmp(name,"pub"))  tp=CMD_PUB;
	else if (!my_stricmp(name,"msgm")) tp=CMD_MSGM;
	else if (!my_stricmp(name,"pubm")) tp=CMD_PUBM;
	else if (!my_stricmp(name,"join")) tp=CMD_JOIN;
	else if (!my_stricmp(name,"part")) tp=CMD_PART;
	else if (!my_stricmp(name,"sign")) tp=CMD_SIGN;
	else if (!my_stricmp(name,"kick")) tp=CMD_KICK;
	else if (!my_stricmp(name,"topc")) tp=CMD_TOPC;
	else if (!my_stricmp(name,"mode")) tp=CMD_MODE;
	else if (!my_stricmp(name,"ctcp")) tp=CMD_CTCP;
	else if (!my_stricmp(name,"ctcr")) tp=CMD_CTCR;
	else if (!my_stricmp(name,"nick")) tp=CMD_NICK;
	else if (!my_stricmp(name,"bot"))  tp=CMD_BOT;
	else if (!my_stricmp(name,"chon")) tp=CMD_CHON;
	else if (!my_stricmp(name,"chof")) tp=CMD_CHOF;
	else if (!my_stricmp(name,"sent")) tp=CMD_SENT;
	else if (!my_stricmp(name,"rcvd")) tp=CMD_RCVD;
	else if (!my_stricmp(name,"chat")) tp=CMD_CHAT;
	else if (!my_stricmp(name,"link")) tp=CMD_LINK;
	else if (!my_stricmp(name,"disc")) tp=CMD_DISC;
	else if (!my_stricmp(name,"rejn")) tp=CMD_REJN;
	else if (!my_stricmp(name,"splt")) tp=CMD_SPLT;
	else if (!my_stricmp(name,"filt")) tp=CMD_FILT;
	else if (!my_stricmp(name, "raw")) tp =CMD_RAW;
	else if (!my_stricmp(name,"cmd"))  tp = CMD_CMD;
	else if (!my_stricmp(name,"input")) tp = CMD_INPUT;
	else if (!my_stricmp(name,"alias")) tp = CMD_ALIAS;
	else if (!my_stricmp(name,"help")) tp = CMD_HELP;
	else if (!my_stricmp(name,"func")) tp = CMD_FUNCTION;
	else if (!my_stricmp(name, "hook")) tp = CMD_HOOK;
	return tp;
}

int tcl_bind STDVAR
{
	int fl = 0,tp;

	
	BADARGS(5,5," type flags cmdName procName");
	fl=convert_str_to_flags(argv[2]);
	tp=get_bind_type(argv[1]);
	if (tp<0) {
		Tcl_AppendResult(irp,"bad type, should be one of: dcc, msg, fil, pub, ",
					"msgm, pubm, join, part, sign, kick, topc, mode, ctcp, ",
					"nick, bot, chon, chof, sent, rcvd, chat, link, disc, ",
					"splt, rejn, filt, cmd, raw, input, alias, help, func, hook",NULL);
		return TCL_ERROR;
	}
	if ((long int)cd == 1) {
		if (!cmd_unbind(tp,fl,argv[3],argv[4])) 
		{
			/* don't error if trying to re-unbind a builtin */
			if (strcmp(argv[3],&argv[4][1])!=0) 
			{
				Tcl_AppendResult(irp,"no such binding",NULL);
				return TCL_ERROR;
			}
		}
	}
	else 
	{
		if (tp != CMD_ALIAS)
				cmd_bind(tp,fl,argv[3],argv[4], NULL);
		else
		{
			BuiltInFunctions *tmp;
			if ((tmp = find_func_alias(argv[4])))
				cmd_bind(tp, fl, argv[3], argv[4], tmp->func);
		}
	}
	Tcl_AppendResult(irp,argv[3],NULL);
	return TCL_OK;
}

int tcl_tellbinds STDVAR
{
Tcl_HashEntry *he;
Tcl_HashSearch srch;
Tcl_HashTable *ht;
int i,fnd=0;
tcl_cmd_t *tt;
char typ[5],*s,*proc;
int kind = -1;

	
	if (argc > 1)
		kind = get_bind_type(argv[1]);
	for (i=0; i<BINDS; i++) 
	{
		if ((kind==(-1)) || (kind==i)) 
		{
			ht=gethashtable(i,NULL,typ);
			for (he=Tcl_FirstHashEntry(ht,&srch); (he!=NULL); he=Tcl_NextHashEntry(&srch)) 
			{
				if (!fnd) 
				{
					bitchsay("TCL Command bindings:"); 
					fnd=1;
					put_it("  TYPE FLGS                 COMMAND              BINDING (TCL)");
				}
				tt=(tcl_cmd_t *)Tcl_GetHashValue(he);
				s=Tcl_GetHashKey(ht,he);
				while (tt!=NULL) {
					proc=tt->func_name; 
					if ((proc[0]!='*') || (strcmp(s,proc+1)!=0))
						put_it("  %-4s %-16s %-20s %s",typ,convert_flags_to_str(tt->flags_needed),s,tt->func_name);
					tt=tt->next;
				}	
			}
			if (!fnd) 
			{
				if (kind==(-1)) 
					put_it("No TCL command bindings.");
				else 
					put_it("No bindings for %s.",argv[1]);
			}
		}	
	}
	return 0;
}


void tcl_init(void)
{
static int done = 0;
	
	if (tcl_interp != NULL && !done)
	{
		done++;
#ifdef TCL_PLUS
		Tcl_InitStandAlone(tcl_interp);
#else
		Tcl_Init(tcl_interp);
#endif
		init_hash();
		init_builtins();

		init_public_tcl(tcl_interp);
		add_tcl_fset(tcl_interp);
	        Tcl_SetVar(tcl_interp,"botnick",nickname,TCL_GLOBAL_ONLY);
               	Tcl_SetVar(tcl_interp,"nick",nickname,TCL_GLOBAL_ONLY);
	} 
	if (tcl_interp)
		init_public_var(tcl_interp);
}

static int tcl_echo = 1;


BUILT_IN_COMMAND(tcl_version)
{
	put_it("%s", convert_output_format("BitchX Tcl Interpreter %GVersion $1%n (c)1997 Colten Edwards", "%s", tcl_versionstr));
}

BUILT_IN_COMMAND(tcl_command)
{
	int result = 0;
	const char *tcl_result;
	
	tcl_init();
	if (args && *args)
	{
		if (*args == '-' && *(args + 1))
		{
			char *bla;
			if (!my_strnicmp(args+1, "file", 4))
			{
				bla = next_arg(args, &args);
				if (get_string_var(LOAD_PATH_VAR))
					bla = path_search(args, get_string_var(LOAD_PATH_VAR));

				result = Tcl_EvalFile(tcl_interp, bla ? bla : args);
				tcl_result = Tcl_GetStringResult(tcl_interp);

				if ((result != TCL_OK) || (tcl_echo && *tcl_result))
					put_it("Tcl: [%s]", tcl_result);
			} 
			else if (!my_strnicmp(args+1, "xecho", 4)) 
			{
				tcl_echo = (tcl_echo ? 0:1);
				put_it("Tcl:  xecho %s", tcl_echo?"on": "off");
			}	 
			else if (!my_strnicmp(args+1, "version", 4)) 
				tcl_version(NULL, NULL, NULL, NULL);
			else
				put_it("Tcl: unknown cmd [%s]", args);
			return;					
		}

		result = Tcl_Eval(tcl_interp, args);
		tcl_result = Tcl_GetStringResult(tcl_interp);

		if ((result != TCL_OK) || (tcl_echo && *tcl_result))
			put_it("Tcl: [%s] %s", args, tcl_result);
	} 
	else
	{
		userage("tcl", "<-version | -file filename> <command>");
		tcl_version(NULL, NULL, NULL, NULL);
	}
}

int Tcl_Invoke(Tcl_Interp *irp, char *cmdname, char *rest)
{
int result = TCL_ERROR, argc = 0;
char **ArgList;
char *TrueArgs[41] = {NULL};
Tcl_CmdInfo info = {0};
char *p;
char *copy;
char *com;
	copy = LOCAL_COPY(rest);
	com = LOCAL_COPY(cmdname);
	
	ArgList = TrueArgs;
	ArgList[argc] = com;
	for (argc = 1; argc < 39; argc++) 
	{
		p = new_next_arg(copy, &copy);
		if (!p || !*p)
			break;
		ArgList[argc] = p;
	}
	if (copy && *copy)
		ArgList[argc] = copy;
	Tcl_ResetResult(irp);
	lower(com);
	if (internal_debug & DEBUG_TCL)
		debugyell("Invoking Tcl [%s] with [%s]", com, rest);
	if (Tcl_GetCommandInfo(irp, com, &info) && info.proc)
	{
		result = (*info.proc)(info.clientData, irp, argc, ArgList);
		if (internal_debug & DEBUG_TCL)
			debugyell("Tcl returning with [%d]", result);
	}
	else
	{
		if (internal_debug & DEBUG_TCL)
			debugyell("Tcl could not find command [%s]", com);
	}

	return result;
}

int tcl_cset STDVAR
{
extern char *set_cset(char *, ChannelList *, char *);
ChannelList *chan = NULL;
char *s = NULL;
	BADARGS(2, 4, " channel variable [value]");
	if (!(chan = lookup_channel(argv[1], from_server, 0)))
	{
		Tcl_AppendResult(irp, "No such channel \"", argv[1], "\"", NULL);
		return TCL_ERROR;
	}
	if (argv[2]) 
	{
		if (!(s = get_cset(argv[2], chan, NULL)))
		{
			Tcl_AppendResult(irp, "No such channel variable \"", argv[2], "\"", NULL);
			return TCL_ERROR;
		}
	}
	switch(argc)
	{
		case 2: /* no variable, do em all*/
			break;
		case 3: /* variable name only */
			break;
		case 4: /* set variable for channel */
			if (set_cset(argv[2], chan, argv[3]))
				malloc_strcpy(&s, argv[3]);
			else
			{
				Tcl_AppendResult(irp, "Error setting variable \"", argv[2], "\"", NULL);
				new_free(&s);
				return TCL_ERROR;
			}
			break;
	}
	if (s)
		Tcl_AppendResult(irp, s, NULL);
	new_free(&s);
	return TCL_OK;
}


#endif

#ifdef WANT_TCLMODULE
int Tcl_Init()
{

}
#endif
