import socket
import select
import sys
import utils
EXMAX = 2**13 - 1
EXMIN = 2**12
EMMAX = 2**12 - 1
EMMIN = 1

def Server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('', int(sys.argv[1])))
    server.listen(5)
    # list of sockets to watch for input and output events
    incoming = [ server ]
    outgoing = []
    # identifiers associations for emitter and exhibiter
    # the key is the emiter and value is  the exhibiter
    identifiersAssoc = {}
    # a dict for the id and connection of each exhibitor 
    exhibitorAssociation = {}
    # list of all  the clients
    idList = []
    # a generator for distribuiting ids for emiter and exhibitor
    emitterIdGenerator = 1
    exibitorIdGenerator = 2**12

    try:
        while True:
            inputs, output, _ = select.select(incoming, outgoing, [])
            for serv in inputs:
                if  serv is server:
                    # client is trying to connect
                    conn, addr = server.accept()
                    print("Connected to ", addr)
                    incoming.append(conn)
                else:
                    # data is ready to be received
                    received = utils.receiveHeader(serv)
                    # a Hi message is receaved 
                    if received and received[0] == 3:
                        type, origin, endpoint, sequence = received
                        # a normal hi from the exhibitor
                        if origin == 0:
                            outgoing.append(serv)
                            utils.constructHeader([serv, 1,utils.SERVER_ID,exibitorIdGenerator,sequence])
                            idList.append(exibitorIdGenerator)
                            exhibitorAssociation[exibitorIdGenerator] = serv
                            exibitorIdGenerator += 1
                        # an association between exhibitor and emiter must be made
                        elif origin >= EXMIN and origin <= EXMAX:
                            if origin in idList and len(idList) != 0:
                                identifiersAssoc[(emitterIdGenerator, serv)] = origin
                                utils.constructHeader([serv, 1, utils.SERVER_ID ,emitterIdGenerator, sequence])
                                idList.append(emitterIdGenerator)
                                emitterIdGenerator += 1
                            else:
                                utils.constructHeader([serv, 2, utils.SERVER_ID, origin, sequence])
                                incoming.remove(serv)
                                serv.close()
                        # just a normal hello from the emitter
                        else:
                            utils.constructHeader([serv, 1,utils.SERVER_ID,emitterIdGenerator,sequence])
                            idList.append(emitterIdGenerator)
                            emitterIdGenerator += 1
                    # flw is received
                    elif received and received[0] == 4:
                        type, origin, endpoint, sequence = received
                        # verify if there is it's an flw sent by an exhibitor
                        # if so remove every existence of server and identifier and close its connection
                        if origin in exhibitorAssociation:
                            connection = exhibitorAssociation[origin]
                            outgoing.remove(connection)
                            incoming.remove(connection)
                            del exhibitorAssociation[origin]
                            idList.remove(origin)
                            connection.close()
                        # if the origin has an association
                        # remove every association, ids and also its exhibitor
                        elif (origin, serv) in identifiersAssoc:
                            id = identifiersAssoc[(origin, serv)]
                            if id in exhibitorAssociation:
                                connection = exhibitorAssociation[id]
                                utils.constructHeader([connection, 4, origin, endpoint, sequence])
                                type, _, _, _ = utils.receiveHeader(connection)
                                if type == 1:
                                    utils.constructHeader([serv, 1, utils.SERVER_ID, origin, sequence])
                                    print("OK")
                                    del exhibitorAssociation[id]
                                    del identifiersAssoc[(origin, serv)]
                                    outgoing.remove(connection)
                                    incoming.remove(connection)
                                    idList.remove(id)
                                else:
                                    print("ERROR")
                            else:
                                del identifiersAssoc[(origin,serv)]
                            idList.remove(origin)
                            incoming.remove(serv)
                            serv.close()
                        # if not, just remove the emiter and its id
                        else:
                            utils.constructHeader([serv, 1, 0, origin, sequence])
                            idList.remove(origin)
                            incoming.remove(serv)
                            serv.close()
                    # a message is received and should be sent to an exhibitor
                    elif received and received[0] == 5:
                        type, origin, endpoint, sequence, length, message = received
                        # send to all of the exhibitors
                        if endpoint == 0:
                            if utils.emptyWritable(output): 
                                utils.constructHeader([serv, 2, 0, origin, sequence])
                            else:
                                for out in output:
                                    utils.constructHeader([out, type, origin, endpoint, sequence, length, message])
                                utils.constructHeader([serv, 1, 0, origin, sequence])
                        # send message to a specific exhibitor
                        elif endpoint >= 2**12 and endpoint <= 2**13 - 1:
                            utils.sendMessageToSpecifiedExhibitor(serv, endpoint,exhibitorAssociation,type,origin,  sequence, length,\
                                message)
                        # send to the exhibitor is association with the emiter
                        else:
                            # get the endpoint identifier
                            if (origin, serv) not in identifiersAssoc:
                                utils.constructHeader([serv, 2, 0, origin, sequence])
                                continue
                            endpointId = identifiersAssoc[(origin, serv)]
                            # if the identifier exists, send the message to it to display
                            utils.sendMessageToSpecifiedExhibitor(serv, endpointId,exhibitorAssociation,type,origin,  sequence, length,\
                                message)
                    # a creq message is received and a clist is prepared to be sent 
                    # an option to send to all exhibitors is also included
                    # the clist messages are sorted first for better visualization
                    elif received and received[0] == 6:
                        type, origin, endpoint, sequence = received
                        idList.sort()
                        # send to all exhibitors
                        if endpoint == 0:
                            if not utils.emptyWritable(output):
                                for exhibitor in output:
                                    utils.constructHeader([exhibitor, 7, origin, endpoint, sequence, len(idList), idList])
                                    type, _, _, _ = utils.receiveHeader(exhibitor)
                                    if type == 1:
                                        print("OK")
                                    elif type == 2:
                                        print("ERROR")
                                utils.constructHeader([serv, 1, 0, origin, sequence])
                            else:
                                utils.errorHeader(serv, origin, sequence)
                        # send to a specific exhibitor
                        elif endpoint >= 2**12 and endpoint <= 2**13 - 1:
                            if endpoint in exhibitorAssociation:
                                connection = exhibitorAssociation[endpoint]
                                utils.constructHeader([connection, 7, origin, endpoint, sequence, len(idList), idList])
                                type, _, _, _ = utils.receiveHeader(connection)
                                if type == 1:
                                    print("Ok")
                                    utils.constructHeader([serv, 1, 0, origin, sequence])
                                else:
                                    utils.errorHeader(serv,origin, sequence)
                            else:
                                utils.errorHeader(serv, origin, sequence)
                    # a file message is received 
                    # the server should notify all exhibitors to be ready to receive a file chunk
                    elif received and received[0] == 8:
                        type, origin, endpoint, sequence, fileId, divChunck, lenExtension, extension = received
                        # send file to all exhibitors 
                        if endpoint == 0:
                            if not utils.emptyWritable(output):
                                for out in output:
                                    utils.constructHeader([out, 8, origin, endpoint, sequence,0,0, fileId, divChunck, lenExtension, extension])
                                    while True:
                                        type, _,_,_ = utils.receiveHeader(out)
                                        if type == 1:
                                            print("OK")
                                            break
                                utils.constructHeader([serv, 1, 0, origin, sequence])
                                totalLength = 0
                                # prepare to receive and send file chunks
                                while True:
                                    type, origin, endpoint, sequence, fileId, lenChunk, messaage = utils.receiveFileChunk(serv)
                                    for out in output:
                                        utils.sendFileChunk([out, 9, origin, endpoint, sequence, fileId, lenChunk, messaage])
                                        while True:
                                            type, _,_,_ = utils.receiveHeader(out)
                                            if type == 1:
                                                print("OK")
                                                break
                                    utils.constructHeader([serv, 1, 0, origin, sequence])
                                    totalLength+=1
                                    if totalLength > divChunck:
                                        break
                            else:
                                utils.errorHeader(serv, origin, sequence)
                        # send file to a specific exhibitors
                        elif endpoint >= 2**12 and endpoint <= 2**13 - 1:
                            if endpoint in exhibitorAssociation:
                                connection = exhibitorAssociation[endpoint]
                                utils.constructHeader([connection, 8, origin, endpoint, sequence,0,0, fileId, divChunck, lenExtension, extension])
                                utils.sendChunckToSpecifiedExhibitor(connection, serv, origin, sequence, divChunck)
                            else:
                                utils.errorHeader(serv,origin, sequence)
                        # send file to an exhibitor in an association with the server
                        else:
                            if (origin,serv) in identifiersAssoc:
                                endpointId = identifiersAssoc[(origin, serv)]
                                if endpointId in exhibitorAssociation:
                                    connection = exhibitorAssociation[endpointId]
                                    utils.constructHeader([connection, 8, origin, endpointId, sequence,0,0, fileId, divChunck, lenExtension, extension])
                                    utils.sendChunckToSpecifiedExhibitor(connection, serv, origin, sequence, divChunck)
                                else:
                                    utils.errorHeader(serv,origin,sequence)
                            else:
                                utils.errorHeader(serv,origin, sequence)
                    #remove the server from the input not to cause any problems of existing again    
                    inputs.remove(serv)
    finally:
        server.close()

Server()