/* ARISA - IRC Network Functions
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#define WANT_POLL

#include "arisa.h"

#include "line-drive.h"

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <arpa/inet.h>
#include <netdb.h>

#include <math.h>

#include <ctype.h>

/* structures/enums */

#define MODEBUFSIZE 32 

/* Message types - only those we are interested in */
enum mst_t {
	UNKNOWN			= 0,
	PING			= 901, 	// 900 commands, internal numerics
	ERROR			= 902,
	PRIVMSG			= 903,
	NOTICE			= 904,
	JOIN			= 905,
	PART			= 906,
	KICK			= 907,
	NICK			= 908,
	QUIT			= 909,
//	KILL			= 910,
	MODE			= 911,
	TOPIC			= 912,
	RPL_WELCOME		= 001,  // RFC commands we are interested in
	RPL_NETINFO		= 005,  // this isn't the RFC definition
	RPL_USERHOST		= 302,
//	RPL_WHOISUSER		= 311,
	RPL_CHANNELMODEIS	= 324,
	RPL_TOPIC		= 332,
	RPL_NAMREPLY		= 353,
	RPL_BANLIST		= 367,
//	RPL_WHOIS_CUSTOM	= 378,
	ERR_NOSUCHNICK		= 401,
	ERR_TOOMANYCHANNELS	= 405,
	ERR_ERRONEUSNICKNAME	= 432,
	ERR_NICKNAMEINUSE	= 433,
	ERR_NICKCOLLISION	= 436,
	ERR_NOTONCHANNEL	= 442,
	ERR_YOUREBANNEDCREEP	= 465,
	ERR_CHANNELISFULL	= 471,
	ERR_INVITEONLYCHAN	= 473,
	ERR_BANNEDFROMCHAN	= 474,
	ERR_BADCHANNELKEY	= 475
};

typedef struct netlocal_t {
	int 		socket;
	int		signal_fd;
	int		server_no;
	netinfo_t	*info;
	network_t	*network;
	time_t		last_connect;
	time_t		last_message;
	time_t		last_join;
	time_t		last_send;
	pqueue_t	rqueue;
	int     	*window;
	int     	window_size;
	
	int		on_failback_ip;
	
	/* send and recv line driving buffers */
	char		*snd_msg;
	linebuf_t	*snd_buf;
	linebuf_t	*rcv_buf;
	
	/* these buffers are used to store character modes of the network */
	char		chantype[MODEBUFSIZE];
	char		acmode[MODEBUFSIZE]; // argument carrying modes
	char		pcmode[MODEBUFSIZE]; // partial argument carrying modes
	char		ncmode[MODEBUFSIZE]; // non-argument carrying modes
	char		mprefix[MODEBUFSIZE]; // user modes
	char		nprefix[MODEBUFSIZE]; // names prefixes, 1-1 mprefix
} netlocal_t;

/* INTERNAL Functions */

static void set_default_modes(netlocal_t *l) {
	xstrncpy(l->chantype,	"#&",	MODEBUFSIZE);
	xstrncpy(l->acmode,	"bk",	MODEBUFSIZE);
	xstrncpy(l->pcmode,	"l",	MODEBUFSIZE);
	xstrncpy(l->ncmode,	"inpst",MODEBUFSIZE);
	xstrncpy(l->mprefix,	"ov",	MODEBUFSIZE);
	xstrncpy(l->nprefix,	"@+",	MODEBUFSIZE);
}

static inline int is_channel(netlocal_t *l, const char *str) {
	return (strchr(l->chantype,str[0]) != NULL);
}

static inline int is_chan_nick_mode(netlocal_t *l, char m) {
	return (strchr(l->mprefix,m) != NULL);
}

static inline int is_chan_nick_prefix(netlocal_t *l, char p) {
	return (strchr(l->nprefix,p) != NULL);
}

static inline int is_chan_arg_mode(netlocal_t *l, char m) {
	return (strchr(l->acmode,m) != NULL);
}

static inline int is_chan_parg_mode(netlocal_t *l, char m) {
	return (strchr(l->pcmode,m) != NULL);
}

static inline int is_chan_noarg_mode(netlocal_t *l, char m) {
	return (strchr(l->ncmode,m) != NULL);
}

static char nprefix_from_mode(netlocal_t *l, char m) {
	int i;
	for(i = 0; l->mprefix[i] != '\0'; ++i) {
		if(l->mprefix[i] == m)
			return l->nprefix[i];
	}
	return ' ';
}

#define EM_LOCK		0x1
#define EM_DUP		0x2
#define EM_PRIORITY	0x4
static int is_dcc_ctcp(const char *msg); // pre-declaration
static int enqueue_msg(netinfo_t *ni, const char *msg, int flags) {
	pqueue_t *pq = NULL;
	if(ni == NULL || msg == NULL)
		return -1;
	
	if(flags & EM_LOCK)
		LOCK(ni);
	
	if(strncasecmp("PING",msg,4) == 0 
			|| strncasecmp("PONG",msg,4) == 0
			|| strncasecmp("NICK",msg,4) == 0)
		pq = &(ni->p_squeue);
	else if(strncasecmp("JOIN",msg,4) == 0)
		pq = &(ni->j_squeue);
	else if(is_dcc_ctcp(msg))
		pq = &(ni->d_squeue);
	else
		pq = &(ni->r_squeue);
	
	//D("pq: %p, msg: \"%s\"",pq,msg);
	
	if(flags & EM_PRIORITY)
		pqueue_push_front(pq,
				flags & EM_DUP ? xstrdup(msg) : (void *)msg);
	else
		pqueue_push_back(pq,
				flags & EM_DUP ? xstrdup(msg) : (void *)msg);

	if(flags & EM_LOCK)
		UNLOCK(ni);

	return 0;
}

static void irc_init(netlocal_t *l, network_t *n) {
	int fds[2] = {-1,-1};
	if(pipe(fds) != 0) {
		LOGTP(L_ERR,"Failed to allocate signaling pipe");
	}
	/* signal pipe should be nonblocking */
	fcntl(fds[0],F_SETFL,O_NONBLOCK);
	fcntl(fds[1],F_SETFL,O_NONBLOCK);
	
	l->socket 	= -1;
	l->signal_fd 	= fds[0]; // reading side
	l->server_no	= -1;
	l->window	= NULL;
	l->window_size	= 0;
	l->info		= alloc_netinfo();
	l->network	= n;
	l->snd_msg	= NULL;
	l->snd_buf	= alloc_linebuf(512);
	l->rcv_buf	= alloc_linebuf(512);
	set_default_modes(l);
	pqueue_init(&l->rqueue,0);
	
	l->on_failback_ip = 0;
	
	pqueue_set_notification_fd(&l->info->p_squeue,fds[1]);
	pqueue_set_notification_fd(&l->info->j_squeue,fds[1]);
	pqueue_set_notification_fd(&l->info->d_squeue,fds[1]);
	pqueue_set_notification_fd(&l->info->r_squeue,fds[1]);
	
	LOCK(n);
	n->info 	= l->info;
	n->state 	= NETWORK_RUNNING;
	UNLOCK(n);
}

static void irc_free(netlocal_t *l) {
	LOCK(l->network);
	LOCK(l->info);
	l->network->state = NETWORK_OFFLINE;
	
	//D("close: %d",l->signal_fd);
	close(l->signal_fd);

	if(l->snd_msg != NULL) {
		xfree(l->snd_msg);
		l->snd_msg = NULL;
	}
	free_linebuf(l->snd_buf);
	free_linebuf(l->rcv_buf);
	
	pqueue_clear(&l->rqueue,xfree,1);

	// do 1 close, since they share an fd
	pqueue_clear(&l->info->p_squeue,xfree,1);
	pqueue_set_notification_fd(&l->info->p_squeue,-1);
	pqueue_set_notification_fd(&l->info->j_squeue,-1);
	pqueue_set_notification_fd(&l->info->d_squeue,-1);
	pqueue_set_notification_fd(&l->info->r_squeue,-1);
		
	if(l->window != NULL)
		xfree(l->window);
	
	l->network->info = NULL;
	UNLOCK(l->network);
	UNLOCK(l->info);
	free_netinfo(l->info);
}

static int pick_server(netlocal_t *l) {
	char *p1,*p2,*p3;
	int serv;
	
	LOCK(l->network);
	LOCK(l->info);
	
	/* free existing server */
	if(l->info->server != NULL) {
		xfree(l->info->server);
		l->info->server = NULL;
	}
	
	/* free existing password */
	if(l->info->password != NULL) {
		xfree(l->info->password);
		l->info->password = NULL;
	}
	
	/* check there are servers to pick from */
	if(l->network->servers == NULL) {
		LOGTP(L_ERR,"Failed to pick a server, due to there being none specified",l->network->name);
		UNLOCK(l->info);
		UNLOCK(l->network);
		return -1;
	}
	
	/* pick a "random" server, preferably not the last one */
	if(l->network->no_servers > 2) {
		do {
			serv = lrand(l->network->no_servers);
		} while(serv == l->server_no);
	} else if(l->network->no_servers == 2) {
		if(l->server_no == 0) {
			serv = 1;
		} else {
			serv = 0;
		}
	} else {
		serv = 0;
	}
	l->server_no    = serv;
	
	/* split the server down to hostname/ip and port */
	p1 = xstrdup(l->network->servers[serv]);
	p2 = strchr(p1,':');
	if(p2 != NULL) {
		*p2 = '\0';
		p2++;
		p3 = strchr(p2,'/');
		if(p3 != NULL) {
			*p3 = '\0';
			p3++;
			l->info->password = xstrdup(p3);
		}
		l->info->port = (unsigned short) atoi(p2);
	} else {
		p3 = strchr(p1,'/');
		if(p3 != NULL) {
			*p3 = '\0';
			p3++;
			l->info->password = xstrdup(p3);
		}
		l->info->port = (unsigned short) 6667;
	}
	l->info->server = xstrdup(p1);
	xfree(p1);
	
	UNLOCK(l->info);
	UNLOCK(l->network);
	
	return 0;
}

static int connect_server(netlocal_t *l) {
	char 			buffer[32],*tmp;
	struct sockaddr_in 	addr,baddr;
	unsigned short		server_port;
	int			ret,val;
	time_t			now;
	
	LOCK(l->info);
	if(l->info->server == NULL) {
		UNLOCK(l->info);
		LOGTP(L_ERR,"Attempt to connect with no server picked",l->network->name);
		return -1;
	}
	tmp = xstrdup(l->info->server);
	server_port = l->info->port;
	UNLOCK(l->info);

	addr.sin_family = AF_INET;
	addr.sin_port   = htons(server_port);
	ret = aresolv_nametoaddr(global->aresolver,AF_INET,tmp,&(addr.sin_addr));
	
	if(ret != 0) {
		LOGTP(L_ERR,"Failed to resolve server %s",tmp);
		xfree(tmp);
		return -1;
	} else
		xfree(tmp);
	

	baddr.sin_family= AF_INET;
	baddr.sin_port	= 0;
	LOCK(l->network);
	if(l->network->settings->bind_ip != NULL) {
		tmp = xstrdup(l->network->settings->bind_ip);
		UNLOCK(l->network);
		ret = aresolv_nametoaddr(global->aresolver,AF_INET,
				tmp,&(baddr.sin_addr));
		if(ret != 0) {
			LOGTP(L_ERR,
				"Failed to resolve bind-ip %s, not using it",
				tmp);
			baddr.sin_addr.s_addr = INADDR_ANY;
		}
		xfree(tmp);
	} else {
		UNLOCK(l->network);
		baddr.sin_addr.s_addr = INADDR_ANY;
	}
	
	/* hostptr is now void, since we reuse the buffer */
	inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
	
	LOCK(l->network);
	LOGTP(L_IRC,"Connecting to %s:%d",buffer,server_port);
	l->network->state = NETWORK_CONNECTING;
	UNLOCK(l->network);

	if(l->socket != -1) {
		//D("close: %d",l->socket);
		close(l->socket);
	}
	
	l->socket = socket(PF_INET, SOCK_STREAM, 0);
	//D("socket: %d",l->socket);
	if(l->socket == -1) {
		char errbuf[96];
		LOGTP(L_ERR,"Failed to get socket for connection to %s:%d, Error: %s",
				buffer,server_port,
				lstrerror_r(errno,errbuf,sizeof(errbuf)));
		return -1;
	}
	if(bind(l->socket,(struct sockaddr *)&baddr,sizeof(baddr)) == -1) {
		char errbuf[96];
		val = errno;
		inet_ntop(AF_INET, &baddr.sin_addr, buffer, sizeof(buffer));
		LOGTP(L_ERR,"Failed to bind to %s, Error: %s",
			buffer,lstrerror_r(val,errbuf,sizeof(errbuf)));
		//D("close: %d",l->socket);
		close(l->socket);
		l->socket = -1;
		return -1;
	}
	if(interruptable_connect(l->socket,(struct sockaddr *)&addr, 
				sizeof(addr)) == -1) {
		char errbuf[96];
		LOGTP(L_ERR,"Failed to connect to %s:%d, Error: %s",
				buffer,server_port,
				lstrerror_r(errno,errbuf,sizeof(errbuf)));
		//D("close: %d",l->socket);
		close(l->socket);
		l->socket = -1;
		return -1;
	}
	LOCK(l->network);
	LOGTP(L_IRC,"Connected to %s:%d",buffer,server_port);
	l->network->state = NETWORK_CONNECTED;
	UNLOCK(l->network);

	val = 1;
	if(setsockopt(l->socket,SOL_SOCKET,SO_KEEPALIVE,&val,sizeof(val)) == -1) {
		char errbuf[96];
		LOGTP(L_ERR,"Failed to set SO_KEEPALIVE for connection to %s:%d, Error: %s",
				buffer,server_port,
				lstrerror_r(errno,errbuf,sizeof(errbuf)));
		// Not Fatal?
	}
	if(fcntl(l->socket,F_SETFL,O_NONBLOCK) == -1) {
		char errbuf[96];
		LOGTP(L_ERR,"Failed to set O_NONBLOCK for connection to %s:%d, Error :%s",
				buffer,server_port,
				lstrerror_r(errno,errbuf,sizeof(errbuf)));
		//D("close: %d",l->socket);
		close(l->socket);
		l->socket = -1;
		return -1; // Fatal
	}
	/* Set all timestamps to now */
	now = xtime();
	l->last_connect = now;
	l->last_message = now;
	l->last_join	= now;
	l->last_send	= now;
	if(l->snd_msg != NULL) {
		enqueue_msg(l->info,l->snd_msg,EM_LOCK);
		l->snd_msg = NULL;
	}
	clear_linebuf(l->snd_buf);
	clear_linebuf(l->rcv_buf);
	LOCK(l->info);
	l->info->connected = now;
	pqueue_clear(&l->info->p_squeue,xfree,0);
	pqueue_clear(&l->info->j_squeue,xfree,0);
	UNLOCK(l->info);
	
	return 0;
}

/* a NULL disconnect message (*msg) means error state disconnect */
static void disconnect_server(netlocal_t *l, char *msg) {
	char buffer[512];
	int i;

	if(l->socket != -1) {
		if(msg != NULL) {
			snprintf(buffer,sizeof(buffer)-1,"QUIT :%s\r\n",msg);
			send(l->socket,buffer,strlen(buffer),0);
		}
		//D("shutdown: %d", l->socket);
		shutdown(l->socket,SHUT_RDWR);
		//D("close: %d",l->socket);
		close(l->socket);
		l->socket = -1;
	}
	
	LOCK(l->network);
	LOCK(l->info);
	
	LOGTP(L_INF,"Disconnected %s:%d, %s%s",
			l->info->server,l->info->port,
			msg != NULL ? "Reason: " : "",
			msg != NULL ? msg : "");
	
	if(l->info->nick != NULL) {
		xfree(l->info->nick);
		l->info->nick = NULL;
	}
	if(l->info->server != NULL) {
		xfree(l->info->server);
		l->info->server = NULL;
	}
	
	pqueue_clear(&l->info->p_squeue,xfree,0);
	pqueue_clear(&l->info->j_squeue,xfree,0);
	
	nickhash_flush(l->info->nick_tracker); /* clear channels */
	UNLOCK(l->info);
	
	l->network->state = NETWORK_RUNNING;
	if(l->window != NULL) {
		xfree(l->window);
		l->window = NULL;
		l->window_size = 0;
	}
	if(l->network->channels != NULL) {
		for(i = 0; i < l->network->no_channels; ++i) {
			LOCK(l->network->channels[i]);
			l->network->channels[i]->joined = 0;
			UNLOCK(l->network->channels[i]);
		}
	}
	UNLOCK(l->network);
	
	pqueue_clear(&l->rqueue,xfree,0);
}

static int recv_msgs(netlocal_t *l, int limit) {
	int count = 0;
	int ret;
	
	do {
		ret = recv_line(l->socket, l->rcv_buf);
		if(ret > 0) {
			pqueue_push_back(&l->rqueue,dup_linebuf(l->rcv_buf));
			l->last_message = xtime();
			count++;
		} else if(errno != 0 && errno != EAGAIN && errno != EINTR) {
			char errbuf[96];
			LOGTP(L_ERR,"Got Error: %s",
				lstrerror_r(errno,errbuf,sizeof(errbuf)));
			return -1;
		}
	} while(ret > 0 && count < limit);
	
	return count;
}

static void tokenise_msg(char *msg, char ***tokens, int *no_tokens) {
	char *tail = NULL;
	int pos;
	
	*tokens 	= NULL;
	*no_tokens 	= 0;
	
	if(msg[0] == '\0')
		return;
	
	for(pos = 0; msg[pos] != '\0'; ++pos) {
		if(msg[pos] == ' ' && msg[pos+1] == ':') {
			msg[pos] = '\0';
			tail = &(msg[pos+2]);
			break;
		}
	}

	*tokens = split_opt(msg,msg,no_tokens);
	if(tail != NULL) {
		(*no_tokens)++;
		*tokens = xreloc(*tokens,sizeof(char *)*(*no_tokens));
		(*tokens)[(*no_tokens)-1] = tail;
	}
}

// FIXME: move this to the top of the file with the mst_t enum definition
static struct {
	const char	*name;
	enum mst_t	type;
	int 		len;
} message_types[] = { // optimise order to speed look up
	{"PRIVMSG",		PRIVMSG,		7},
	{"PING",		PING,			4},
	{"NOTICE",		NOTICE,			6},
	{"JOIN",		JOIN,			4},
	{"PART",		PART,			4},
	{"MODE",		MODE,			4},
	{"NICK",		NICK,			4},
	{"QUIT",		QUIT,			4},
	{"KICK",		KICK,			4},
	{"ERROR",		ERROR,			5},
	{"TOPIC",		TOPIC,			5},
	{"RPL_WELCOME",		RPL_WELCOME,		0},
	{"RPL_NETINFO",		RPL_NETINFO,		0},
	{"RPL_USERHOST",	RPL_USERHOST,		0},
//	{"RPL_WHOISUSER",	RPL_WHOISUSER,		0},
//	{"RPL_WHOIS_CUSTOM",	RPL_WHOIS_CUSTOM,	0},
	{"RPL_CHANNELMODEIS",	RPL_CHANNELMODEIS,	0},
	{"RPL_TOPIC",		RPL_TOPIC,		0},
	{"RPL_NAMREPLY",	RPL_NAMREPLY,		0},
	{"RPL_BANLIST",		RPL_BANLIST,		0},
	{"ERR_TOOMANYCHANNELS",	ERR_TOOMANYCHANNELS,	0},
	{"ERR_ERRONEUSNICKNAME",ERR_ERRONEUSNICKNAME,	0},
	{"ERR_NICKNAMEINUSE",	ERR_NICKNAMEINUSE,	0},
	{"ERR_NICKCOLLISION",	ERR_NICKCOLLISION,	0},
	{"ERR_NOTONCHANNEL",	ERR_NOTONCHANNEL,	0},
	{"ERR_YOUREBANNEDCREEP",ERR_YOUREBANNEDCREEP,	0},
	{"ERR_CHANNELISFULL",	ERR_CHANNELISFULL,	0},
	{"ERR_INVITEONLYCHAN",	ERR_INVITEONLYCHAN,	0},
	{"ERR_BANNEDFROMCHAN",	ERR_BANNEDFROMCHAN,	0},
	{"ERR_BADCHANNELKEY",	ERR_BADCHANNELKEY,	0},
	{NULL}
};

static enum mst_t str_to_mst(const char *type) {
	int i,tmp;
	
	if(type == NULL)
		return UNKNOWN;
	
	if(!isdigit(type[0])) {
		tmp = strlen(type);
		for(i = 0; message_types[i].name != NULL; ++i) {
			if(message_types[i].len == tmp) {
				if(strcasecmp(message_types[i].name,type) == 0)
					return message_types[i].type;
			}
		}
	} else if(strlen(type) == 3) {
		tmp = atoi(type);
		/* This means only known numeric types go through,
		 * other types will be mapped to unknown.
		 */
		for(i = 0; message_types[i].name != NULL; ++i) {
			if(message_types[i].type == tmp)
				return message_types[i].type;
		}
	}

	return UNKNOWN;
}

/* p2n - prefix to nickname */
static char *p2n(const char *prefix) {
	char *nick;
	int i;
	if(prefix == NULL)
		return strdup("");
	
	for(i = 0; prefix[i] != '!' && prefix[i] != '\0'; ++i) ;
	if(prefix[i] == '!') {
		nick = xalloc(i+1);
		memcpy(nick,prefix,i);
		nick[i] = '\0';
	} else {
		nick = strdup(prefix);
	}
	return nick;
}

/* p2h - prefix to hostname */
static char *p2h(const char *prefix) {
	char *host;
	int i,len;
	if(prefix == NULL)
		return strdup("");

	len = strlen(prefix);
	for(i = 0; prefix[i] != '@' && prefix[i] != '\0'; ++i) ;
	if(prefix[i] == '@') {
		host = xalloc((len - i) + 1);
		memcpy(host,prefix+i+1,(len - i));
		host[len - i] = '\0';
	} else {
		host = strdup("");
	}
	return host;
}

// lock assumed
static void channel_reset_state(channel_t *chan) {
	strarr_free(&(chan->bans),&(chan->no_bans));
	if(chan->mode != NULL) {
		xfree(chan->mode);
		chan->mode = NULL;
	}
	if(chan->topic != NULL) {
		xfree(chan->topic);
		chan->topic = NULL;
	}
	chan->limit = 0;
}	

static void channel_change_mode(channel_t *chan, int op, 
				char mode, const char *arg) {
	char buffer[64];
	int i;
	
	LOCK(chan);
	if(mode == 'b') {
		for(i = 0; i < chan->no_bans; ++i) {
			if(strcasecmp(chan->bans[i],arg) == 0)
				break;
		}
		if(i >= chan->no_bans && op == MODE_ADD)
			PTRARR_ADD(&(chan->bans),&(chan->no_bans),xstrdup(arg));
		else if(i < chan->no_bans && op == MODE_DEL)
			PTRARR_DEL(&(chan->bans),&(chan->no_bans),chan->bans[i]);
	} else if(mode == 'k' && chan->track_key) {
		if(op == MODE_ADD) {
			if(chan->key != NULL)
				xfree(chan->key);
			chan->key = xstrdup(arg);
		} else if(op == MODE_DEL && chan->key != NULL) {
			xfree(chan->key);
			chan->key = NULL;
		}
	} else if(mode == 'e') {
		// ignore
	} else if(mode == 'l') {
		if(op == MODE_ADD)
			chan->limit = atoi(arg);
		else if(op == MODE_DEL)
			chan->limit = 0;
	} else {
		if(chan->mode != NULL)
			xstrncpy(buffer,chan->mode,sizeof(buffer));
		else
			buffer[0] = '\0';
		
		for(i = 0; buffer[i] != '\0'; ++i) {
			if(buffer[i] == mode)
				break;
		}
		
		if(op == MODE_ADD && buffer[i] == '\0') {
			if(i+2 < sizeof(buffer)) {
				buffer[i] = mode;
				buffer[i+1] = '\0';
			}
			if(chan->mode != NULL)
				xfree(chan->mode);
			chan->mode = xstrdup(buffer);
		} else if(op == MODE_DEL && buffer[i] == mode) {
			for(; buffer[i] != '\0'; ++i)
				buffer[i] = buffer[i+1];
			if(chan->mode != NULL)
				xfree(chan->mode);
			chan->mode = xstrdup(buffer);
		}
	}
	UNLOCK(chan);
}

static int is_dcc_ctcp(const char *msg) {
	char *p;
	if(strncasecmp(msg,"PRIVMSG",7) == 0 ||
			strncasecmp(msg,"NOTICE",6) == 0) {
		if((p = strchr(msg,' ')) != NULL) {
			if((p = strchr(p+1,' ')) != NULL) {
				if((p = strchr(p,':')) != NULL) {
					if(strncasecmp(p,":\001DCC",5) == 0)
						return 1;
				}
			}
		}
	}
	return 0;
}

static void handle_ctcp(network_t *n, channel_t *chan, 
		const char *host, const char *hostmask,
		const char *nick, const char *msg) {
	char buffer[512], temp[512];
	int i,j;
	
	/* Copy the CTCP quoted part of the message into our buffer. */
	for(i = 0; msg[i] != '\001' && msg[i] != '\0'; ++i) ;
	if(msg[i] != '\001')
		return;
	for(i++, j = 0; msg[i] != '\001' && msg[i] != '\0'; ++i, ++j)
		buffer[j] = msg[i];
	buffer[j] = '\0';

	if(strncasecmp(buffer,"ACTION",6) == 0) {
		return; // trap this here to save processing
	} else if(strcasecmp(buffer,"VERSION") == 0) {
		snprintf(buffer,sizeof(buffer),
				"VERSION %s",ARISA_VERSION);
		irc_send_ctcp_notice(n,nick,buffer);
		LOGTP(L_IRC,"CTCP VERSION From %s",nick);
	} else if(strncasecmp(buffer,"PING",4) == 0 && strlen(buffer) > 5) {
		snprintf(temp,sizeof(temp),
				"PING %s",buffer+5);
		irc_send_ctcp_notice(n,nick,temp);
		LOGTP(L_IRC,"CTCP PING From %s:%s",nick,buffer+4);
	} else if(strncasecmp(buffer,"DCC",3) == 0) {
		dcc_cmd(n,host,hostmask,nick,buffer);
	} else if(strncasecmp(buffer,"XDCC",4) == 0) {
		xdcc_cmd(n,host,hostmask,nick,buffer);
	} else
		LOGTP(L_IRC,"CTCP \"%s\" From %s",buffer,nick);
}

static void xmit_msg(network_t *net, enum mst_t type, channel_t *chan,
				const char *nick, const char *data, int r) {
	const size_t bufsize = 510;
	char *buffer;
	int i;

	if(net == NULL || nick == NULL)
		return;
	
	LOCK(net);
	if(net->no_listeners == 0 && chan == NULL) {
		UNLOCK(net);
		return;
	} else if(net->no_listeners == 0 && chan != NULL) {
		LOCK(chan);
		if(chan->no_listeners == 0) {
			UNLOCK(chan);
			UNLOCK(net);
			return;
		}
	}
	
	buffer = xalloc(bufsize);
	buffer[0] = '\0';
	if((type == PRIVMSG || type == NOTICE) && data != NULL) {
		if(chan != NULL) {
			if(strncasecmp(data,"\001ACTION",7) != 0)
				snprintf(buffer,bufsize,
					"%%K>>%%n[%s/%s] %s<%%n%s%s>%%n %s",
					net->name,chan->name,
					r == 1 ? COLOUR_BLUE : COLOUR_MAGENTA,
					nick,
					r == 1 ? COLOUR_BLUE : COLOUR_MAGENTA,
					data);
			else {
				snprintf(buffer,bufsize,
					"%%K>>%%n[%s/%s] %%K*%%n %%9%s%%9 %s",
					net->name,chan->name,nick,
					data + 8);
				buffer[strlen(buffer)-1] = '\0';
			}
		} else if(r == 1) {
			snprintf(buffer,bufsize,
					"%%K>>%%n[%s] %%b<%%n%s%%b>%%n %s",
					net->name,nick,data);
		} else
			snprintf(buffer,bufsize,
					"%%K>>%%n[%s] %%m[%%n%s%%m]%%n %s",
					net->name,nick,data);
	} else if(type == JOIN && chan != NULL) {
		snprintf(buffer,bufsize,
			"%%K>>%%n[%s] %%R-%%W:%%R-%%n %%C%s%%n joined %s",
			net->name,nick,chan->name);
	} else if(type == PART && chan != NULL) {
		snprintf(buffer,bufsize,
			"%%K>>%%n[%s] %%R-%%W:%%R-%%n %%c%s%%n left %s",
				net->name,nick,chan->name);
	} else if(type == KICK && chan != NULL) {
		snprintf(buffer,bufsize,
			"%%K>>%%n[%s] %%R-%%W:%%R-%%n %%c%s%%n was kicked from %s by %%c%s%%n",
				net->name,nick,chan->name,
				data != NULL ? data : "");
	} else if(type == NICK && data != NULL) {
		snprintf(buffer,bufsize,
			"%%K>>%%n[%s] %%R-%%W:%%R-%%n %%W%s%%n changed nick to %%c%s%%n",
				net->name,nick,data);
	} else if(type == QUIT) {
		snprintf(buffer,bufsize,
			"%%K>>%%n[%s] %%R-%%W:%%R-%%n %%W%s%%n quit",
				net->name,nick);
	}
	if(buffer[0] != '\0') {
		irc_strip_colours(buffer);
		if(chan != NULL) {
			for(i = 0; i < chan->no_listeners; ++i)
				pqueue_push_back(chan->listeners[i],
						xstrdup(buffer));
		}
		for(i = 0; i < net->no_listeners; ++i)
			pqueue_push_back(net->listeners[i],xstrdup(buffer));
	}
	xfree(buffer);
	if(chan != NULL)
		UNLOCK(chan);
	UNLOCK(net);
}

static void rxmit_msg(network_t *net, const char *msg) {
	char buffer[512],nick[32];
	channel_t *chan;
	char **parts;
	int i,no_parts;
	
	if((strncasecmp("PRIVMSG",msg,7) != 0 && 
			strncasecmp("NOTICE",msg,6) != 0) || is_dcc_ctcp(msg))
		return;
	
	xstrncpy(buffer,msg,sizeof(buffer));
	for(i = strlen(buffer) - 1; i >= 0; --i) {
		if(buffer[i] == '\n' || buffer[i] == '\r')
			buffer[i] = '\0';
		else
			break;
	}
	if(i < 0)
		return;
	
	no_parts = 3;
	parts = split_opt(buffer,buffer,&no_parts);
	if(no_parts != 3) {
		if(parts != NULL)
			xfree(parts);
		return;
	}
	parts[2]++; // remove leading :
	
	chan = irc_channel_find(net,parts[1]);
	nick[0] = '\0';
	if(chan != NULL) {
		LOCK(net);
		if(net->info != NULL) {
			LOCK(net->info);
			xstrncpy(nick,net->info->nick,sizeof(nick));
			UNLOCK(net->info);
		}
		UNLOCK(net);
	} else
		xstrncpy(nick,parts[1],sizeof(nick));
	if(nick[0] != '\0')
		xmit_msg(net,PRIVMSG,chan,nick,parts[2],0);
	
	xfree(parts);
}

static void perform_a_magic_trick(network_t *n,
		const char *nick, const char *host) {
	/* Okay, in a strange mood when I named this function.
	 * What it goes and finds sends/chats/uploads/queues for nick
	 * which are missing a host and fills it in.
	 */
	int i,j;
	
	LOCK(global);
	for(i = 0; i < global->no_interfaces; ++i) {
		interface_t *intf = global->interfaces[i];
		LOCK(intf);
		if(intf->type == INTERFACE_SEND) {
			for(j = 0; j < intf->send.no_pools; ++j) {
				pool_t *p = intf->send.pools[j];
				int k;
				
				LOCK(p);
				for(k = 0; k < p->no_csends; ++k) {
					LOCK(p->csends[k]);
					if(p->csends[k]->host == NULL &&
						p->csends[k]->network == n &&
						irc_strcasecmp(p->csends[k]->nick,nick) == 0)
						p->csends[k]->host = xstrdup(host);
					UNLOCK(p->csends[k]);
				}
				UNLOCK(p);
			}
		} else if(intf->type == INTERFACE_RECV) {
			for(j = 0; j < intf->recv.no_uploads; ++j) {
				LOCK(intf->recv.uploads[j]);
				if(intf->recv.uploads[j]->host == NULL &&
					intf->recv.uploads[j]->network == n &&
					intf->recv.uploads[j]->nick != NULL)
					if(irc_strcasecmp(intf->recv.uploads[j]->nick,nick) == 0)
						intf->recv.uploads[j]->host = xstrdup(host);
				UNLOCK(intf->recv.uploads[j]);
			}
		} else if(intf->type == INTERFACE_CHAT) {
			for(j = 0; j < intf->chat.no_chats; ++j) {
				LOCK(intf->chat.chats[j]);
				if(intf->chat.chats[j]->host == NULL &&
					intf->chat.chats[j]->network == n &&
					intf->chat.chats[j]->nick != NULL)
					if(irc_strcasecmp(intf->chat.chats[j]->nick,nick) == 0)
						intf->chat.chats[j]->host = xstrdup(host);
				UNLOCK(intf->chat.chats[j]);
			}
		}
		UNLOCK(intf);
	}
	for(i = 0; i < global->no_queues; ++i) {
		queue_t *q = global->queues[i];
		LOCK(q);
		for(j = 0; j < q->no_qsends; ++j) {
			LOCK(q->qsends[j]);
			if(q->qsends[j]->host == NULL &&
				q->qsends[j]->network == n &&
				irc_strcasecmp(q->qsends[j]->nick,nick) == 0)
				q->qsends[j]->host = xstrdup(host);
			UNLOCK(q->qsends[j]);
		}
		UNLOCK(q);
	}
	UNLOCK(global);
}

static void handle_ping(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char buffer[512];
	
	snprintf(buffer,sizeof(buffer),"PONG %s",tokens[1]),
	enqueue_msg(l->info,buffer,EM_LOCK | EM_DUP);
}

static void handle_error(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	disconnect_server(l,tokens[1]);
}

//4
static void handle_privmsg(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *src = tokens[1], *dst = tokens[2], *data = tokens[3];
	char *host = NULL, *nick = NULL, *tmp = NULL;
	char buffer[512];
	channel_t *chan = NULL;
			
	
	if(is_channel(l,dst)) {
		/* channel message */
		chan = irc_channel_find(l->network,dst);
	} else {
		chan = NULL;
	}
			
	if(chan == NULL) {
		if(ignore_test(global->ignore_list,src)) {
			return; // IGNORE
		}
	}

	host = p2h(src);
	nick = p2n(src);
	
	xmit_msg(l->network,type,chan,nick,data,1);
	
	if(data[0] == '\001') {
		handle_ctcp(l->network,chan,host,src,nick,data);
	} else if(strncasecmp(data,"!list",5) == 0) {
		tmp = strchr(data,' ');
		if(tmp == NULL && strlen(data) == 5)
			xdcc_info(l->network,chan,host,src,nick);
		else if(tmp != NULL) {
			while(isspace(*tmp)) tmp++;
			if(*tmp == '\0')
				xdcc_info(l->network,chan,host,src,nick);
			else {
				LOCK(l->info);
				strncpy(buffer,l->info->nick,sizeof(buffer));
				UNLOCK(l->info);
						
				if(irc_strcasecmp(tmp,buffer) == 0) {
					tmp += strlen(buffer);
					if(*tmp == '\0' || isspace(*tmp))
						xdcc_info(l->network,chan,
							host,src,nick);
				}
			}
		}
	} else if(chan != NULL && strncasecmp(data,"xdcc list",9) == 0) {
		if(data[nextchar(data,9)] == '\0')
			xdcc_info(l->network,chan,host,src,nick);
	} else if(chan == NULL && (strncasecmp(data,"xdcc",4) == 0)) {
		//D("host:%s, nick: %s, data: %s",host,nick,data);
		xdcc_cmd(l->network,host,src,nick,data);
	} else if(chan == NULL)
		LOGTP(L_IRC,"%s From %s: %s",
				(type == PRIVMSG ? "MSG" : "NOTICE"),nick,data);
	
	xfree(nick);
	xfree(host);
}

//3
static void handle_join(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *src = tokens[1], *data = tokens[2], buffer[128];
	char *nick = p2n(src);
	channel_t *chan = irc_channel_find(l->network,data);

	//D("data: \"%s\", chan: %p, nick: \"%s\"",data,chan,nick);
	
	LOCK(l->info);
	if(chan != NULL)
		nickhash_add(l->info->nick_tracker,chan,nick);
			
	if(irc_strcasecmp(l->info->nick,nick) == 0) {
		UNLOCK(l->info);
		if(chan != NULL) {
			LOCK(chan);
			chan->joined = xtime();
			chan->last_plist = chan->joined;
			channel_reset_state(chan);
			UNLOCK(chan);
			snprintf(buffer,sizeof(buffer),"MODE %s",data);
			irc_send_raw(l->network,buffer);
			snprintf(buffer,sizeof(buffer),"MODE %s b",data);
			irc_send_raw(l->network,buffer);
			LOGTP(L_IRC,"Joined Channel: %s",data);
		} else
			LOGTP(L_IRC,"Received JOIN for Unknown Channel: %s",data);
	} else
		UNLOCK(l->info);
			
	xmit_msg(l->network,JOIN,chan,nick,NULL,1);
	xfree(nick);
}

//3-4
static void handle_part(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *src = tokens[1], *data = tokens[2]; // parted channel
	char *nick = p2n(src);
	channel_t *chan = irc_channel_find(l->network,data);
	int i;
	
	//D("chan: %p, src: \"%s\", nick: \"%s\"",chan,src,nick);
	
	LOCK(l->info);
	if(chan != NULL)
		nickhash_del(l->info->nick_tracker,chan,nick);
	
	if(irc_strcasecmp(l->info->nick,nick) == 0) {
		UNLOCK(l->info);
		LOCK(l->network);
		/* Special case, we want PART's for deleted
		 * channels. */
		for(i = 0, chan = NULL; i < l->network->no_channels && chan == NULL; ++i) {
			LOCK(l->network->channels[i]);
			if(irc_strcasecmp(l->network->channels[i]->name,data) == 0)
				chan = l->network->channels[i];
			else
				UNLOCK(l->network->channels[i]);
		}
		if(chan != NULL) {
			chan->joined = 0;
			channel_reset_state(chan);
			UNLOCK(chan);
			nickhash_flush_channel(l->info->nick_tracker,chan);
			LOGTP(L_IRC,"Parted From %s",data);
		} else {
			LOGTP(L_IRC,"Received PART for unknown channel %s",data);
		}
		UNLOCK(l->network);
	} else
		UNLOCK(l->info);
			
	xmit_msg(l->network,PART,chan,nick,NULL,1);
	xfree(nick);
}

//4
static void handle_kick(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *src = tokens[1], *dst = tokens[2], *data = tokens[3];
	char *nick = p2n(src); // who did the kick
	channel_t *chan = irc_channel_find(l->network,dst);
	
	LOCK(l->info);
	if(irc_strcasecmp(data,l->info->nick) == 0) {
		UNLOCK(l->info);
		if(chan != NULL) {
			LOCK(chan);
			chan->joined = 0;
			channel_reset_state(chan);
			UNLOCK(chan);
			LOGTP(L_IRC,"Was Kicked From %s, By %s, Reason: %s",
					dst,nick,tokens[no_tokens-1]);
		} else
			LOGTP(L_IRC,"Recieved KICK for unknown channel %s",dst);
	} else {
		if(chan != NULL)
			nickhash_del(l->info->nick_tracker,chan,data);
		UNLOCK(l->info);
	}
	
	xmit_msg(l->network,KICK,chan,data,nick,1);
	xfree(nick);
}

//3
static void handle_nick(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *src = tokens[1], *data = tokens[2];
	char *nick = p2n(src);
	int i,tracking;

	//D("nick:\"%s\", data:\"%s\"",nick,data);
	
	LOCK(l->network);
	tracking = l->network->settings->nick_tracking;
	LOCK(l->info);
	nickhash_rename(l->info->nick_tracker,nick,data);
	if(irc_strcasecmp(nick,l->info->nick) == 0) {
		if(l->info->nick != NULL)
			xfree(l->info->nick);
		l->info->nick = xstrdup(data);
		LOGTP(L_IRC,"Changed Nick to: %s",data);
	}
	UNLOCK(l->info);
	UNLOCK(l->network);
	
	if(tracking) {
		admin_nick_change(l->network,nick,data);
		LOCK(global);
		for(i = 0; i < global->no_interfaces; ++i)
			interface_nick_change(global->interfaces[i],
					l->network,nick,data);
		for(i = 0; i < global->no_queues; ++i)
			queue_nick_change(global->queues[i],
					l->network,nick,data);
		UNLOCK(global);
	}
	
	xmit_msg(l->network,NICK,NULL,nick,data,1);
	xfree(nick);
}

//2-3
static void handle_quit(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *src = tokens[1]; // who quit
	char *nick = p2n(src);
	
	LOCK(l->network);
	LOCK(l->info);
	//D("QUIT, nick: %s",nick);
	nickhash_del(l->info->nick_tracker,NULL,nick);
	if(irc_strcasecmp(l->network->settings->nick,l->info->nick) != 0) {
		if(irc_strcasecmp(l->network->settings->nick,nick) == 0) {
			char buffer[128];
			//LOGTP(L_INF,"Attempting to change nick to: %s",nick);
			snprintf(buffer,sizeof(buffer),"NICK %s",nick);
			enqueue_msg(l->info,buffer,EM_DUP);
		}
	}
	UNLOCK(l->info);
	UNLOCK(l->network);
	
	xmit_msg(l->network,QUIT,NULL,nick,NULL,1);
	xfree(nick);
}

//3-
static void handle_rpl_welcome(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *dst = tokens[2]; // our nick in non-prefix form
	
	LOCK(l->network);
	LOCK(l->info);
	if(l->info->nick != NULL) {
		if(irc_strcasecmp(l->info->nick,dst) != 0) {
			xfree(l->info->nick);
			l->info->nick = xstrdup(dst);
		}
	} else {
		l->info->nick = xstrdup(dst);
	}
	
	l->network->state = NETWORK_CONNECTED;
	LOGTP(L_IRC,"Login Complete, Set Nick: %s",l->info->nick);
	
	/* Now we know our nickname look up our IP */
	enqueue_msg(l->info,"USERHOST $BOT",EM_DUP);
	
	UNLOCK(l->info);
	UNLOCK(l->network);
}

#if 0
//5,6-
static void determine_ip(netlocal_t *l); // pre-declaration
static void handle_rpl_whois(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *nick = tokens[3]; // whois was of this nick
	char *host = NULL;
	struct in_addr addr;
	int ret;
	
	LOCK(l->info);
	if(l->info->nick != NULL) {
		if(irc_strcasecmp(l->info->nick,nick) == 0) {
			if(type == RPL_WHOISUSER)
				host = tokens[5];
			else if(type == RPL_WHOIS_CUSTOM) {
				char *data = tokens[4];
				if(strncasecmp(data,"is connecting from ",19) == 0)
					host = p2h(data + 19);
			}
			UNLOCK(l->info);
		} else if(type == RPL_WHOISUSER) {
			UNLOCK(l->info);
			/* MMMMMMMagic! */
			perform_a_magic_trick(l->network,nick,tokens[5]);
		}
	} else
		UNLOCK(l->info);

	if(host != NULL) {
		ret = aresolv_nametoaddr(global->aresolver,AF_INET,host,&addr);
		if(ret == 0) {
			LOGTP(L_IRC,"Connected via \"%s\"",host);
			LOCK(l->info);
			l->info->self_ip = ntohl((unsigned long) addr.s_addr);
			UNLOCK(l->info);
			l->on_failback_ip = 0;
		} else
			determine_ip(l);
		if(type == RPL_WHOIS_CUSTOM)
			xfree(host);
	}
}
#endif

// 4
static void determine_ip(netlocal_t *l); // pre-declaration
static void handle_rpl_userhost(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	struct in_addr addr;
	char buffer[256];
	char *nick, *host, *tmp;
	int ret = -1;

	xstrncpy(buffer,tokens[3],sizeof(buffer));
	
	if((tmp = strchr(buffer,'=')) == NULL)
		return;
	
	*tmp = '\0';
	if(tmp == buffer)
		return;
	if(*(tmp-1) == '*')
		*(tmp-1) = '\0';
	if(*(tmp+1) == '\0')
		return;
	
	nick = buffer;
	host = (tmp+1);
	
	if((tmp = strchr(host,'@')) != NULL)
		host = (tmp+1);
	else if(*host == '+' || *host == '-')
		host++;
	
	trim(nick);
	trim(host);
	
	LOCK(l->info);
	if(l->info->nick != NULL) {
		if(irc_strcasecmp(l->info->nick,nick) == 0)
			ret = 0;
	}	
	UNLOCK(l->info);
	
	if(ret == 0) {
		ret = aresolv_nametoaddr(global->aresolver,AF_INET,host,&addr);
		LOGTP(L_IRC,"Server says we are connected via \"%s\"",host);
		if(ret == 0) {
			LOCK(l->info);
			l->info->self_ip = ntohl((unsigned long) addr.s_addr);
			UNLOCK(l->info);
			
			inet_ntop(AF_INET,&addr,buffer,sizeof(buffer));
			LOGTP(L_IRC,"Advertising address \"%s\"",buffer);
		} else {
			determine_ip(l);
		}
	} else {
		perform_a_magic_trick(l->network,nick,host);
	}
}

// 5-6
static void handle_rpl_namreply(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *data = tokens[3];
	char **nicks;
	int no_nicks; 
	channel_t *chan;
	int i;
	
	//D("RPL_NAMREPLY, data: \"%s\"",data);
			
	if(strlen(data) == 1) {
		if(	data[0] == '=' || 
			data[0] == '*' ||
			data[0] == '@') {
			if(no_tokens < 5)
				return;
			else
				data = tokens[4];
		}
		//D("RPL_NAMREPLY, data change: \"%s\"",data);
	}
	
	chan = irc_channel_find(l->network,data);
	if(chan == NULL)
		return;
	
	no_nicks = 0;
	nicks = split_opt(tokens[no_tokens-1],tokens[no_tokens-1],&no_nicks);
	
	LOCK(l->info);
	for(i = 0; i < no_nicks; ++i) {
		if(strlen(nicks[i]) == 0)
			continue;
		if(!is_chan_nick_prefix(l,nicks[i][0]))
			nickhash_add_mode(l->info->nick_tracker,chan,
					nicks[i],MODE_REG);
		else // first char is mode
			nickhash_add_mode(l->info->nick_tracker,chan,
					nicks[i]+1,mode_from_char(nicks[i][0]));
	}
	UNLOCK(l->info);

	if(nicks != NULL)
		xfree(nicks);
}

//4-
static void handle_err_nosuchnick(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *nick = p2n(tokens[3]); // failed nick
	
	queue_remove(NULL,NULL,l->network,nick,NULL,NULL,NULL);
	
	xfree(nick);
}

//4-
static void handle_err_nicknameinuse(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *dst = tokens[2];
	char buffer[64]; // nicknames
	int i;
	
	if(strcmp(dst,"*") == 0) {
		/* Part of the login, we don't have a nick yet
		 * since it seems are primary is in use.
		 */
		LOCK(l->info);
		if(l->info->nick == NULL) {
			/* re-serialise locks */
			UNLOCK(l->info);
			LOCK(l->network);
			LOCK(l->info);

			l->info->nick = xstrdup(l->network->settings->nick);
			UNLOCK(l->network);
		}
		if(strlen(l->info->nick) < 9) {
			snprintf(buffer,sizeof(buffer)-1,"%s_",l->info->nick);
		} else {
			/* So your nick is now 9 chars, and we
			 * don't want to go beyond the RFC so
			 * we try something other than adding
			 * _ to the end of the nick.
			 */
			for(i = 1; i < strlen(l->info->nick); ++i) {
				buffer[i-1] = l->info->nick[i];
			}
			buffer[i-1] = l->info->nick[0];
			buffer[i] = '\0';			
		}
		if(l->info->nick != NULL)
			xfree(l->info->nick);
		l->info->nick = xstrdup(buffer);
		snprintf(buffer,sizeof(buffer)-1,"NICK %s\r\n",l->info->nick);
		enqueue_msg(l->info,buffer,EM_DUP);
		UNLOCK(l->info);
	}
}


//4-
static void handle_channel_error(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *data = tokens[4];
	int i;
	
	switch (type) {
		case ERR_CHANNELISFULL:
		case ERR_INVITEONLYCHAN:
		case ERR_BANNEDFROMCHAN:
		case ERR_BADCHANNELKEY:
			/*
			chan = irc_channel_find(l->network,data);
			if(chan != NULL) {
				LOCK(chan);
				chan->joined = 0;
				UNLOCK(chan);
			}
			*/
			LOGTP(L_IRC,"Failed to Join Channel %s: %s",
					tokens[3],data);
			break;
		case ERR_NOTONCHANNEL:
			LOCK(l->network);
			/* special case, for deleted channels */
			for(i = 0; i < l->network->no_channels; ++i) {
				LOCK(l->network->channels[i]);
				if(irc_strcasecmp(l->network->channels[i]->name,data) == 0)
					l->network->channels[i]->joined = 0;
				UNLOCK(l->network->channels[i]);
			}
			UNLOCK(l->network);
			break;
		default:
			break;
	}
}

//3-
static void handle_mode(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char **modes, **args;
	char *dst; // what had its mode changed
	channel_t *chan;
	int i,mpos,apos,state,arg_start;
	
	if(type == MODE) {
		dst = tokens[2];
		arg_start = 3;
	} else if(type == RPL_CHANNELMODEIS && no_tokens >= 4) {
		dst = tokens[3];
		arg_start = 4;
	} else
		return;
	
	if(!is_channel(l,dst))
		return; // user mode, ignore

	chan = irc_channel_find(l->network,dst);
	if(chan == NULL) {
		LOGTP(L_IRC,"Recieved MODE for unknown channel %s",dst);
		return;
	}

	if(type == RPL_CHANNELMODEIS) {
		LOCK(chan);
		if(chan->mode != NULL) {
			xfree(chan->mode);
			chan->mode = NULL;
		}
		UNLOCK(chan);
	}

	modes = xalloc(sizeof(char *)*(no_tokens-3));
	args = xalloc(sizeof(char *)*(no_tokens-3));
	for(i = 0; i < (no_tokens-3); ++i) {
		modes[i] = NULL;
		args[i] = NULL;
	}
	
	mpos = 0;
	apos = 0;
	for(i = arg_start; i < no_tokens; ++i) {
		if(tokens[i][0] == '+' || tokens[i][0] == '-') {
			//D("mode %d \"%s\"",mpos,tokens[i]);
			modes[mpos++] = tokens[i];
		} else {
			//D("arg %d \"%s\"",apos,tokens[i]);
			args[apos++] = tokens[i];
		}
	}

	LOCK(l->network);
	LOCK(l->info);
	mpos = 0;
	apos = 0;
	while(mpos < (no_tokens-3) && modes[mpos] != NULL) {
		if(modes[mpos][0] == '+')
			state = MODE_ADD;
		else if(modes[mpos][0] == '-')
			state = MODE_DEL;
		else {
			mpos++;
			continue;
		}
		for(i = 1; modes[mpos][i] != '\0'; ++i) {
			if(is_chan_nick_mode(l,modes[mpos][i]) 
					&& args[apos] != NULL) {
				nickhash_change_mode(
						l->info->nick_tracker,
						chan,args[apos++],
						state,
						mode_from_char(nprefix_from_mode(l,modes[mpos][i])));
			} else if(is_chan_arg_mode(l,modes[mpos][i])
					&& args[apos] != NULL) {
				channel_change_mode(chan,
					state,modes[mpos][i],
					args[apos++]);
			} else if(is_chan_parg_mode(l,modes[mpos][i])) {
				if(state == MODE_ADD && args[apos] != NULL)
					channel_change_mode(chan,
						state,modes[mpos][i],
						args[apos++]);
				else if(state == MODE_DEL)
					channel_change_mode(chan,
						state,modes[mpos][i],NULL);
			} else if(is_chan_noarg_mode(l,modes[mpos][i])) {
				channel_change_mode(chan,
						state,modes[mpos][i],NULL);
			}
		}
		mpos++;
	}
	UNLOCK(l->info);
	UNLOCK(l->network);

	if(modes != NULL)
		xfree(modes);
	if(args != NULL)
		xfree(args);
}

static void handle_topic(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	channel_t *chan = NULL;
	char *topic = NULL;
	
	//D("no_tokens: %d",no_tokens);
	
	if(type == TOPIC) {
		chan = irc_channel_find(l->network,tokens[2]);
		topic = tokens[3];
	} else if(type == RPL_TOPIC && no_tokens >= 5) {
		chan = irc_channel_find(l->network,tokens[3]);
		topic = tokens[4];
	} else
		return;

	if(chan != NULL) {
		LOCK(chan);
		if(chan->topic != NULL)
			xfree(chan->topic);
		chan->topic = xstrdup(topic);
		UNLOCK(chan);
	}
}

static void handle_banlist(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	channel_t *chan = irc_channel_find(l->network,tokens[3]);
	if(chan != NULL) {
		LOCK(chan);
		PTRARR_ADD(&(chan->bans),&(chan->no_bans),xstrdup(tokens[4]));
		UNLOCK(chan);
	}
}

static void handle_netinfo(netlocal_t *l, enum mst_t type,
		char **tokens, int no_tokens) {
	char *data;
	int i,j,k;
	
	for(i = 3; i < no_tokens; ++i) {
		if(strncasecmp(tokens[i],"CHANTYPES=",10) == 0) {
			data = strchr(tokens[i],'=');
			xstrncpy(l->chantype,data+1,MODEBUFSIZE);
		} else if(strncasecmp(tokens[i],"PREFIX=(",8) == 0) {
			data = strchr(tokens[i],'(');
			data++;
			for(j = 0; data[j] != '\0' && data[j] != ')'; ++j) ;
			if(j < (MODEBUFSIZE+1))
				xstrncpy(l->mprefix,data,j+1); // copies j
			data += j+1;
			xstrncpy(l->nprefix,data,MODEBUFSIZE);
		} else if(strncasecmp(tokens[i],"CHANMODES=",10) == 0) {
			data = strchr(tokens[i],'=');
			data++;
			k = 0;
			for(j = 0; data[j] != '\0' && data[j] != ',' && 
					k < MODEBUFSIZE-1; ++j)
				l->acmode[k++] = data[j];
			if(data[j] != '\0')
				j++;
			for(; data[j] != '\0' && data[j] != ',' &&
					k < MODEBUFSIZE-1; ++j)
				l->acmode[k++] = data[j];
			l->acmode[k]= '\0';
			
			if(data[j] != '\0')
				j++;
			k = 0;
			for(; data[j] != '\0' && data[j] != ',' &&
					k < MODEBUFSIZE-1; ++j)
				l->pcmode[k++] = data[j];
			l->pcmode[k] = '\0';
			
			if(data[j] != '\0')
				j++;
			k = 0;
			for(; data[j] != '\0' && data[j] != ',' &&
					k < MODEBUFSIZE-1; ++j)
				l->ncmode[k++] = data[j];
		}
	}
	
	//D("chantypes: \"%s\"",l->chantype);
	//D("acmodes: \"%s\"",l->acmode);
	//D("pcmodes: \"%s\"",l->pcmode);
	//D("ncmodes: \"%s\"",l->ncmode);
	//D("mprefix: \"%s\"",l->mprefix);
	//D("nprefix: \"%s\"",l->nprefix);
}

static void handle_unknown(netlocal_t *l, enum mst_t type, 
		char **tokens, int no_tokens) {
	// just to prevent worst case look ups
	return;
}

static const struct {
	enum mst_t 	type;
	int		min_args; // these include cmd,src,dst
	int		max_args;
	void		(*func)(netlocal_t *, enum mst_t, char **, int);
} msg_hooks[] = { /* ordered to optomise lookup */
	{PRIVMSG, 		4, 4, handle_privmsg},
	{PING, 			2, 2, handle_ping},
	{UNKNOWN,		0,-1, handle_unknown},
	{NOTICE, 		4, 4, handle_privmsg},
	{JOIN, 			3, 3, handle_join},
	{QUIT, 			2, 3, handle_quit},
	{NICK,			3, 3, handle_nick},
	{MODE,			3,-1, handle_mode},
	{PART,			3, 4, handle_part},
	{KICK,			4, 5, handle_kick},
	{ERROR,			2, 2, handle_error},
	{RPL_NAMREPLY,		5, 6, handle_rpl_namreply},
	{RPL_CHANNELMODEIS,	3,-1, handle_mode},
	{RPL_USERHOST,		4, 4, handle_rpl_userhost},
//	{RPL_WHOISUSER,		6,-1, handle_rpl_whois},
//	{RPL_WHOIS_CUSTOM,	5, 5, handle_rpl_whois},
	{RPL_TOPIC,		4, 5, handle_topic},
	{TOPIC,			4, 5, handle_topic},
	{RPL_BANLIST,		5,-1, handle_banlist},
	{RPL_WELCOME,		3,-1, handle_rpl_welcome},
	{RPL_NETINFO,		4,-1, handle_netinfo},
	{ERR_NOSUCHNICK,	4,-1, handle_err_nosuchnick},
	{ERR_NICKNAMEINUSE, 	4,-1, handle_err_nicknameinuse},
	{ERR_CHANNELISFULL,	4,-1, handle_channel_error},
	{ERR_INVITEONLYCHAN,	4,-1, handle_channel_error},
	{ERR_BANNEDFROMCHAN,	4,-1, handle_channel_error},
	{ERR_BADCHANNELKEY,	4,-1, handle_channel_error},
	{ERR_NOTONCHANNEL,	4,-1, handle_channel_error},
	{0,			0, 0, NULL}
};

static int process_msgs(netlocal_t *l) {
	char **tokens;
	char *msg,*tmp;
	int i,found,no_tokens;
	enum mst_t type;
	
	while((msg = pqueue_pop_front(&l->rqueue)) != NULL) {
		tokenise_msg(msg,&tokens,&no_tokens);
		if(no_tokens > 0) {
			if(tokens[0][0] == ':') {
				tmp = tokens[0];
				tokens[0] = tokens[1];
				tokens[1] = tmp + 1;
			}
			type = str_to_mst(tokens[0]);
			for(i = 0, found = -1; 
				msg_hooks[i].func != NULL && found == -1; ++i) {
				if(msg_hooks[i].type == type)
					found = i;
			}
			//D("type: %s, tokens: %d, found: %d",tokens[0],no_tokens,found);
			if(found != -1) {
				i = found;
				/* This means we can bind more than one function
				   to a message type, but they must be next to
				   each other in the list. */
				while(msg_hooks[i].func != NULL && msg_hooks[i].type == type) {
					if((no_tokens >= msg_hooks[i].min_args) && (no_tokens <= msg_hooks[i].max_args || msg_hooks[i].max_args == -1)) {
						msg_hooks[i].func(l,type,tokens,no_tokens);
					} else
						D("%d, %d does not fit %d to %d",
							type,no_tokens,
							msg_hooks[i].min_args,
							msg_hooks[i].max_args);
					i++;
				}
			}
			se_irc_msg(l->network,tokens,no_tokens);
		}
		if(tokens != NULL)
			xfree(tokens);
		xfree(msg);
		if(l->socket == -1) {
			/* Error occur and we have been disconnected */
			return -1;
		}
	}
	return 0;
}

static int login_server(netlocal_t *l) {
	char buffer[512];
	channel_t *chan;
	int i;
	LOCK(l->network);
	LOCK(l->info);
	if(l->info->nick != NULL) {
		if(strcmp(l->info->nick,l->network->settings->nick) != 0) {
			xfree(l->info->nick);
			l->info->nick = xstrdup(l->network->settings->nick);
		}
	} else {
		l->info->nick = xstrdup(l->network->settings->nick);
	}

	if(l->info->password == NULL) {
		snprintf(buffer,sizeof(buffer)-1,
				"NICK %s\r\nUSER %s * * :%s\r\n",
				l->info->nick,
				l->network->settings->loginname,
				l->network->settings->realname
		);
	} else {
		snprintf(buffer,sizeof(buffer)-1,
				"PASS %s\r\nNICK %s\r\nUSER %s * * :%s\r\n",
				l->info->password,
				l->info->nick,
				l->network->settings->loginname,
				l->network->settings->realname
		);
	}
	
	l->network->state = NETWORK_LOGIN;
	UNLOCK(l->info);
	UNLOCK(l->network);
	
	if(l->snd_msg != NULL)
		xfree(l->snd_msg);
	l->snd_msg = xstrdup(buffer);
	load_linebuf(l->snd_buf,buffer);
	i = send_line(l->socket,l->snd_buf);
	if(i <= 0 && errno != EAGAIN) {
		xfree(l->snd_msg);
		l->snd_msg = NULL;
		return -1;
	} else if(i > 0) {
		xfree(l->snd_msg);
		l->snd_msg = NULL;
	}
	
	LOCK(l->network);
	LOCK(l->info);
	if(l->network->channels != NULL) {
		for(i = l->network->no_channels - 1; i >= 0; --i) {
			chan = l->network->channels[i];
			LOCK(chan);
			if(!chan->deleted) {
				if(chan->key == NULL)
					snprintf(buffer,(sizeof(buffer))-1,
							"JOIN %s",
							chan->name);
				else
					snprintf(buffer,(sizeof(buffer))-1,
							"JOIN %s %s",
							chan->name,chan->key);
				UNLOCK(chan);
				enqueue_msg(l->info,buffer,EM_DUP);
			} else {
				UNLOCK(chan);
			}
		}
	}
	if(l->network->on_connects != NULL) {
		for(i = l->network->no_on_connects - 1; i >= 0; --i)
			enqueue_msg(l->info,l->network->on_connects[i],
					EM_DUP | EM_PRIORITY);
	}
	l->info->self_ip = 0;
	UNLOCK(l->info);
	
	/* Initialize Send Averaging Window */
	l->window_size = l->network->settings->average_window;
	if(l->window_size < 5)
		l->window_size = 5;
	UNLOCK(l->network);
	l->window = xalloc(sizeof(int)*l->window_size);
	for(i = 0; i < l->window_size; ++i)
		l->window[i] = 0;
	
	l->last_join = xtime();
	l->last_send = xtime();
	
	return 0;
}	

static int window_sum(netlocal_t *l) {
	int i,sum;
	for(i = 0, sum = 0; i < l->window_size; ++i) {
		sum += l->window[i];
	}
	return sum;
}

static char *dequeue_msg(netlocal_t *l, time_t now) {
	enum networkstate_t state;
	time_t join_delay, on_connect_delay;
	char *msg = NULL;
	
	LOCK(l->network);
	state			= l->network->state;
	join_delay		= l->network->settings->join_delay;
	on_connect_delay	= l->network->settings->on_connect_delay;
	UNLOCK(l->network);
	
	LOCK(l->info);
	msg = pqueue_pop_front(&(l->info->p_squeue));
	if(msg == NULL && state != NETWORK_LOGIN) {
		if((l->last_join + join_delay) < now) {
			msg = pqueue_pop_front(&(l->info->j_squeue));
			if(msg != NULL)
				l->last_join = now;
		}
		if((l->last_connect + on_connect_delay) < now) {
			if(msg == NULL && l->info->self_ip != 0)
				msg = pqueue_pop_front(&(l->info->d_squeue));
			if(msg == NULL)
				msg = pqueue_pop_front(&(l->info->r_squeue));
		}
	}
	UNLOCK(l->info);

	return msg;
}

static size_t expand_msg(netlocal_t *l,
		const char *msg, char *buffer, size_t bufsize) {
	int i, j;
	
	for(i = 0, j = 0; msg[i] != '\0' && j < bufsize; ++i) {
		if(msg[i] == '$') {
			if(strncmp(msg+i,"$BOT",4) == 0) {
				LOCK(l->info);
				j += IMIN(
					snprintf(buffer+j,
						bufsize-j,
						"%s",l->info->nick),
					bufsize-j-1
				);
				UNLOCK(l->info);
				i += 3; // will get another 1 from ++i
			} else if(strncmp(msg+i,"$IIP",4) == 0) {
				LOCK(l->info);
				j += IMIN(
					snprintf(buffer+j,bufsize-j,"%lu",
						(unsigned long)l->info->self_ip),
					bufsize-j-1
				);
				UNLOCK(l->info);
				i += 3; // will get another 1 from ++i
			} else {
				buffer[j++] = '$';
			}
		} else {
			buffer[j++] = msg[i];
		}
	}
	j += snprintf(buffer+j,bufsize-j,"\r\n");

	return j;
}

static int send_msgs(netlocal_t *l) {
	char 	buffer[512], *p;
	float 	messages, message_rate, message_rate_max;
	time_t 	shift, now = xtime();
	int 	i, ret = 0;

	/* take local copies of some settings for the duration of the loop */
	LOCK(l->network);
	message_rate 	= (float) l->network->settings->message_rate;
	message_rate_max= (float) l->network->settings->message_rate_max;
	UNLOCK(l->network);
	if(message_rate <= 0.0)
		message_rate = 0.5;
	if(message_rate_max <= 0.0)
		message_rate_max = 2.0;
	
	/* zero the average window for time we haven't been sending messages */
	shift = l->last_send;
	if((shift - now) > l->window_size) {
		for(i = 0; i < l->window_size; ++i)
			l->window[i] = 0;
		messages = 0.0;
	} else {
		while(shift < now) {
			shift++;
			l->window[shift % l->window_size] = 0;
		}
		messages = (float) window_sum(l);
	}
	while	((
		 (float) l->window[shift % l->window_size] <
		 (float) ceil(message_rate_max * (
		   (message_rate - (messages / (float) l->window_size))
		   / message_rate))
		) && (
		 (messages / (float) l->window_size) < message_rate
		)) {

		if(l->snd_msg == NULL) {
			p = dequeue_msg(l,now);
			if(p == NULL)
				break;
			l->snd_msg = p;
			expand_msg(l,p,buffer,sizeof(buffer));
			rxmit_msg(l->network,buffer);
			load_linebuf(l->snd_buf,buffer);
		}

		ret = send_line(l->socket,l->snd_buf);
		if(ret > 0) {
			if(l->snd_msg != NULL) {
				xfree(l->snd_msg);
				l->snd_msg = NULL;
			}
			l->last_send = shift;
			l->window[shift % l->window_size]++;
			messages += 1.0;
		} else {
			if(ret == -1) {
				if(errno != EAGAIN || errno == EINTR) {
					char errbuf[96];
					LOGTP(L_ERR,"Got Error: %s",
						lstrerror_r(errno,errbuf,
							sizeof(errbuf)));
				} else
					ret = 0;
			}
			break;
		}
	}

	/* try to thin a large queue */
	LOCK(l->info);
	if(pqueue_length(&l->info->r_squeue) >= IMAX(50,(int)(message_rate*300.0))) {
		LOGTP(L_ERR,"Send queue exceeds %d messages, completely flushing it.",IMAX(50,(int)(message_rate*300.0)));
		pqueue_clear(&l->info->r_squeue,xfree,0);
	}
	UNLOCK(l->info);
	
	return ret;
}

static void check_channels(netlocal_t *l) {
	char 		buffer[512];
	channel_t 	*chan;
	int 		i,chans;
	
	LOCK(l->network);
	LOCK(l->info);
	chans = l->network->no_channels;
	for(i = 0; i < l->network->no_channels; ++i) {
		chan = l->network->channels[i];
		LOCK(chan);
		if(chan->joined == 0 && chan->deleted == 0) {
			if(chan->key == NULL)
				snprintf(buffer,sizeof(buffer),"JOIN %s",
						chan->name);
			else
				snprintf(buffer,sizeof(buffer),"JOIN %s %s",
						chan->name,chan->key);
			enqueue_msg(l->info,buffer,EM_DUP);
		} else if(chan->joined != 0 && chan->deleted) {
			snprintf(buffer,sizeof(buffer),"PART %s",chan->name);
			enqueue_msg(l->info,buffer,EM_DUP);
			chan->joined = 0;
		}
		if(chan->deleted) {
			if(IS_PURGE_TIME(chan->deleted,xtime())) {
				l->network->channels[i] = NULL;
				nickhash_flush_channel(l->info->nick_tracker,
									chan);
				UNLOCK(chan);
				free_channel(chan);
				chans--;
				continue; // skip UNLOCK()
			}
		}
		UNLOCK(chan);
	}
	if(chans < l->network->no_channels) {
		/* Purge deleted channels */
		PTRARR_DEL(&(l->network->channels),
				&(l->network->no_channels),NULL);
	}
	UNLOCK(l->info);
	UNLOCK(l->network);
}

static void channel_plists(netlocal_t *l, char *buffer, size_t bufsize) {
	char cname[64];
	char **names = NULL, **msgs;
	packlist_t *list,**lists;
	pformat_t pformat;
	channel_t *c;
	pqueue_t cq,*sq;
	time_t now = xtime();
	int i,j,no_names = 0, no_lists;
	
	pqueue_init(&cq,0);

	LOCK(l->network);
	for(i = 0; i < l->network->no_channels; ++i) {
		c = l->network->channels[i];
		LOCK(c);
		if(c->deleted == 0 && c->plist != 0 
				&& (c->last_plist + (c->plist*60)) < now 
				&& now > c->nolist)
			pqueue_push_back(&cq,c);
		UNLOCK(c);
	}
	LOCK(l->info);
	sq = &(l->info->r_squeue);
	UNLOCK(l->info);
	UNLOCK(l->network);

	if(pqueue_length(&cq) == 0)
		return;
	
	LOCK(global);
	lists = xalloc(sizeof(packlist_t *)*(global->no_packlists+1));
	no_lists = 0;
	for(i = 0; i < global->no_packlists; ++i) {
		LOCK(global->packlists[i]);
		if(global->packlists[i]->deleted == 0)
			lists[no_lists++] = global->packlists[i];
		UNLOCK(global->packlists[i]);
	}
	UNLOCK(global);

	if(no_lists == 0) {
		xfree(lists);
		return;
	}
	
	while((c = pqueue_pop_front(&cq)) != NULL) {
		LOCK(c);
		c->last_plist = now;
		pformat = c->pformat;
		xstrncpy(cname,c->name,sizeof(cname));
		UNLOCK(c);

		for(i = 0; i < no_lists; ++i) {
			list = lists[i];
			if(packlist_check_access_channel(list,l->network,c)) {
				if(pformat == PFORMAT_MINIMAL) {
					LOCK(list);
					snprintf(buffer,bufsize,"PRIVMSG %s :%s**%s XDCC %s**%s Packlist: %s\r\n",
						IRC_BOLD,IRC_BOLD,
						IRC_BOLD,IRC_BOLD,
						cname,list->name);
					UNLOCK(list);
					
					pqueue_push_back(sq,xstrdup(buffer));
					snprintf(buffer,bufsize,"PRIVMSG %s :",cname);
					packlist_xdl_body(list,sq,COLOUR_IRC,buffer,"\r\n");
				} else if(pformat == PFORMAT_SUMMARY) {
					LOCK(list);
					PTRARR_ADD(&names,&no_names,xstrdup(list->name));
					UNLOCK(list);
				} else if(pformat == PFORMAT_FULL) {
					snprintf(buffer,bufsize,"PRIVMSG %s :",cname);
					packlist_xdl_head(list,sq,
							COLOUR_IRC,buffer,"\r\n",1);
					packlist_xdl_body(list,sq,
							COLOUR_IRC,buffer,"\r\n");
					packlist_xdl_tail(list,sq,
							COLOUR_IRC,l->network,c,
							buffer,"\r\n");
				} else if(pformat == PFORMAT_TAGONLY) {
					snprintf(buffer,bufsize,"PRIVMSG %s :",cname);
					packlist_xdl_tail(list,sq,
							COLOUR_IRC,l->network,c,
							buffer,"\r\n");
				}
			}
		}
		if(pformat == PFORMAT_SUMMARY) {
			LOCK(l->network);
			msgs = xdcc_info_msgs(names,no_names,&j,
					l->network->settings->info_line);
			UNLOCK(l->network);
			strarr_free(&names,&no_names);
			for(i = 0; i < j; ++i) {
				snprintf(buffer,bufsize,
						"PRIVMSG %s :%s\r\n",
						cname,msgs[i]);
				xfree(msgs[i]);
				pqueue_push_back(sq,xstrdup(buffer));
			}
			if(msgs != NULL)
				xfree(msgs);
		}
	}
	xfree(lists);
}

static void determine_ip(netlocal_t *l) {
	struct sockaddr_in addr;
	socklen_t len = sizeof(addr);
	char buffer[64];

	if(l->socket != -1) {
		getsockname(l->socket,(struct sockaddr *)&addr,&len);
		inet_ntop(AF_INET,&(addr.sin_addr),buffer,sizeof(buffer));
		LOGTP(L_INF,"Advertising address \"%s\" (failback)",buffer);
		LOCK(l->info);
		l->info->self_ip = ntohl(addr.sin_addr.s_addr);
		UNLOCK(l->info);
		l->on_failback_ip = 1;
	}
}	

static void main_loop(netlocal_t *l) {
#define MAX_LOOP_PER_SEC 10
#define SIG 0
#define SOK 1
	char 		buffer[512];
	int		timeout;
	struct pollfd	fds[2];
	int		irc_timeout = 180, msg_limit = 100;
	int		ret;
	time_t		last_plists, last_nick_check, last_sys_check;
	time_t		last_ping, last_channel_check, last_msg_limit_fill;
	time_t		tmp, now = xtime();
	antispin_t	as;

	antispin_init(&as);
	
	last_plists		= now;
	last_nick_check		= now;
	last_sys_check		= now;
	last_ping		= now;
	last_channel_check	= now;
	last_msg_limit_fill	= 0;
	
	fds[SIG].fd	= l->signal_fd;
	fds[SIG].events	= POLLIN;
	fds[SOK].fd	= l->socket;
	fds[SOK].events	= POLLIN;
	
	while(l->socket != -1 && (!thread_should_end())) {
		antispin_test_and_sleep(&as,MAX_LOOP_PER_SEC);
		
		/* Message Processing */

		/* trust no one! */
		fds[SIG].revents = 0; 
		fds[SOK].revents = 0;
		
		/* configure message limit */
		if(test_update_timer(0,&last_msg_limit_fill,1)) {
			LOCK(l->network);
			msg_limit = l->network->settings->recv_per_sec;
			UNLOCK(l->network);
		}
		/* configure timeout */
		LOCK(l->info);
		if((pqueue_length(&l->info->p_squeue)
				+ pqueue_length(&l->info->j_squeue)
				+ pqueue_length(&l->info->d_squeue)
				+ pqueue_length(&l->info->r_squeue)) > 0
				|| linebuf_inuse(l->snd_buf)) {
			timeout	= 1000;
		} else {
			timeout	= 10000;
		}
		UNLOCK(l->info);
	
		if(msg_limit > 0) {
			fds[SOK].events = POLLIN;
		} else {
			timeout = 1000;
			fds[SOK].events = 0;
		}
		
		poll(fds,2,timeout);
		
		/* clear signal pipe */
		if(fds[SIG].revents & POLLIN) {
			do {
				ret = read(l->signal_fd,buffer,1);
			} while(ret > 0);
		}
		
		/* If a bit other than POLLIN was set then it's an error. */
		if(fds[SOK].revents & ~POLLIN) {
			char errbuf[96];

			if(fds[SOK].revents & POLLERR) {
				socklen_t len;
				int val;
				len = sizeof(val);
				ret = getsockopt(l->socket,SOL_SOCKET,SO_ERROR,
						&val,&len);
				lstrerror_r(ret != -1 ? val : errno,
						errbuf,sizeof(errbuf));
			} else {
				lstrerror_r(ECONNRESET,errbuf,sizeof(errbuf));
			}
			
			LOGTP(L_ERR,"Got Error: %s",errbuf);
			disconnect_server(l,NULL);
			break;
		}
		
		/* check for pending messages */
		if(fds[SOK].revents & POLLIN) {
			ret = recv_msgs(l,msg_limit);
			if(ret > 0) {
				msg_limit -= ret;
				if(process_msgs(l) == -1)
					break; // link failure
			} else if(ret == -1) {
				disconnect_server(l,NULL);
				break;
			}
		}
		
		/* send outgoing messages */
		if(send_msgs(l) == -1) { // link failure
			disconnect_server(l,NULL);
			break;
		}

		
		/* Periodic Checks */

		now = xtime();
	
		if((now - l->last_join) > 60 && 
			test_timer(now,last_channel_check,60)) {
			check_channels(l);
			last_channel_check = now;
		}
	
		LOCK(l->network);
		irc_timeout 	= l->network->settings->irc_timeout;
		UNLOCK(l->network);
		
		/* work out if we have pinged out without knowing it */
		tmp = l->last_message;
		if(abs(now - tmp) > irc_timeout) {
			LOGTP(L_ERR,"No messages for %d Seconds, Closing Link",now - tmp);
			disconnect_server(l,"Ping Timeout");
			break;
		} else if(abs(now - tmp) > (irc_timeout - 30) &&
				abs(now - last_ping) > 30) {
			irc_send_raw(l->network,"PING searching_for_life");
			last_ping = now;
		}
	
		if(test_update_timer(now,&last_plists,60))
			channel_plists(l,buffer,sizeof(buffer));
		
		if(test_update_timer(now,&last_nick_check,60)) {
			LOCK(l->network);
			LOCK(l->info);
			if(irc_strcasecmp(l->info->nick,
					l->network->settings->nick) != 0) {
				//LOGTP(L_INF,"Attempting to change nick to: %s",
				//		l->network->settings->nick);
				snprintf(buffer,sizeof(buffer),"NICK %s",
						l->network->settings->nick);
				enqueue_msg(l->info,buffer,EM_DUP);
			}
			UNLOCK(l->info);
			UNLOCK(l->network);
		}
		
		if(test_update_timer(now,&last_sys_check,300)) {
			LOCK(l->info);
			if(l->info->self_ip == 0 || l->on_failback_ip)
				enqueue_msg(l->info,"USERHOST $BOT",EM_DUP);
			UNLOCK(l->info);
		}
	}
#undef SIG
#undef SOK
#undef MAX_LOOP_PER_SEC
}

/* EXPORTED Functions */

void *irc_thread(void *arg) {
	netlocal_t	local;
	netlocal_t 	*l;
	time_t		last_loop = 0;
	int 		ret,sleepfor;
	
	thread_signal_started(&(((network_t *)arg)->thread));
	
	irc_init(&local,(network_t *)arg);
	l = &local;

	LOCK(l->network);
	LOG_TITLE("Network %s",l->network->name);
	UNLOCK(l->network);
	
	LOGTP(L_INF,"Started");
	
	while(!thread_should_end()) {
		LOCK(l->network);
		if((xtime() - last_loop) >= l->network->settings->reconnect_delay)
			sleepfor = 0;
		else
			sleepfor = (l->network->settings->reconnect_delay) - 
				(xtime() - last_loop);
		UNLOCK(l->network);
		
		if(sleepfor != 0) {
			if(thread_sleep(sleepfor) != 0) {
				if(thread_should_end())
					break;
			}
		}

		last_loop = xtime();
		
		pick_server(l);
		ret = connect_server(l);
		if(ret == 0) {
			ret = login_server(l);
			if(ret == -1)
				disconnect_server(l,NULL);
		}
		if(ret != -1)
			main_loop(l);
		
		/*
		LOCK(l->info);
		D("%d,%d,%d,%d",
			pqueue_length(&l->info->p_squeue),
			pqueue_length(&l->info->j_squeue),
			pqueue_length(&l->info->d_squeue),
			pqueue_length(&l->info->r_squeue));
		UNLOCK(l->info);
		*/
	}

	/* the end! */
	LOGTP(L_INF,"Shutdown");
		
	if(l->socket != -1)
		disconnect_server(l,ARISA_VERSION);
	irc_free(l);	

	thread_signal_finished();
	
	return NULL;
}

int irc_network_add(network_t *n) {
	int i,ret = -1;
	LOCK(global);
	LOCK(n);
	for(i = 0; i < global->no_networks; ++i) {
		LOCK(global->networks[i]);
		if(!global->networks[i]->deleted &&
			strcmp(global->networks[i]->name,n->name) == 0) {				LOGP(L_ERR,"Error trying to add duplicate network: %s",n->name);
			UNLOCK(global->networks[i]);
			break;
		}
		UNLOCK(global->networks[i]);
	}
	if(i == global->no_networks) {
		/* we completed the scan without break'ing */
		PTRARR_ADD(&global->networks,&global->no_networks,n);
		validity_insert(n);
		LOGP(L_INF,"Added Network %s",n->name);
		ret = 0;
	} // else ret = -1 (already)
	if(ret == 0 && !n->settings->no_autoconnect)
		ret = thread_create(&(n->thread),irc_thread,n);
	UNLOCK(n);
	UNLOCK(global);
	return ret != 0 ? -1 : 0;
}

int irc_network_run(network_t *n) {
	int ret = 0;
	if(n->deleted == 0 && n->state == NETWORK_OFFLINE 
			&& n->info == NULL && !thread_is_running(&(n->thread)))
		ret = thread_create(&(n->thread),irc_thread,n);
	return ret != 0 ? -1 : 0;
}

thread_t *irc_network_stop(network_t *n) {
	char buffer[64];
	thread_t *ret = &n->thread;
	thread_end(ret);
	snprintf(buffer,sizeof(buffer),"QUIT :%s",ARISA_VERSION);
	enqueue_msg(n->info,buffer,EM_LOCK | EM_DUP);
	return ret;
}		

static int purge_network_ref(network_t *n, const time_t now) {
	interface_t *intf;
	int i,j,k,ref = 0;
	
	LOCK(global);
	for(i = 0; i < global->no_interfaces; ++i) {
		intf = global->interfaces[i];
		LOCK(intf);
		if(intf->type == INTERFACE_SEND) {
			for(j = 0; j < intf->send.no_pools; ++j) {
				LOCK(intf->send.pools[j]);
				for(k = 0; k < intf->send.pools[j]->no_csends; ++k) {
					LOCK(intf->send.pools[j]->csends[k]);
					if(intf->send.pools[j]->csends[k]->network == n) {
						intf->send.pools[j]->csends[k]->state = STATE_DELETED;
						ref++;
					}
					UNLOCK(intf->send.pools[j]->csends[k]);
				}
				UNLOCK(intf->send.pools[j]);
			}
		} else if(intf->type == INTERFACE_RECV) {
			for(j = 0; j < intf->recv.no_uploads; ++j) {
				LOCK(intf->recv.uploads[j]);
				if(intf->recv.uploads[j]->network == n) {
					intf->recv.uploads[j]->state = STATE_DELETED;
					ref++;
				}
				UNLOCK(intf->recv.uploads[j]);
			}
		} else if(intf->type == INTERFACE_CHAT) {
			for(j = 0; j < intf->chat.no_chats; ++j) {
				LOCK(intf->chat.chats[j]);
				if(intf->chat.chats[j]->network == n) {
					intf->chat.chats[j]->state = STATE_DELETED;
					ref++;
				}
				UNLOCK(intf->chat.chats[j]);
			}
		}
		UNLOCK(intf);
	}
	for(i = 0; i < global->no_queues; ++i)
		queue_remove(global->queues[i],NULL,n,NULL,NULL,NULL,NULL);
	for(i = 0; i < global->no_admins; ++i) {
		LOCK(global->admins[i]);
		if(global->admins[i]->network == n)
			global->admins[i]->network = NULL;
		UNLOCK(global->admins[i]);
	}
	UNLOCK(global);

	access_purge_network(n);
	
	return ref;
}
					
int irc_network_del(network_t *n) {
	time_t now = xtime();
	int i;
	
	purge_network_ref(n,now);
	
	LOCK(global);
	LOCK(n);
	if(thread_is_running(&(n->thread)))
		thread_end(&(n->thread));
	n->deleted = now;
	validity_delete(n);
	for(i = 0; i < n->no_channels; ++i) {
		LOCK(n->channels[i]);
		n->channels[i]->deleted = now;
		validity_delete(n->channels[i]);
		UNLOCK(n->channels[i]);
	}
	LOGP(L_INF,"Deleted Network %s",n->name);
	UNLOCK(n);
	UNLOCK(global);

	purge_network_ref(n,now);
	
	return 0;
}

network_t *irc_network_find(const char *name) {
	network_t *ret = NULL;
	int i;
	if(name == NULL)
		return NULL;

	LOCK(global);
	for(i = 0; i < global->no_networks && ret == NULL; ++i) {
		LOCK(global->networks[i]);
		if(!global->networks[i]->deleted &&
			strcmp(global->networks[i]->name,name) == 0) {
			ret = global->networks[i];
			validity_insert(ret);
		}
		UNLOCK(global->networks[i]);
	}
	UNLOCK(global);
	return ret;
}

/* purge previously deleted networks */
void irc_networks_purge(const time_t now) {
	network_t *n;
	int i;
	pqueue_t dq;

	//D("IRC Networks Purge Begins");
	
	pqueue_init(&dq,0);
	
	LOCK(global);
	for(i = 0; i < global->no_networks; ++i) {
		LOCK(global->networks[i]);
		if(IS_PURGE_TIME(global->networks[i]->deleted,now))
			pqueue_push_back(&dq,global->networks[i]);
		UNLOCK(global->networks[i]);
	}
	UNLOCK(global);
	
	if(pqueue_length(&dq) == 0) {
		//D("IRC Networks Purge Ends");
		return;
	}
	for(i = pqueue_length(&dq); i > 0; --i) {
		n = pqueue_pop_front(&dq);
		if(purge_network_ref(n,now) == 0) {
			LOCK(n);
			if(n->info == NULL)
				pqueue_push_back(&dq,n);
			UNLOCK(n);
		}
	}
	
	if(pqueue_length(&dq) != 0) {
		while((n = pqueue_pop_front(&dq)) != NULL) {
			LOCK(global);
			LOCK(n);
			PTRARR_DEL(&global->networks,&global->no_networks,n);
			UNLOCK(global);
			UNLOCK(n);
			thread_join(&(n->thread),NULL);
			free_network(n);
		}
	}

	//D("IRC Networks Purge Ends");
}

int irc_channel_add(network_t *n, channel_t *c) {
	char buffer[512];
	int i,ret = -1;
	
	LOCK(n);
	LOCK(c);
	for(i = 0; i < n->no_channels; ++i) {
		LOCK(n->channels[i]);
		if(!n->channels[i]->deleted &&
			strcasecmp(n->channels[i]->name,c->name) == 0) {
			UNLOCK(n->channels[i]);
			break;
		}
		UNLOCK(n->channels[i]);
	}
	if(i == n->no_channels) {
		PTRARR_ADD(&n->channels,&n->no_channels,c);
		validity_pair_insert(n,c);
		c->joined = 0;
		ret = 0;
		if(c->key == NULL)
			snprintf(buffer,sizeof(buffer)-1,"JOIN %s",c->name);
		else
			snprintf(buffer,sizeof(buffer)-1,"JOIN %s %s",
					c->name,c->key);
		ret = 0;
	}
	UNLOCK(c);
	if(ret == 0 && n->state == NETWORK_CONNECTED) {
		UNLOCK(n);
		irc_send_raw(n,buffer);
	} else 
		UNLOCK(n);
	return ret;
}

int irc_channel_del(network_t *n, channel_t *c) {
	char buffer[512];
	int sendpart = 0;
	LOCK(n);
	LOCK(c);
	if(!c->deleted) {
		c->deleted = xtime();
		validity_delete(c);
		if(c->joined) {
			snprintf(buffer,sizeof(buffer)-1,"PART %s",c->name);
			sendpart = 1;
		}
	}
	UNLOCK(c);
	UNLOCK(n);
	
	access_purge_channel(c);
	
	if(sendpart)
		irc_send_raw(n,buffer); // this will also signal the thread
	
	return 0;
}

channel_t *irc_channel_find(network_t *n, const char *name) {
	channel_t *ret = NULL;
	int i;
	if(name == NULL)
		return NULL;
	LOCK(n);
	for(i = 0; i < n->no_channels && ret == NULL; ++i) {
		LOCK(n->channels[i]);
		if(!n->channels[i]->deleted &&
			strcasecmp(n->channels[i]->name,name) == 0) {
			ret = n->channels[i];
			validity_pair_insert(n,ret);
		}
		UNLOCK(n->channels[i]);
	}
	UNLOCK(n);
	return ret;
}

/* All of these msg sends are quite in-efficient.
 * I doubt it matters too much, but for every irc_send level added at least
 * another 512 will be taken from the heap. So a ctcp call will use about 2k
 * of heap...  Again I don't think it matters. (heap/stack)
 */

int irc_send_raw(network_t *n, const char *msg) {
	int ret = -1;
	if(n != NULL && msg != NULL) {
		LOCK(n);
		ret = enqueue_msg(n->info,msg,EM_LOCK | EM_DUP);
		UNLOCK(n);
	}
	return ret;
}

int irc_send_msg(network_t *n, const char *nick, const char *msg) {
	char buffer[512];
	if(nick != NULL && msg != NULL) {
		snprintf(buffer,sizeof(buffer)-1,"PRIVMSG %s :%s",nick,msg);
		return irc_send_raw(n,buffer);
	}
	return -1;
}

int irc_send_notice(network_t *n, const char *nick, const char *msg) {
	char buffer[512];
	if(nick != NULL && msg != NULL) {
		snprintf(buffer,sizeof(buffer)-1,"NOTICE %s :%s",nick,msg);
		return irc_send_raw(n,buffer);
	}
	return -1;
}

int irc_send_ctcp_msg(network_t *n, const char *nick, const char *msg) {
	char buffer[512];
	if(msg != NULL) {
		snprintf(buffer,sizeof(buffer)-1,"\001%s\001",msg);
		return irc_send_msg(n,nick,buffer);
	}
	return -1;
}

int irc_send_ctcp_notice(network_t *n, const char *nick, const char *msg) {
	char buffer[512];
	if(msg != NULL) {
		snprintf(buffer,sizeof(buffer)-1,"\001%s\001",msg);
		return irc_send_notice(n,nick,buffer);
	}
	return -1;
}

int irc_tolower(int c) {
	if(isalnum(c))
		return tolower(c);
	else {
		switch (c) {
			case '[': c = '{'; break;
			case ']': c = '}'; break;
			case '|': c = '|'; break;
			default:
				  c = tolower(c);
		}
	}
	return c;
}

int irc_strcasecmp(const char *s1, const char *s2) {
	unsigned int s1p,s2p;
	int c1,c2;

	if(s1 == NULL && s2 == NULL)
		return 0;
	else if(s1 == NULL || s2 == NULL)
		return 1;
	
	for(s1p = 0, s2p = 0; 
			s1[s1p] != '\0' && s2[s2p] != '\0'; ++s1p,++s2p) {
		c1 = irc_tolower(s1[s1p]);
		c2 = irc_tolower(s2[s2p]);
		if(c1 == c2)
			continue;
		else if(c1 < c2)
			return -1;
		else
			return 1;
	}
	if(s1[s1p] != s2[s2p]) {
		if(s1[s1p] == '\0')
			return -1;
		else /* s2[s1p] == '\0' */
			return 1;
	}
	return 0;
}

int irc_hostmask_match(const char *host, const char *mask) {
	if(host == NULL || mask == NULL)
		return 0;
	return wild_match_cf(host,mask,irc_tolower);
}

void irc_strip_colours(char *buffer) {
	int i,j;
	for(i = 0, j = 0; buffer[i] != '\0';) {
		if(buffer[i] == '\003') {
			/* colour codes */
			if(isdigit(buffer[i+1])) {
				i += 2;
				if(isdigit(buffer[i]))
					i++;
				if(i == 3 && buffer[i] == ',')
					i++;
				if(i == 4 && isdigit(buffer[i]))
					i++;
				if(i == 5 && isdigit(buffer[i]))
					i++;
			} else
				i++;
		} else if(buffer[i] == '\002' || buffer[i] == '\017' ||
				buffer[i] == '\037')
			/* bold/off/underline */
			++i;
		else
			buffer[j++] = buffer[i++];
	}
	buffer[j] = '\0';
}

int irc_listen(network_t *n, channel_t *c, pqueue_t *msgq) {
	int tmp,ret = -1;
	if(n != NULL && c == NULL) {
		LOCK(n);
		tmp = n->no_listeners;
		if(n->deleted == 0)
			PTRARR_ADD(&n->listeners,&n->no_listeners,msgq);
		if(tmp != n->no_listeners)
			ret = 0;
		UNLOCK(n);
	} else if(c != NULL) {
		LOCK(c);
		tmp = c->no_listeners;
		if(c->deleted == 0)
			PTRARR_ADD(&c->listeners,&c->no_listeners,msgq);
		if(tmp != c->no_listeners)
			ret = 0;
		UNLOCK(c);
	}
	return ret;
}

int irc_unlisten(network_t *n, channel_t *c, pqueue_t *msgq) {
	int i,j;
	
	if(n == NULL && c == NULL) {
		LOCK(global);
		for(i = 0; i < global->no_networks; ++i) {
			LOCK(global->networks[i]);
			PTRARR_DEL(&global->networks[i]->listeners,
					&global->networks[i]->no_listeners,
					msgq);
			for(j = 0; j < global->networks[i]->no_channels; ++j) {
				LOCK(global->networks[i]->channels[j]);
				PTRARR_DEL(&global->networks[i]->channels[j]->listeners,
					&global->networks[i]->channels[j]->no_listeners,
					msgq);
				UNLOCK(global->networks[i]->channels[j]);
			}
			UNLOCK(global->networks[i]);
		}
		UNLOCK(global);
	} else if(n != NULL) {
		LOCK(n);
		PTRARR_DEL(&n->listeners,&n->no_listeners,msgq);
		for(i = 0; i < n->no_channels; ++i) {
			LOCK(n->channels[i]);
			PTRARR_DEL(&n->channels[i],&n->no_channels,msgq);
			UNLOCK(n->channels[i]);
		}
		UNLOCK(n);
	} else if(c != NULL) {
		LOCK(c);
		PTRARR_DEL(&c->listeners,&c->no_listeners,msgq);
		UNLOCK(c);
	}
	return 0; // alway succeed... we guess
}

int irc_network_valid(network_t *n) {
	int i,ret = 0;
	
	if(n == NULL)
		return 0;

	if(validity_test(n))
		return 1;

	LOCK(global);
	for(i = 0; i < global->no_networks; ++i) {
		if(global->networks[i] == n)
			break;
	}
	if(i < global->no_networks) { // network is in array
		LOCK(n);
		if(n->deleted == 0)
			ret = 1;
		UNLOCK(n);
	}
	UNLOCK(global);

	return ret;
}

int irc_network_rename(network_t *n, const char *name) {
	int i,ret = 0;
	
	if(n == NULL || name == NULL)
		return -1;
	
	LOCK(global);
	for(i = 0; i < global->no_networks && ret == 0; ++i) {
		LOCK(global->networks[i]);
		if(global->networks[i]->deleted == 0 &&
				strcmp(global->networks[i]->name,name) == 0)
			ret = -1;
		UNLOCK(global->networks[i]);
	}
	if(ret == 0) {
		LOCK(n);
		if(n->name != NULL)
			xfree(n->name);
		n->name = xstrdup(name);
		UNLOCK(n);
	}
	UNLOCK(global);

	return ret;
}

int irc_channel_valid(network_t *n, channel_t *c) {
	int i;

	if(n == NULL || c == NULL)
		return 0;

	if(validity_pair_test(n,c))
		return 1;

	if(irc_network_valid(n)) {
		LOCK(n);
		if(n->deleted != 0) {
			UNLOCK(n);
			return 0;
		}
		for(i = 0; i < n->no_channels; ++i) {
			if(n->channels[i] == c) {
				LOCK(c);
				i = c->deleted;
				if(c->deleted == 0)
					validity_pair_insert(n,c);
				UNLOCK(c);
				UNLOCK(n);
				if(i == 0)
					return 1;
				else
					return 0;
			}
		}
		UNLOCK(n);
	}
	
	return 0;
}

int irc_channel_rename(network_t *n, channel_t *c, const char *name) {
	int i,j, ret = 0;

	if(c == NULL || name == NULL)
		return -1;

	if(n == NULL) {
		LOCK(global);
		for(i = 0; i < global->no_networks && n == NULL; ++i) {
			LOCK(global->networks[i]);
			if(global->networks[i]->deleted == 0) {
				for(j = 0; j < global->networks[i]->no_channels && n == NULL; ++j) {
					if(global->networks[i]->channels[j] == c)
						n = global->networks[i];
				}
			}
			UNLOCK(global->networks[i]);
		}
		if(n != NULL)
			LOCK(n);
		UNLOCK(global);
		if(n == NULL)
			return -1;
	} else
		LOCK(n);

	if(n->deleted != 0) {
		UNLOCK(n);
		return -1;
	}

	for(i = 0; i < n->no_channels && ret == 0; ++i) {
		LOCK(n->channels[i]);
		if(n->channels[i]->deleted == 0 &&
				irc_strcasecmp(n->channels[i]->name,name) == 0)
			ret = -1;
		UNLOCK(n->channels[i]);
	}
	if(ret == 0) {
		char buffer[512],key[512];
		LOCK(c);
		if(c->name != NULL) {
			if(c->joined != 0)
				snprintf(buffer,sizeof(buffer),
						"PART %s",c->name);
			else
				buffer[0] = '\0';
			xfree(c->name);
		} else
			buffer[0] = '\0';
		c->joined = 0;
		c->name = xstrdup(name);
		if(c->key != NULL)
			xstrncpy(key,c->key,sizeof(key));
		else
			key[0] = '\0';
		UNLOCK(c);

		LOCK(n->info);
		if(n->state == NETWORK_CONNECTED) {
			if(buffer[0] != '\0')
				enqueue_msg(n->info,buffer,EM_DUP);
			
			if(key[0] != '\0')
				snprintf(buffer,sizeof(buffer),
						"JOIN %s %s",name,key);
			else
				snprintf(buffer,sizeof(buffer),"JOIN %s",name);
			enqueue_msg(n->info,buffer,EM_DUP);
		}
		UNLOCK(n->info);
	}
	UNLOCK(n);

	return ret;
}

/** Settings Data **/
static setting_t netset_data[] = {
	{"nick",		ST_STR,	OFFSETOF(netset_t,nick),
		0,NULL,NULL,NULL,NULL},
	{"loginname",		ST_STR, OFFSETOF(netset_t,loginname),
		0,NULL,NULL,NULL,NULL},
	{"realname",		ST_STR,	OFFSETOF(netset_t,realname),	
		0,NULL,NULL,NULL,NULL},
	{"message-rate",	ST_FLOAT,OFFSETOF(netset_t,message_rate),
		0,"0.1",NULL,NULL,NULL},
	{"message-rate-max",	ST_INT, OFFSETOF(netset_t,message_rate_max),
		0,"1",NULL,NULL,NULL},
	{"recv-per-sec",	ST_INT,	OFFSETOF(netset_t,recv_per_sec),
		0,"1",NULL,NULL,NULL},
	{"average-window",	ST_INT,	OFFSETOF(netset_t,average_window),
		0,"1",NULL,NULL,NULL},
	{"max-target",		ST_INT, OFFSETOF(netset_t,max_target),
		0,"1","5",NULL,NULL},
	{"irc-timeout",		ST_INT,	OFFSETOF(netset_t,irc_timeout),
		0,"0",NULL,NULL,NULL},
	{"on-connect-delay",	ST_INT,	OFFSETOF(netset_t,on_connect_delay),
		0,"0",NULL,NULL,NULL},
	{"join-delay",		ST_INT, OFFSETOF(netset_t,join_delay),
		0,"0",NULL,NULL,NULL},
	{"reconnect-delay",	ST_INT, OFFSETOF(netset_t,reconnect_delay),
		0,"0",NULL,NULL,NULL},
	{"no-autoconnect",	ST_BOOL,OFFSETOF(netset_t,no_autoconnect),
		VALUE_BOOL_ONOFF,NULL,NULL,NULL,NULL},
	{"nick-tracking",	ST_BOOL, OFFSETOF(netset_t,nick_tracking),
		VALUE_BOOL_ONOFF,NULL,NULL,NULL,NULL},
	{"bind-ip",		ST_NSTR,OFFSETOF(netset_t,bind_ip),
		0,NULL,NULL,NULL,NULL},
	{"info-line",		ST_NSTR,OFFSETOF(netset_t,info_line),
		0,NULL,NULL,NULL,NULL},
	{NULL}
};

static setting_t channel_data[] = {
	{"key",			ST_NSTR,	OFFSETOF(channel_t,key),
		0,NULL,NULL,NULL,NULL},
	{"plist",		ST_INT,		OFFSETOF(channel_t,plist),
		0,"0",NULL,NULL},
	{"track-key",		ST_BOOL,	OFFSETOF(channel_t,track_key),
		0,NULL,NULL,NULL,NULL},
	{"pformat",		ST_ENUM,	OFFSETOF(channel_t,pformat),
		0,NULL,NULL,pformat_to_str,str_to_pformat},
	{NULL}
};

int netset_apply(netset_t *ns, const char *name, const char *value) {
	value_t *v = value_string(name,value);
	int ret = apply_setting(netset_data,ns,v);
	xfree(v);
	return ret;
}

int netset_read(netset_t *ns, pqueue_t *out) {
	return read_settings(netset_data,ns,out);
}

int network_apply_setting(network_t *n, const char *name, const char *value) {
	int ret;
	LOCK(n);
	ret = netset_apply(n->settings,name,value);
	if(strncasecmp(name,"ni",2) == 0 && strlen(name) <= 4) {
		if(value != NULL && n->info != NULL) {
			char buffer[128];
			snprintf(buffer,sizeof(buffer),"NICK %s",value);
			enqueue_msg(n->info,buffer,EM_LOCK | EM_DUP);
		}
	}
	UNLOCK(n);
	return ret;
}

int network_read_settings(network_t *n, pqueue_t *out) {
	int ret;
	LOCK(n);
	ret = netset_read(n->settings,out);
	UNLOCK(n);
	return ret;
}

int channel_apply_setting(channel_t *c, const char *name, const char *value) {
	value_t *v = value_string(name,value);
	int ret;
	LOCK(c);
	ret = apply_setting(channel_data,c,v);
	UNLOCK(c);
	xfree(v);
	return ret;
}

int channel_read_settings(channel_t *c, pqueue_t *out) {
	int ret;
	LOCK(c);
	ret = read_settings(channel_data,c,out);
	UNLOCK(c);
	return ret;
}
