import eventlet
import eventlet.queue as queue
import tincanchat


HOST = tincanchat.HOST
PORT = tincanchat.PORT
send_queue = {}


def handle_client_recv(sock, addr):
    """ Receive messages from client and broadcast them to other clients until client disconnects """
    rest = bytes()
    while True:
        try:
            (msgs, rest) = tincanchat.recv_msgs(sock)
        except (EOFError, ConnectionError):
            handle_disconnect(sock, addr)
            break
        for msg in msgs:
            msg = '{}:{}'.format(addr, msg)
            print(msg)
            broadcast_msg(msg)


def handle_client_send(sock, q, addr):
    """ Monitor queue for new messages, send them to client as they arrive """
    while True:
        msg = q.get()
        if not msg:
            break
        try:
            tincanchat.send_msg(sock, msg)
        except (ConnectionError, BrokenPipeError):
            handle_disconnect(sock, addr)
            break


def broadcast_msg(msg):
    """ Add message to each connected client's send queue """
    for q in send_queue.values():
        q.put(msg)


def handle_disconnect(sock, addr):
    """ Ensure queue is cleaned up and socket closed when a client disconnects """
    fd = sock.fileno()
    # Get send_queue for this client
    q = send_queue.get(fd, None)
    # If we find a queue then this disconnect has not yet
    # been handled
    if q:
        q.put(None)
        del send_queue[fd]
        addr = sock.getpeername()
        print('Client {} disconnected'.format(addr))
        sock.close()


if __name__ == '__main__':
    server = eventlet.listen((HOST, PORT))
    addr = server.getsockname()
    print('Listening on {}'.format(addr))

    while True:
        client_sock, addr = server.accept()
        q = queue.Queue()
        send_queue[client_sock.fileno()] = q
        # TODO: see send_queue
        eventlet.spawn_n(handle_client_recv, client_sock, addr)
        eventlet.spawn_n(handle_client_send, client_sock, q, addr)
        print('Connection from {}'.format(addr))
