# -*- Mode: Python; tab-width: 4 -*-
#
#	Author: Sam Rushing <rushing@nightmare.com>
#	Copyright 1997-2000 by Sam Rushing
#						 All Rights Reserved.
#

RCS_ID = '$Id: chat_server.py,v 1.5 2003/03/18 21:15:16 fdrake Exp $'

import string

VERSION = string.split(RCS_ID)[2]

import socket
import asyncore
import asynchat
import status_handler

class chat_channel (asynchat.async_chat):

    def __init__ (self, server, sock, addr):
        asynchat.async_chat.__init__ (self, sock)
        self.server = server
        self.addr = addr
        self.set_terminator ('\r\n')
        self.data = ''
        self.nick = None
        self.push ('nickname?: ')
        
    def collect_incoming_data (self, data):
        self.data = self.data + data
        
    def found_terminator (self):
        line = self.data
        self.data = ''
        if self.nick is None:
            self.nick = string.split (line)[0]
            if not self.nick:
                self.nick = None
                self.push ('huh? gimmee a nickname: ')
            else:
                self.greet()
        else:
            if not line:
                pass
            elif line[0] != '/':
                self.server.push_line (self, line)
            else:
                self.handle_command (line)
                
    def greet (self):
        self.push ('Hello, %s\r\n' % self.nick)
        num_channels = len(self.server.channels)-1
        if num_channels == 0:
            self.push ('[Kinda lonely in here... you\'re the only caller!]\r\n')
        else:
            self.push ('[There are %d other callers]\r\n' % (len(self.server.channels)-1))
            nicks = map (lambda x: x.get_nick(), self.server.channels.keys())
            self.push (string.join (nicks, '\r\n  ') + '\r\n')
            self.server.push_line (self, '[joined]')
            
    def handle_command (self, command):
        import types
        command_line = string.split(command)
        name = 'cmd_%s' % command_line[0][1:]
        if hasattr (self, name):
                # make sure it's a method...
            method = getattr (self, name)
            if type(method) == type(self.handle_command):
                method (command_line[1:])
            else:
                self.push ('unknown command: %s' % command_line[0])
                
    def cmd_quit (self, args):
        self.server.push_line (self, '[left]')
        self.push ('Goodbye!\r\n')
        self.close_when_done()
        
        # alias for '/quit' - '/q'
    cmd_q = cmd_quit
    
    def push_line (self, nick, line):
        self.push ('%s: %s\r\n' % (nick, line))
        
    def handle_close (self):
        self.close()
        
    def close (self):
        del self.server.channels[self]
        asynchat.async_chat.close (self)
        
    def get_nick (self):
        if self.nick is not None:
            return self.nick
        else:
            return 'Unknown'
            
class chat_server (asyncore.dispatcher):

    SERVER_IDENT = 'Chat Server (V%s)' % VERSION
    
    channel_class = chat_channel
    
    spy = 1
    
    def __init__ (self, ip='', port=8518):
        self.port = port
        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
        self.bind ((ip, port))
        print '%s started on port %d' % (self.SERVER_IDENT, port)
        self.listen (5)
        self.channels = {}
        self.count = 0
        
    def handle_accept (self):
        conn, addr = self.accept()
        self.count = self.count + 1
        print 'client #%d - %s:%d' % (self.count, addr[0], addr[1])
        self.channels[self.channel_class (self, conn, addr)] = 1
        
    def push_line (self, from_channel, line):
        nick = from_channel.get_nick()
        if self.spy:
            print '%s: %s' % (nick, line)
        for c in self.channels.keys():
            if c is not from_channel:
                c.push ('%s: %s\r\n' % (nick, line))
                
    def status (self):
        lines = [
                '<h2>%s</h2>'						% self.SERVER_IDENT,
                '<br>Listening on Port: %d'			% self.port,
                '<br><b>Total Sessions:</b> %d'		% self.count,
                '<br><b>Current Sessions:</b> %d'	% (len(self.channels))
                ]
        return status_handler.lines_producer (lines)
        
    def writable (self):
        return 0
        
if __name__ == '__main__':
    import sys
    
    if len(sys.argv) > 1:
        port = string.atoi (sys.argv[1])
    else:
        port = 8518
        
    s = chat_server ('', port)
    asyncore.loop()
