#!/usr/bin/env python

import socket
import sys
import re
import select

_linesep_regexp = re.compile("\r?\n")

users = {} # users[alice] = (alice, secret)
conns = [] # conns[i] = [sock, addr, buf, user, authenticated]

def disconnect(conn, msg):
    global conns
    print "disconnect ", repr(conn), repr(msg)
    conns = filter(lambda x: x != conn, conns)
    
def process_conn_data(conn):
    global conns, users
    print "conns ", repr(conns)
    print "users ", repr(users)
    try:
        new_data = conn[0].recv(2**14)
    except socket.error, x:
        # The server hung up.
        disconnect(conn, "Connection reset by peer")
        return
    if not new_data:
        # Read nothing: connection must be down.
        disconnect(conn, "Connection reset by peer")
        return
    
    lines = _linesep_regexp.split(conn[2] + new_data)

    # Save the last, unfinished line.
    conn[2] = lines[-1]
    lines = lines[:-1]

    for line in lines:
#            print "FROM SERVER:", line
        if not line:
            continue
        words = line.split(" ")
        if words[0] == "USER":
            if conn[3] != None:
                conn[0].send("503 Already logged in\n")
            else:
                conn[3] = words[1]
        elif words[0] == "PASS":
            if conn[4] == True:
                conn[0].send("504 Already authenticated\n")
            else:
                if conn[3] not in users:
                    users[conn[3]] = (conn[3], words[1])
                if conn[3] in map(lambda x: x[3], filter(lambda y: y[4] == True, conns)):
                    conn[0].send("506 Authenticated using another connection\n")
                elif users[conn[3]][1] == words[1]:
                    conn[4] = True
                else:
                    conn[0].send("505 Wrong password\n")
        elif words[0] == "PING":
            conn[0].send("PONG " + " ".join(words[1:]) + "\n")
        elif conn[4] == False:
            conn[0].send("506 Not authenticated\n")
        elif words[0] == "MSG":
            if words[1] not in users:
                conn[0].send("507 Unknown user\n")
            elif words[1] not in map(lambda x: x[3], filter(lambda y: y[4] == True, conns)):
                conn[0].send("508 User offline\n")
            else:
                for c in conns:
                    if words[1] == c[3]:
                        c[0].send("100 " + conn[3] + " " + " ".join(words[2:]) + "\n")

def main():
    global conns
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 8080))
    sock.listen(5)

    while True:
        sockets = map(lambda x: x[0], conns)
        sockets = filter(lambda x: x != None, sockets)
        sockets += [sock]
        (i, o, e) = select.select(sockets, [], [], None)

        for s in i:
            if s == sock:
                conn, addr = sock.accept()
                conns += [[conn, addr, "", None, False]]
            else:
                for c in conns:
                    if s == c[0]:
                        process_conn_data(c)
    
main()
