# This software was originally developed as a part of a project paid for by
# UNINETT, Norway.

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
