import string, re, socket, sys, time, socket
from ircmultiplexer import RemoveStream
from errno import EAGAIN

LostConnectionError = Exception('LostConnectionError')

class msghandler:
	def __init__(self, msgname, tokenizer):
		self.msgname = msgname
		self.tokenizer = tokenizer
	def __call__(self, data):
		return self.tokenizer(data)

class re_tokens:
	def __init__(self, regexp):
		self.matcher = re.compile(regexp)
	def __call__(self, line):
		return self.matcher.match(line).groups()
	
def splitparams(line,
			splitter = re.compile(
			r"(?P<middle>( [^\000 :][^\000 ]*){0,14})(?P<trailing> :?[^\000]*)?")):
	params = splitter.match(line)
	if params.group('middle'):
		middle = string.split(params.group('middle')[1:], ' ')
	else:
		middle = []
	if params.group('trailing'):
		trailing = params.group('trailing')[1:]
		if trailing[0] == ':':
			trailing = trailing[1:]
		middle.append(trailing)
	return middle

# The reason for the following symbols is the protocol varying in which
# letters signify what state.

# Users
away = 'away'
invisible = 'invisible'
local_oper = 'local_oper'
op = 'op'     # Channel operator
oper = 'oper' # IRC operator
restricted = 'restricted'
serv_note = 'serv_note'
voice = 'voice'
wallops = 'wallops'

# For parsing mode lines
UMODES = {
	'a': away,
	'i': invisible,
	'w': wallops,
	'r': restricted,
	'o': oper,
	'O': local_oper,
	's': serv_note
}

# Channels
anonymous = 'anonymous'
invite = 'invite'
moderated = 'moderated'
nomsg = 'nomsg'
quiet = 'quiet'
private = 'private'
public = 'public'
server_reop = 'server_reop'
topic = 'topic'
key = 'key'
limit = 'limit'
secret = 'secret'

# For parsing mode line
CHANNELMODES = {
	'a': anonymous,
	'i': invite,
	'm': moderated,
	'n': nomsg,
	'q': quiet,
	'p': private,
	's': secret,
	'r': server_reop,
	't': topic,
	'k': key,
	'l': limit,

	'o': op,   # I hope these doesn't cause collisions...
	'v': voice
}

CHANNELMODES_wParams = (key, limit, op, voice)

# ident...
ident = 'ident'
other_ident = 'other_ident'
no_ident = 'noident'
i_line = 'i_line'
I_line = 'I_line'

ident_types = {
	'': (ident, I_line),
	'^': (other_ident, I_line),
	'~': (no_ident, I_line),
	'+': (ident, i_line),
	'=': (other_ident, i_line),
	'-': (no_ident, i_line)
}

# Having 160 brittle functions is better than one robust. :]

def RPL_WELCOME(line, builder =
		re_tokens(" :Welcome to the Internet Relay Network (?P<nick>.*)")):
	tmp = builder(line)
	return prefixsplit(tmp[0])
def RPL_YOURHOST(line, builder =
		re_tokens(" :Your host is (?P<servername>.*?), running version (?P<ver>.*)")):
	return builder(line)
def RPL_CREATED(line, builder =
		re_tokens(" :This server was created (?P<date>.*)")):
	return builder(line)
def RPL_MYINFO(line):
	#servername, version, available user modes, available channel modes
	return splitparams(line)
def RPL_BOUNCE(line, builder =
		re_tokens(" :Try server (?P<server_name>.*?), port (?P<port_number>.*)")):
	return builder(line)
def RPL_USERHOST(line):
	users = string.split(line[2:])
	params = []
	for user in users:
		info, attribs = [], []
		nick, host = string.split(user, '=')
		if nick[-1] == '*':
			info.append(nick[:-1])
			attribs.append(oper)
		else:
			info.append(nick)
		info.append(host[1:])
		if hostname[0] == '-':
			attribs.append(away)
		params.append((info,attribs))
	return params
def RPL_ISON(line):
	return string.split(line[2:])
def RPL_AWAY(line):
	return splitparams(line)
def RPL_UNAWAY(line):
	return splitparams(line)
def RPL_NOWAWAY(line):
	return splitparams(line)
def RPL_WHOISUSER(line):
	tmp = splitparams(line)
	del tmp[3]
	return tmp
def RPL_WHOISSERVER(line):
	return splitparams(line)
def RPL_WHOISOPERATOR(line):
	return splitparams(line)
def RPL_WHOISIDLE(line):
	return splitparams(line)
def RPL_ENDOFWHOIS(line):
	return splitparams(line)
def RPL_WHOISCHANNELS(line):
	params = []
	tmp = splitparams(line)
	params.append(tmp[0])
	for channel in string.split(params[-1][1:]):
		if channel[0] == '@':
			params.append((channel[1:],op))
		elif channel[0] == '+':
			params.append((channel[1:],voice))
		else:
			params.append((channel,))
	return params
def RPL_WHOWASUSER(line):
	tmp = splitparams(line)
	del tmp[3]
	return tmp
def RPL_ENDOFWHOWAS(line):
	return splitparams(line)
def RPL_LISTSTART(line):
	# RPL_LISTSTART is obsolete
	return splitparams(line)
def RPL_LIST(list):
	return splitparams(line)
def RPL_LISTEND(list):
	return splitparams(line)
def RPL_UNIQOPIS(line):
	return splitparams(line)
def RPL_CHANNELMODEIS(line):
	tmp = splitparams(line)
	modes = []
	mode_params = tmp[2:]
	for char in tmp[1]:
		flag = CHANNELMODES.get(char, None)
		if flag and ( flag in CHANNELMODES_wParams ):
			modes.append((flag, mode_params[0]))
			del mode_params[0]
		elif flag:
			modes.append((flag,))
	return tmp[:1] + modes
def RPL_NOTOPIC(line):
	return splitparams(line)
def RPL_TOPIC(line):
	return splitparams(line)
def RPL_INVITING(line):
	return splitparams(line)
def RPL_SUMMONING(line):
	return splitparams(line)
def RPL_INVITELIST(line):
	return splitparams(line)
def RPL_ENDOFINVITELIST(line):
	return splitparams(line)
def RPL_EXCEPTLIST(line):
	return splitparams(line)
def RPL_ENDOFEXCEPTLIST(line):
   return splitparams(line)
def RPL_VERSION(line):
	tmp = splitparams(line)
	tmpv = string.split(tmp[0], '.', 1)
	if len(tmpv) == 1:
		tmpv.append('0')
	tmp[:1] = tmpv
	return tmp
def RPL_WHOREPLY(line):
	tmp = splitparams(line)
	attrstr = tmp[-2]
	attribs = []
	for char in attrstr:
		if char == 'G':
			attribs.append(away)
		elif char == '*':
			attribs.append(oper)
		elif char == '@':
			attribs.append(op)
		elif char == '+':
			attribs.append(voice)
	tmp[-2] = attribs
	tmp[-1:] = string.split(tmp[-1], ' ', 1)
	return tmp
def RPL_ENDOFWHO(line):
	return splitparams(line)
def RPL_NAMREPLY(line):
	tmp = splitparams(line)
	tmp[0] = {'@': secret, '*': private, '=': public}[tmp[0]]
	trailing = tmp[-1]
	del tmp[-1]
	for nick in string.split(trailing[1:]):
		if nick[0] == '@':
			tmp.append((nick[1:],op))
		elif nick[0] == '+':
			tmp.append((nick[1:],voice))
		else:
			tmp.append((nick,))
	return tmp
def RPL_ENDOFNAMES(line):
	return splitparams(line)
def RPL_LINKS(line):
	tmp = splitparams(line)
	tmp[-1:] = string.split(tmp[-1], ' ', 1)
	return tmp
def RPL_ENDOFLINKS(line):
	return splitparams(line)
def RPL_BANLIST(line):
	return splitparams(line)
def RPL_ENDOFBANLIST(line):
	return splitparams(line)
def RPL_INFO(line):
	return [line[2:]]
def RPL_ENDOFINFO(line):
	return [line[2:]]
def RPL_MOTDSTART(line, builder =
		re_tokens(" :- (?P<server>.*?) Message of the Day - ")):
	return builder(line)
def RPL_MOTD(line):
	return [line[4:]]
def RPL_ENDOFMOTD(line):
	return [line[2:]]
def RPL_YOUREOPER(line):
	return [line[2:]]
def RPL_REHASHING(line):
	return splitparams(line)
def RPL_YOURESERVICE(line):
	return [string.split(line)[-1]]
def RPL_TIME(line):
	return splitparams(line)
def RPL_USERSSTART(line):
	return [string.split(line[2:])]
def RPL_USERS(line):
	return [string.split(line[2:])]
def RPL_ENDOFUSERS(line):
	return [line[2:]]
def RPL_NOUSERS(line):
	return [line[2:]]
def RPL_TRACELINK(line):
	tmp = string.split(line[1:])
	del tmp[0]
	tmp[3] = tmp[3][1:]
	return tmp
def RPL_TRACECONNECTING(line):
	return string.split(line)[-2:]
def RPL_TRACEHANDSHAKE(line):
	return string.split(line)[-2:]
def RPL_TRACEUNKNOWN(line):
	return string.split(line)[-2:]
def RPL_TRACEOPERATOR(line):
	return string.split(line)[-2:]
def RPL_TRACEUSER(line):
	return string.split(line)[-2:]
def RPL_TRACESERVER(line, builder =
		re_tokens(" Serv (?P<class>.*?) (?P<sint>.*?)S (?P<cint>.*?)C (?P<nick>.*?)!(?P<user>.*?)@(?P<hostserver>.*) V(?P<protocolversion>.*)")):
	return builder(line)
def RPL_TRACESERVICE(line):
	return string.split(line)[-4]
def RPL_TRACENEWTYPE(line):
	tmp = string.split(line)
	del tmp[1]
	return tmp
def RPL_TRACECLASS(line):
	return string.split(line)[-2]
def RPL_TRACERECONNECT(line):
	# Unused
	return splitparams(line)
def RPL_TRACELOG(line):
	return string.split(line)[-2]
def RPL_TRACEEND(line):
	return splitparams(line)
def RPL_STATSLINKINFO(line):
	return splitparams(line)
def RPL_STATSCOMMANDS(line):
	return splitparams(line)
def RPL_ENDOFSTATS(line):
	return splitparams(line)
def RPL_STATSUPTIME(line, builder =
		re_tokens(" :Server Up (?P<days>.*?) days (?P<h>..):(?P<m>..):(?P<s>..)")):
	return builder(line)
def RPL_STATSOLINE(line, builder =
		re_tokens("O (?P<hostmask>.*?) \\* (?P<name>.*)")):
	return builder(line)
def RPL_UMODEIS(line):
	params = []
	for char in line[1:]:
		attrib = UMODES.get(char, None)
		if attrib:
			params.append(attrib)
	return params
def RPL_SERVLIST(line):
	return string.split(line)
def RPL_SERVLISTEND(line):
	return splitparams(line)
def RPL_LUSERCLIENT(line, builder =
		re_tokens(r" :There are (?P<users>\d+) users and (?P<servc>\d+) services on (?P<serv>\d+) servers")):
	return builder(line)
def RPL_LUSEROP(line):
	return splitparams(line)
def RPL_LUSERUNKNOWN(line):
	return splitparams(line)
def RPL_LUSERCHANNELS(line):
	return splitparams(line)
def RPL_LUSERME(line, builder =
		re_tokens(r" :I have (?P<cln>\d+) clients, (?P<servc>\d+) services and (?P<serv>\d+) servers")):
	return builder(line)
def RPL_ADMINME(line):
	return splitparams(line)
def RPL_ADMINLOC1(line):
	return [line[2:]]
def RPL_ADMINLOC2(line):
	return [line[2:]]
def RPL_ADMINEMAIL(line):
	return [line[2:]]
def RPL_TRYAGAIN(line):
	return splitparams(line)
def ERR_NOSUCHNICK(line):
	return splitparams(line)
def ERR_NOSUCHSERVER(line):
	return splitparams(line)
def ERR_NOSUCHCHANNEL(line):
	return splitparams(line)
def ERR_CANNOTSENDTOCHAN(line):
	return splitparams(line)
def ERR_TOOMANYCHANNELS(line):
	return splitparams(line)
def ERR_WASNOSUCHNICK(line):
	return splitparams(line)
def ERR_TOOMANYTARGETS(line, builder =
		re_tokens(r"(?P<errorcode>.*?) recipients\. (?P<abortmsg>.*)")):
	tmp = splitparams(line)
	return tmp[:1] + builder(tmp[-1])
def ERR_NOSUCHSERVICE(line):
	return splitparams(line)
def ERR_NOORIGIN(line):
	return [line[2:]]
def ERR_NORECIPIENT(line, builder =
		re_tokens(r" :No recipient given \((?P<command>.*)\)")):
	return builder(line)
def ERR_NOTEXTTOSEND(line):
	return [line[2:]]
def ERR_NOTOPLEVEL(line):
	return splitparams(line)
def ERR_WILDTOPLEVEL(line):
	return splitparams(line)
def ERR_BADMASK(line):
	return splitparams(line)
def ERR_UNKNOWNCOMMAND(line):
	return splitparams(line)
def ERR_NOMOTD(line):
	return [line[2:]]
def ERR_NOADMININFO(line):
	return splitparams(line)
def ERR_FILEERROR(line, builder =
		re_tokens(" :File error doing (?P<fileop>.*?) on (?P<file>.*)")):
	return builder(line)
def ERR_NONICKNAMEGIVEN(line):
	return [line[2:]]
def ERR_ERRONEUSNICKNAME(line):
	return splitparams(line)
def ERR_NICKNAMEINUSE(line):
	return splitparams(line)
def ERR_NICKCOLLISION(line, builder =
		re_tokens("Nickname collision KILL from (?P<user>.*?)@(?P<host>.*)")):
	tmp = splitparams(line)
	return tmp[:1] + builder(tmp[-1])
def ERR_UNAVAILRESOURCE(line):
	return splitparams(line)
def ERR_USERNOTINCHANNEL(line):
	return splitparams(line)
def ERR_NOTONCHANNEL(line):
	return splitparams(line)
def ERR_USERONCHANNEL(line):
	return splitparams(line)
def ERR_NOLOGIN(line):
	return splitparams(line)
def ERR_SUMMONDISABLED(line):
	return [line[2:]]
def ERR_USERSDISABLED(line):
	return [line[2:]]
def ERR_NOTREGISTERED(line):
	return [line[2:]]
def ERR_NEEDMOREPARAMS(line):
	return splitparams(line)
def ERR_ALREADYREGISTRED(line):
	return [line[2:]]
def ERR_NOPERMFORHOST(line):
	return [line[2:]]
def ERR_PASSWDMISMATCH(line):
	return [line[2:]]
def ERR_YOUREBANNEDCREEP(line):
	return [line[2:]]
def ERR_YOUWILLBEBANNED(line):
	# Doesn't give any useful info as far as I know, but puts splitparams
	# as OK dummy
	splitparams(line)
def ERR_KEYSET(line):
	return splitparams(line)
def ERR_CHANNELISFULL(line):
	return splitparams(line)
def ERR_UNKNOWNMODE(line, builder =
		re_tokens("is unknown mode char to me for (?P<channel> .*)")):
	tmp = splitparams(line)
	return tmp[:1] + builder(tmp[-1])
def ERR_INVITEONLYCHAN(line):
	return splitparams(line)
def ERR_BANNEDFROMCHAN(line):
	return splitparams(line)
def ERR_BADCHANNELKEY(line):
	return splitparams(line)
def ERR_BADCHANMASK(line):
	return splitparams(line)
def ERR_NOCHANMODES(line):
	return splitparams(line)
def ERR_BANLISTFULL(line):
	return splitparams(line)
def ERR_NOPRIVILEGES(line):
	return [line[2:]]
def ERR_CHANOPRIVSNEEDED(line):
	return splitparams(line)
def ERR_CANTKILLSERVER(line):
	return [line[2:]]
def ERR_RESTRICTED(line):
	return [line[2:]]
def ERR_UNIQOPPRIVSNEEDED(line):
	return [line[2:]]
def ERR_NOOPERHOST(line):
	return [line[2:]]
def ERR_UMODEUNKNOWNFLAG(line):
	return [line[2:]]
def ERR_USERSDONTMATCH(line):
	return [line[2:]]

def reserved_num(line):
	return splitparams(line)
RPL_SERVICEINFO = reserved_num
RPL_ENDOFSERVICES = reserved_num
RPL_SERVICE = reserved_num
RPL_NONE = reserved_num
RPL_WHOISCHANOP = reserved_num
RPL_KILLDONE = reserved_num
RPL_CLOSING = reserved_num
RPL_CLOSEEND = reserved_num
RPL_INFOSTART = reserved_num
RPL_MYPORTIS = reserved_num
RPL_STATSCLINE = reserved_num
RPL_STATSNLINE = reserved_num
RPL_STATSILINE = reserved_num
RPL_STATSKLINE = reserved_num
RPL_STATSQLINE = reserved_num
RPL_STATSYLINE = reserved_num
RPL_STATSVLINE = reserved_num
RPL_STATSLLINE = reserved_num
RPL_STATSHLINE = reserved_num
RPL_STATSPING = reserved_num
RPL_STATSBLINE = reserved_num
RPL_STATSDLINE = reserved_num
ERR_NOSERVICEHOST = reserved_num

def NICK(line):
	# Just strip away the leading space...
	return [line[1:]]
def MODE(line):
	# This one assumes pretty well formed mode messages, as it only is meant
	# for parsing output from a server, this is not a too absurd assumption.
	params = splitparams(line)
	chewparams = [params[0]]
	sign = '+'
	for mode in params[1]:
		if mode in ('+', '-'):
			sign = mode
		else:
			mode = CHANNELMODES.get(mode, None)
			if mode:
				if mode in CHANNELMODES_wParams:
					chewparams.append((sign, mode, params[2]))
					del params[2]
				else:
					chewparams.append((sign, mode))
	return chewparams
def QUIT(line):
	return [line[2:]]
def JOIN(line):
	return [line[2:]]
def PART(line):
	return splitparams(line)
def TOPIC(line):
	return splitparams(line)
def INVITE(line):
	return splitparams(line)
def KICK(line):
	return splitparams(line)
def PRIVMSG(line):
	return splitparams(line)
def NOTICE(line):
	return splitparams(line)
def PING(line):
	return splitparams(line)
def PONG(line):
	return splitparams(line)
def ERROR(line):
	return splitparams(line)

protocol = {
	"001": msghandler("RPL_WELCOME", RPL_WELCOME),
	"002": msghandler("RPL_YOURHOST", RPL_YOURHOST),
	"003": msghandler("RPL_CREATED", RPL_CREATED),
	"004": msghandler("RPL_MYINFO", RPL_MYINFO),
	"005": msghandler("RPL_BOUNCE", RPL_BOUNCE),
	"302": msghandler("RPL_USERHOST", RPL_USERHOST),
	"303": msghandler("RPL_ISON", RPL_ISON),
	"301": msghandler("RPL_AWAY", RPL_AWAY),
	"305": msghandler("RPL_UNAWAY", RPL_UNAWAY),
	"306": msghandler("RPL_NOWAWAY", RPL_NOWAWAY),
	"311": msghandler("RPL_WHOISUSER", RPL_WHOISUSER),
	"312": msghandler("RPL_WHOISSERVER", RPL_WHOISSERVER),
	"313": msghandler("RPL_WHOISOPERATOR", RPL_WHOISOPERATOR),
	"317": msghandler("RPL_WHOISIDLE", RPL_WHOISIDLE),
	"318": msghandler("RPL_ENDOFWHOIS", RPL_ENDOFWHOIS),
	"319": msghandler("RPL_WHOISCHANNELS", RPL_WHOISCHANNELS),
	"314": msghandler("RPL_WHOWASUSER", RPL_WHOWASUSER),
	"369": msghandler("RPL_ENDOFWHOWAS", RPL_ENDOFWHOWAS),
	"321": msghandler("RPL_LISTSTART", RPL_LISTSTART),
	"322": msghandler("RPL_LIST", RPL_LIST),
	"323": msghandler("RPL_LISTEND", RPL_LISTEND),
	"325": msghandler("RPL_UNIQOPIS", RPL_UNIQOPIS),
	"324": msghandler("RPL_CHANNELMODEIS", RPL_CHANNELMODEIS),
	"331": msghandler("RPL_NOTOPIC", RPL_NOTOPIC),
	"332": msghandler("RPL_TOPIC", RPL_TOPIC),
	"341": msghandler("RPL_INVITING", RPL_INVITING),
	"342": msghandler("RPL_SUMMONING", RPL_SUMMONING),
	"346": msghandler("RPL_INVITELIST", RPL_INVITELIST),
	"347": msghandler("RPL_ENDOFINVITELIST", RPL_ENDOFINVITELIST),
	"348": msghandler("RPL_EXCEPTLIST", RPL_EXCEPTLIST),
	"349": msghandler("RPL_ENDOFEXCEPTLIST", RPL_ENDOFEXCEPTLIST),
	"351": msghandler("RPL_VERSION", RPL_VERSION),
	"352": msghandler("RPL_WHOREPLY", RPL_WHOREPLY),
	"315": msghandler("RPL_ENDOFWHO", RPL_ENDOFWHO),
	"353": msghandler("RPL_NAMREPLY", RPL_NAMREPLY),
	"366": msghandler("RPL_ENDOFNAMES", RPL_ENDOFNAMES),
	"364": msghandler("RPL_LINKS", RPL_LINKS),
	"365": msghandler("RPL_ENDOFLINKS", RPL_ENDOFLINKS),
	"367": msghandler("RPL_BANLIST", RPL_BANLIST),
	"368": msghandler("RPL_ENDOFBANLIST", RPL_ENDOFBANLIST),
	"371": msghandler("RPL_INFO", RPL_INFO),
	"374": msghandler("RPL_ENDOFINFO", RPL_ENDOFINFO),
	"375": msghandler("RPL_MOTDSTART", RPL_MOTDSTART),
	"372": msghandler("RPL_MOTD", RPL_MOTD),
	"376": msghandler("RPL_ENDOFMOTD", RPL_ENDOFMOTD),
	"381": msghandler("RPL_YOUREOPER", RPL_YOUREOPER),
	"382": msghandler("RPL_REHASHING", RPL_REHASHING),
	"383": msghandler("RPL_YOURESERVICE", RPL_YOURESERVICE),
	"391": msghandler("RPL_TIME", RPL_TIME),
	"392": msghandler("RPL_USERSSTART", RPL_USERSSTART),
	"393": msghandler("RPL_USERS", RPL_USERS),
	"394": msghandler("RPL_ENDOFUSERS", RPL_ENDOFUSERS),
	"395": msghandler("RPL_NOUSERS", RPL_NOUSERS),
	"200": msghandler("RPL_TRACELINK", RPL_TRACELINK),
	"201": msghandler("RPL_TRACECONNECTING", RPL_TRACECONNECTING),
	"202": msghandler("RPL_TRACEHANDSHAKE", RPL_TRACEHANDSHAKE),
	"203": msghandler("RPL_TRACEUNKNOWN", RPL_TRACEUNKNOWN),
	"204": msghandler("RPL_TRACEOPERATOR", RPL_TRACEOPERATOR),
	"205": msghandler("RPL_TRACEUSER", RPL_TRACEUSER),
	"206": msghandler("RPL_TRACESERVER", RPL_TRACESERVER),
	"207": msghandler("RPL_TRACESERVICE", RPL_TRACESERVICE),
	"208": msghandler("RPL_TRACENEWTYPE", RPL_TRACENEWTYPE),
	"209": msghandler("RPL_TRACECLASS", RPL_TRACECLASS),
	"210": msghandler("RPL_TRACERECONNECT", RPL_TRACERECONNECT),
	"261": msghandler("RPL_TRACELOG", RPL_TRACELOG),
	"262": msghandler("RPL_TRACEEND", RPL_TRACEEND),
	"211": msghandler("RPL_STATSLINKINFO", RPL_STATSLINKINFO),
	"212": msghandler("RPL_STATSCOMMANDS", RPL_STATSCOMMANDS),
	"219": msghandler("RPL_ENDOFSTATS", RPL_ENDOFSTATS),
	"242": msghandler("RPL_STATSUPTIME", RPL_STATSUPTIME),
	"243": msghandler("RPL_STATSOLINE", RPL_STATSOLINE),
	"221": msghandler("RPL_UMODEIS", RPL_UMODEIS),
	"234": msghandler("RPL_SERVLIST", RPL_SERVLIST),
	"235": msghandler("RPL_SERVLISTEND", RPL_SERVLISTEND),
	"251": msghandler("RPL_LUSERCLIENT", RPL_LUSERCLIENT),
	"252": msghandler("RPL_LUSEROP", RPL_LUSEROP),
	"253": msghandler("RPL_LUSERUNKNOWN", RPL_LUSERUNKNOWN),
	"254": msghandler("RPL_LUSERCHANNELS", RPL_LUSERCHANNELS),
	"255": msghandler("RPL_LUSERME", RPL_LUSERME),
	"256": msghandler("RPL_ADMINME", RPL_ADMINME),
	"257": msghandler("RPL_ADMINLOC1", RPL_ADMINLOC1),
	"258": msghandler("RPL_ADMINLOC2", RPL_ADMINLOC2),
	"259": msghandler("RPL_ADMINEMAIL", RPL_ADMINEMAIL),
	"263": msghandler("RPL_TRYAGAIN", RPL_TRYAGAIN),
	"401": msghandler("ERR_NOSUCHNICK", ERR_NOSUCHNICK),
	"402": msghandler("ERR_NOSUCHSERVER", ERR_NOSUCHSERVER),
	"403": msghandler("ERR_NOSUCHCHANNEL", ERR_NOSUCHCHANNEL),
	"404": msghandler("ERR_CANNOTSENDTOCHAN", ERR_CANNOTSENDTOCHAN),
	"405": msghandler("ERR_TOOMANYCHANNELS", ERR_TOOMANYCHANNELS),
	"406": msghandler("ERR_WASNOSUCHNICK", ERR_WASNOSUCHNICK),
	"407": msghandler("ERR_TOOMANYTARGETS", ERR_TOOMANYTARGETS),
	"408": msghandler("ERR_NOSUCHSERVICE", ERR_NOSUCHSERVICE),
	"409": msghandler("ERR_NOORIGIN", ERR_NOORIGIN),
	"411": msghandler("ERR_NORECIPIENT", ERR_NORECIPIENT),
	"412": msghandler("ERR_NOTEXTTOSEND", ERR_NOTEXTTOSEND),
	"413": msghandler("ERR_NOTOPLEVEL", ERR_NOTOPLEVEL),
	"414": msghandler("ERR_WILDTOPLEVEL", ERR_WILDTOPLEVEL),
	"415": msghandler("ERR_BADMASK", ERR_BADMASK),
	"421": msghandler("ERR_UNKNOWNCOMMAND", ERR_UNKNOWNCOMMAND),
	"422": msghandler("ERR_NOMOTD", ERR_NOMOTD),
	"423": msghandler("ERR_NOADMININFO", ERR_NOADMININFO),
	"424": msghandler("ERR_FILEERROR", ERR_FILEERROR),
	"431": msghandler("ERR_NONICKNAMEGIVEN", ERR_NONICKNAMEGIVEN),
	"432": msghandler("ERR_ERRONEUSNICKNAME", ERR_ERRONEUSNICKNAME),
	"433": msghandler("ERR_NICKNAMEINUSE", ERR_NICKNAMEINUSE),
	"436": msghandler("ERR_NICKCOLLISION", ERR_NICKCOLLISION),
	"437": msghandler("ERR_UNAVAILRESOURCE", ERR_UNAVAILRESOURCE),
	"441": msghandler("ERR_USERNOTINCHANNEL", ERR_USERNOTINCHANNEL),
	"442": msghandler("ERR_NOTONCHANNEL", ERR_NOTONCHANNEL),
	"443": msghandler("ERR_USERONCHANNEL", ERR_USERONCHANNEL),
	"444": msghandler("ERR_NOLOGIN", ERR_NOLOGIN),
	"445": msghandler("ERR_SUMMONDISABLED", ERR_SUMMONDISABLED),
	"446": msghandler("ERR_USERSDISABLED", ERR_USERSDISABLED),
	"451": msghandler("ERR_NOTREGISTERED", ERR_NOTREGISTERED),
	"461": msghandler("ERR_NEEDMOREPARAMS", ERR_NEEDMOREPARAMS),
	"462": msghandler("ERR_ALREADYREGISTRED", ERR_ALREADYREGISTRED),
	"463": msghandler("ERR_NOPERMFORHOST", ERR_NOPERMFORHOST),
	"464": msghandler("ERR_PASSWDMISMATCH", ERR_PASSWDMISMATCH),
	"465": msghandler("ERR_YOUREBANNEDCREEP", ERR_YOUREBANNEDCREEP),
	"466": msghandler("ERR_YOUWILLBEBANNED", ERR_YOUWILLBEBANNED),
	"467": msghandler("ERR_KEYSET", ERR_KEYSET),
	"471": msghandler("ERR_CHANNELISFULL", ERR_CHANNELISFULL),
	"472": msghandler("ERR_UNKNOWNMODE", ERR_UNKNOWNMODE),
	"473": msghandler("ERR_INVITEONLYCHAN", ERR_INVITEONLYCHAN),
	"474": msghandler("ERR_BANNEDFROMCHAN", ERR_BANNEDFROMCHAN),
	"475": msghandler("ERR_BADCHANNELKEY", ERR_BADCHANNELKEY),
	"476": msghandler("ERR_BADCHANMASK", ERR_BADCHANMASK),
	"477": msghandler("ERR_NOCHANMODES", ERR_NOCHANMODES),
	"478": msghandler("ERR_BANLISTFULL", ERR_BANLISTFULL),
	"481": msghandler("ERR_NOPRIVILEGES", ERR_NOPRIVILEGES),
	"482": msghandler("ERR_CHANOPRIVSNEEDED", ERR_CHANOPRIVSNEEDED),
	"483": msghandler("ERR_CANTKILLSERVER", ERR_CANTKILLSERVER),
	"484": msghandler("ERR_RESTRICTED", ERR_RESTRICTED),
	"485": msghandler("ERR_UNIQOPPRIVSNEEDED", ERR_UNIQOPPRIVSNEEDED),
	"491": msghandler("ERR_NOOPERHOST", ERR_NOOPERHOST),
	"501": msghandler("ERR_UMODEUNKNOWNFLAG", ERR_UMODEUNKNOWNFLAG),
	"502": msghandler("ERR_USERSDONTMATCH", ERR_USERSDONTMATCH),
	"231": msghandler("RPL_SERVICEINFO", reserved_num),
	"232": msghandler("RPL_ENDOFSERVICES", reserved_num),
	"233": msghandler("RPL_SERVICE", reserved_num),
	"300": msghandler("RPL_NONE", reserved_num),
	"316": msghandler("RPL_WHOISCHANOP", reserved_num),
	"361": msghandler("RPL_KILLDONE", reserved_num),
	"362": msghandler("RPL_CLOSING", reserved_num),
	"363": msghandler("RPL_CLOSEEND", reserved_num),
	"373": msghandler("RPL_INFOSTART", reserved_num),
	"384": msghandler("RPL_MYPORTIS", reserved_num),
	"213": msghandler("RPL_STATSCLINE", reserved_num),
	"214": msghandler("RPL_STATSNLINE", reserved_num),
	"215": msghandler("RPL_STATSILINE", reserved_num),
	"216": msghandler("RPL_STATSKLINE", reserved_num),
	"217": msghandler("RPL_STATSQLINE", reserved_num),
	"218": msghandler("RPL_STATSYLINE", reserved_num),
	"240": msghandler("RPL_STATSVLINE", reserved_num),
	"241": msghandler("RPL_STATSLLINE", reserved_num),
	"244": msghandler("RPL_STATSHLINE", reserved_num),
	"246": msghandler("RPL_STATSPING", reserved_num),
	"247": msghandler("RPL_STATSBLINE", reserved_num),
	"250": msghandler("RPL_STATSDLINE", reserved_num),
	"492": msghandler("ERR_NOSERVICEHOST", reserved_num),
	"NICK": msghandler("NICK", NICK),
	"MODE": msghandler("MODE", MODE),
	"QUIT": msghandler("QUIT", QUIT),
	"JOIN": msghandler("JOIN", JOIN),
	"PART": msghandler("PART", PART),
	"TOPIC": msghandler("TOPIC", TOPIC),
	"INVITE": msghandler("INVITE", INVITE),
	"KICK": msghandler("KICK", KICK),
	"PRIVMSG": msghandler("PRIVMSG", PRIVMSG),
	"NOTICE": msghandler("NOTICE", NOTICE),
	"PING": msghandler("PING", PING),
	"ERROR": msghandler("ERROR", ERROR)
}

def prefixsplit(prefix, ident_prefixes =
		filter(lambda x: len(x) == 1, ident_types.keys())):
	# Important: This shuffles the usual sequence of certain data,
	# this was done to make the meaning of prefixlist[n] independent
	# of len(prefixlist), the list is defined as follows using () to
	# denote list (not tuple) structure, and [] to denote optional items.
	# (nickname|servername, [host, [user, ident_and_linetype]])
	if '@' in prefix:
		prefixlist = string.split(prefix, '@', 1)
		if '!' in prefixlist[0]:
			tmp = string.split(prefixlist[0], '!')
			prefixlist[0] = tmp[0]
			if tmp[1][0] in ident_prefixes:
				ident_t = tmp[1][0]
				tmp[1] = tmp[1][1:]
			else:
				ident_t = ''
			prefixlist.append(tmp[1])
			prefixlist.append(ident_types[ident_t])
		return prefixlist
	else:
		return [prefix]
		

class irclink:
	nummsg = re.compile(r'\d\d\d')
	def __init__(self, server, port, parsedict=protocol):
		self.server, self.port = server, port
		self.parsedict = parsedict
		self.writebuffer = ''
		self.readbuffer = ''
	def fileno(self):
		return self.ircsocket.fileno()
	def connect(self):
		self.ircsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.ircsocket.connect(self.server, self.port)
		self.ircsocket.setblocking(0)
	def cleanup(self):
		try:
			self.ircsocket.shutdown(2)
		except:
			pass
		self.ircsocket.close()
		del self.ircsocket # Will make any further attempts
		                   # to access the socket fail
	def getevent(self):
		input = self.readline()
		if not input:
			return None
		else:
			return self.parse(input[:-2])
	def readline(self, input=1):
		try:
			input = self.ircsocket.recv(1024)
		except socket.error, val:
			if val[0] == EAGAIN:
				pass
			else:
				raise sys.exc_info()
		if (input != 1) and (len(input) == 0):
			raise LostConnectionError
		elif input == 1:
			input = ''
		self.readbuffer = self.readbuffer + input
		linelength = string.find(self.readbuffer, '\r\n')
		if linelength != -1:
			output = self.readbuffer[:linelength+2]
			self.readbuffer = self.readbuffer[linelength+2:]
		else:
			output = None
		return output
	def writeline(self, data):
		outstring = self.writebuffer + ( "%s\r\n" % data[:510] )
		sent = self.ircsocket.send(outstring)
		self.writebuffer = outstring[sent:]
	def __splitcommand(self, line):
		msgdict = {}
		if line[0] == ':':
			tmplist = string.split(line, ' ', 2)
			msgdict['prefix'] = prefixsplit(tmplist[0][1:])
			msgdict['command'] = tmplist[1]
			del tmplist[0], tmplist[0]
		else:
			tmplist = string.split(line, ' ', 1)
			msgdict['command'] = tmplist[0]
			del tmplist[0]
		return msgdict, tmplist
	def parse(self, line):
		msgdict, tmplist = self.__splitcommand(line)
		msgdict['original'] = line
		if len(tmplist) == 0:
			return msgdict
		if self.nummsg.match(msgdict['command']):
			tmplist = string.split(tmplist[0], ' ', 1)
			msgdict['target'] = tmplist[0]
			del tmplist[0]
		try:
			paramstring = ' '+tmplist[0]
		except IndexError:
			# In case of no more data
			return msgdict
		msgdict['paramstring'] = paramstring
		handler = self.parsedict.get(msgdict['command'], None)
		if handler:
			msgdict['name'] = handler.msgname
			try:
				msgdict['params'] = handler(paramstring)
			except:
				# Ugly, but I want to get the msgdict passed even if someone
				# has changed the plain text strings in the ircd source. :]
				pass
		return msgdict

	# Add commands here as necessary...
	def NICK(self, nick):
		self.writeline('NICK ' + nick)
	def USER(self, user, realname, mode='0'):
		self.writeline('USER %s %s * :%s' % (user, mode, realname))
	def JOIN(self, channel):
		self.writeline('JOIN '+channel)
	def PRIVMSG(self, target, msg):
		self.writeline('PRIVMSG %s :%s' % (target, msg))
	def PONG(self, serverlist):
		self.writeline("PONG %s" % string.join(serverlist, ' '))

class throttle:
	def __init__(self, client):
		self.client = client
		self.output = []
		self.time = time.time()
	def __call__(self):
		if self.time < time.time():
			self.time = time.time()
		if self.time < time.time() + 10:
			try:
				apply(getattr(self.client, self.output[0][0]), self.output[0][1])
				del self.output[0]
			except socket.error:
				return RemoveStream
			except:
				del self.output[0] # Assumes it was a malformed message
			self.time = self.time + 2
	def queue(self, command, params):
		self.output.append((command, params))
	def queuelen(self):
		return len(self.output)
	def getstream(self):
		return self.client
