##
## server.py -- IRC server interface.
##
## Copyleft 1997 Teemu Kalvas <chery@s2.org>
##

## +class Server             (p,       persistent storage object
##                            server)  configuration data
##  -method read             (p)       persistent storage object
##  -method write            (p)       persistent storage object
##  -method handle_XXX       (p,       persistent storage object
##                            nick,    nickname or server name of sender
##                            addr,    address of sender
##                            args)    command tail
##  -method handle_ctcp_XXX  (p,       persistent storage object
##                            nick,    nickname or server name of sender
##                            addr,    address of sender
##                            chan,    target channel or nickname
##                            args)    ctcp command arguments
##  -method sock
##  -method out
##  -method connected
##  -method quitting
##  -method nick

import config
import regex
import socket
import string
import time

def check_and_op(p, (lnick, chan, serv)):
    if serv.channels.has_key(chan):
	dict = serv.channels[chan][1]
	if dict.has_key(lnick) and '@' not in dict[lnick]:
	    serv.out.append('MODE ' + chan + ' +o ' + lnick)

class Server:
    def __init__(self, p, server):
	self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	self.sock.connect(server[:2])
	self.nicks = server[2]
	self.out = ['USER ' + config.login + ' * * :' + config.realname,
		    'NICK ' + self.nicks[0]]
	self.autojoin = server[3]
	self.ind = ''
	self.window = p.screen.new_window('(none)', self)
	self.channels = {}
	self.rcline = regex.compile(':\([^ !]*\)\(!\([^ ]*\)\)? \([a-zA-Z0-9]*\) \(.*\)')
	self.rcctcp = regex.compile('\001\([^ ]+\)\( \(.*\)\)?\001')
	self.connected = 1
	self.quitting = 0
	self.nick = '(no one)'

    def read(self, p):
	ind = self.sock.recv(768)
	if ind == '':
	    self.connected = 0
	    return
	self.ind = self.ind + ind
	f = string.find(self.ind, '\r\n')
	while f <> -1:
	    line, self.ind = self.ind[:f], self.ind[f + 2:]
	    p.rand = p.rand ^ hash(line)
	    if regex.match('PING', line) > 0:
		self.out.append('PONG' + line[4:])
	    elif self.rcline.match(line) > 0:
		nick, addr, command, tail = self.rcline.group(1, 3, 4, 5)
		command = string.lower(command)
		args = []
		while 1:
		    if len(tail) == 0:
			break
		    if tail[0] == ':':
			args.append(tail[1:])
			break
		    f = string.find(tail, ' ')
		    if f == -1:
			break
		    args.append(tail[:f])
		    tail = tail[f + 1:]
		if config.__dict__.has_key(command + '_hook'):
		    skip = config.__dict__[command + '_hook'](self, p, nick,
							      addr, args)
		else:
		    skip = 0
		if not skip and Server.__dict__.has_key('handle_' + command):
		    Server.__dict__['handle_' + command](self, p, nick, addr,
		    args)
		if config.__dict__.has_key(command + '_post_hook'):
		    config.__dict__[command + '_post_hook'](self, p, nick,
							    addr, args)
	    f = string.find(self.ind, '\r\n')

    def write(self, p):
	self.sock.send(self.out[0] + '\r\n')
	del self.out[0]

    ## :irc-2.cs.hut.fi 001 ilciin :Welcome to the Internet Relay Network \
    ## ilciin!chery@a31b.mtalo.ton.tut.fi
    def handle_001(self, p, nick, addr, args):
	self.name = nick
	self.window.set_name(nick)
	self.nick = config.low(args[0])
	for chan in self.autojoin:
	    self.out.append('JOIN ' + chan)

    ## :irc.atm.tut.fi 311 ilciin jma jma one.sci.fi * :Joonas Mäkinen
    def handle_311(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' (' + args[2] + '@' + args[3] +
			') is ' + args[5])

    ## :irc.atm.tut.fi 312 ilciin jma irc.atm.tut.fi :Tampere Univ of Tech \
    ## <IRC Net>
    def handle_312(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' is on irc via ' + args[2] + ' (' +
			args[3] + ')')

    ## :irc.atm.tut.fi 317 ilciin jma 298 :seconds idle
    def handle_317(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' has been ' + args[2] + ' ' +
			args[3])

    ## :irc.atm.tut.fi 319 ilciin jma :#yakki @#pallomeri
    def handle_319(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' is on channels: ' + args[2])

    ## :irc.pspt.fi 332 ilciin #tampere :Ristipolytysta 17,90 mk/min
    def handle_332(self, p, nick, addr, args):
	self.channels[config.low(args[1])][0].log('*** topic on ' + args[1] +
						  ': ' + args[2])

    ## :irc-2.cs.hut.fi 353 ilciin = #qwer :@ilciin 
    def handle_353(self, p, nick, addr, args):
	chan = config.low(args[2])
	self.channels[chan][0].log('*** users on ' + args[2] + ': ' + args[3])
	for nick in string.split(args[3]):
	    if nick[0] == '@' or nick[0] == '+':
		lnick = config.low(nick[1:])
	    else:
		lnick = config.low(nick)
	    self.channels[chan][1][lnick] = filter(lambda x: x in '@+', nick)

    ## :irc-2.cs.hut.fi 433 ilciin cliini :Nickname is already in use.
    def handle_433(self, p, nick, addr, args):
	self.nicks = self.nicks[1:] + self.nicks[:1]
	self.out.append('NICK ' + self.nicks[0])

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi JOIN :#qwer
    def handle_join(self, p, nick, addr, args):
	chan = config.low(args[0])
	lnick = config.low(nick)
	if lnick == self.nick:
	    win = p.screen.current.next
	    while win <> p.screen.current:
		if config.low(win.name) == chan and win.server == self:
		    break
		win = win.next
	    else:
		if config.low(win.name) <> chan or win.server <> self:
		    win = p.screen.new_window(args[0], self)
	    self.channels[chan] = win, {}
	self.channels[chan][0].log('*** ' + nick + ' (' + addr +
				   ') has joined channel ' + args[0])
	self.channels[chan][1][lnick] = ''
	if config.autoop(addr, chan):
	    p.wait_add(time.time() + (p.rand % 17000) / 1000.0 + 5.0,
		       check_and_op, (lnick, chan, self))

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi KICK #qwer ilciin :bleh
    def handle_kick(self, p, nick, addr, args):
	chan = config.low(args[0])
	lnick = config.low(args[1])
	self.channels[chan][0].log('*** ' + nick + ' has kicked ' + args[1] +
				   ' from channel ' + args[0] + ' (' +
				   args[2] + ')')
	del self.channels[chan][1][lnick]
	if lnick == self.nick:
	    self.channels[chan] = self.channels[chan][0], {}

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi MODE #qwer +snt 
    def handle_mode(self, p, nick, addr, args):
	chan = config.low(args[0])
	if self.channels.has_key(chan):
	    self.channels[chan][0].log('*** ' + nick + ' has set mode ' +
				       reduce(lambda x, y: x + ' ' + y, args))
	    chs, args = args[1], args[2:]
	    for ch in chs:
		if ch == '+':
		    sign = 1
		elif ch == '-':
		    sign = 0
		elif ch in 'ov':
		    dict = self.channels[chan][1]
		    lnick = config.low(args[0])
		    if sign:
			if ch == 'o':
			    if '@' not in dict[lnick]:
				dict[lnick] = dict[lnick] + '@'
			elif ch == 'v':
			    if '+' not in dict[lnick]:
				dict[lnick] = dict[lnick] + '+'
		    elif ch == 'o':
			dict[lnick] = filter(lambda x: x <> '@', dict[lnick])
		    else:
			dict[lnick] = filter(lambda x: x <> '+', dict[lnick])
		    del args[0]
		elif ch in 'bkl':
		    del args[0]
	else:
	    self.window.log('*** ' + nick + ' has set mode ' +
			    reduce(lambda x, y: x + ' ' + y, args))

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi NICK :niicli
    def handle_nick(self, p, nick, addr, args):
	lnick = config.low(nick)
	for chan in self.channels.values():
	    if chan[1].has_key(lnick):
		chan[0].log('*** ' + nick + ' is now known as ' + args[0])
		if lnick <> config.low(args[0]):
		    chan[1][config.low(args[0])] = chan[1][lnick]
		    del chan[1][lnick]

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi PART #qwer :Nymmä lähren enkä \
    ## takasin tuu!
    def handle_part(self, p, nick, addr, args):
	chan = config.low(args[0])
	lnick = config.low(nick)
	self.channels[chan][0].log('*** ' + nick + ' has left channel ' +
				   args[0] + ' (' + args[1] + ')')
	del self.channels[chan][1][lnick]
	if lnick == self.nick:
	    win = p.screen.current
	    while win <> self.channels[chan][0]:
		win = win.next
	    if win == p.screen.current:
		p.screen.current = win.next
		p.screen.touched = p.screen.touched | 63
	    win.next.prev = win.prev
	    win.prev.next = win.next
	    del self.channels[chan]

    ## :cliini!chery@tjtk2.trin.cam.ac.uk PRIVMSG ilciin :bleh
    def handle_privmsg(self, p, nick, addr, args):
	chan = config.low(args[0])
	if self.rcctcp.match(args[1]) > 0:
	    command, tail = self.rcctcp.group(1, 3)
	    command = string.lower(command)
	    if config.__dict__.has_key('ctcp_' + command + '_hook'):
		skip = config.__dict__['ctcp_' + command + '_hook'](self, p,
								    nick, addr,
								    chan, tail)
	    else:
		skip = 0
	    if not skip and Server.__dict__.has_key('handle_ctcp_' + command):
		Server.__dict__['handle_ctcp_' + command](self, p, nick, addr,
							  chan, tail)
	elif self.channels.has_key(chan):
	    self.channels[chan][0].log('<' + nick + '> ' + args[1])
	else:
	    self.window.log('*' + nick + '* ' + args[1])

    ## ERROR :Closing Link: ilciin[chery@a31b.mtalo.ton.tut.fi] (ör.)
    def handle_quit(self, p, nick, addr, args):
	lnick = config.low(nick)
	for chan in self.channels.values():
	    if chan[1].has_key(lnick):
		chan[0].log('*** ' + nick + ' has quit irc (' + args[0] + ')')
		del chan[1][lnick]

    ## :ilciin!chery@tjtk2.trin.cam.ac.uk TOPIC #Tampere :Voi olla vain yksi \
    ## Kaipila.
    def handle_topic(self, p, nick, addr, args):
	self.channels[config.low(args[0])][0].log('*** ' + nick +
						  ' has set the topic on ' +
						  args[0] + ' to: ' + args[1])

    def handle_ctcp_action(self, p, nick, addr, chan, args):
	if self.channels.has_key(chan):
	    self.channels[chan][0].log('* ' + nick + ' ' + args)
	else:
	    self.window.log('* ' + nick + ' ' + args)

    def handle_ctcp_ping(self, p, nick, addr, chan, args):
	self.out.append('NOTICE ' + nick + ' :\001PING ' + args + '\001')

    def handle_ctcp_version(self, p, nick, addr, chan, args):
	self.out.append('NOTICE ' + nick + ' :\001VERSION ' + config.version +
			'\001')

## End. ##
