#!/usr/bin/python

import time
import socket
import json
import sys
import select
import os
import random
import re

sname = "/tmp/webchatsrv"

# Globals
sessions = {}
sockets = {}
buffers = {}
users = {}
log = None

def logit(msg, sid = 0):
    log.write(time.asctime()+" => ["+str(sid)+"] "+str(msg)+"\n")
    log.flush()

def make_sid():
    sid = int(random.random()*2**32)
    if sid in sessions:
        return make_sid()
    else:
        return sid

def send(s, obj):
    try:
        s.send(json.write(obj)+"\nEND\n")
    except socket.error:
        sid = sockets[s]
        del sockets[s]
        try:
            del users[sid]
            del sessions[sid]
        except KeyError:
            pass
        else:
            update_userlists()
        

def send_msg_all(sid, msg):
    send_all(sid, {"cmd":"MSG", "msg":msg, "from":users[sid]})

def send_all(sid, obj):
    sess = dict(sessions)
    if sid in users:
        user = users[sid]
        for i in sess:
            if i != sid:
                send(sess[i], obj)


def update_userlists():
    sess = dict(sessions) # Workaround since it can change size
    for i in sess:
        send(sess[i], {"cmd":"USERLIST", "userlist" : users.values()})


def dispatch(sid, js):
    if js["cmd"] == "MSG":
        logit("<"+users[sid]+"> "+js["msg"], sid=sid);
        send_msg_all(sid, js["msg"])
    elif js["cmd"] == "NICK":
        if "nick" in js:
            logit("Nick change ["+str(users[sid])+"] -> ["
                  +str(js["nick"])+"]", sid)
            users[sid] = js["nick"]
            update_userlists()
    else:
        logit("got ["+str(js)+"]", sid)


def receive(s):
    try:
        msg = s.recv(65536)
    except socket.error:
        msg = None
    if msg == None or len(msg) == 0:
        s.close()
        try:
            sid = sockets[s]
            del sockets[s]
            del users[sid]
            del sessions[sid]
        except KeyError:
            pass
        else:
            update_userlists()
        return
    
    if msg == "SESSION\n":
        sid = make_sid()
        logit("New session ", sid)
        sessions[sid] = s
        users[sid] = "Anon"+str(make_sid())
        sockets[s] = sid
        buffers[s] = ""
        send(s, {"cmd":"HELLO", "sesid":sid})
        update_userlists()
    else:
        buffers[s] += msg
        msg = buffers[s]
        while re.search("\nEND\n", msg):
            m = re.search("\nEND\n", msg)
            js = json.read(msg[0:m.start()])
            if sockets[s] and "sesid" in js:
                sid = sockets[s]
            else:
                sid = js["sesid"]
            dispatch(sid, js)
            msg = msg[m.end():]
        buffers[s] = msg

def newcon(s):
    z = s.accept()
    sockets[z[0]] = None
    buffers[z[0]] = ""


#####
### Main program
#####

# Open our socket
try:
    os.unlink(sname)
except OSError:
    pass

# Fork off
if os.fork():
    sys.exit()
try:
    os.setsid()
except OSError:
    pass
for i in range(0, 100):
    try:
        os.close(i)
    except OSError:
        pass
if os.fork():
    sys.exit()
                    
# Wait for connections
s = socket.socket(socket.AF_UNIX)
s.bind(sname)
os.chmod(sname, 0666)
s.listen(5)

# Log file
log = open("/var/log/webchat/chatsrv.log", "a")

logit("Started!")

lastping = time.time();
ptdiff = 0
users[0] = "System"

while True:
    # Build list for select
    slist = sockets.keys()
    slist.append(s)

    # Wait for something
    res = select.select(slist,[],slist, 1-ptdiff)

    # If timeout
    ptdiff = time.time() - lastping
    if time.time() - lastping > 1:
        lastping = time.time()
        ptdiff = 0
        send_all(0, {"cmd":"PING"});

    # Process result
    rlist = res[0];
    xlist = res[2];
    for i in xlist:
        if i == s:
            newcon(s)
        else:
            i.close()
            try:
                del sockets[i]
                del sessions[sockets[i]]
            except KeyError:
                pass
        try:
            rlist.remove(i)
        except ValueError:
            pass

    for i in rlist:
        if i == s:
            newcon(s)
        else:
            receive(i)
# end while True
            
