import struct

SERVER_ID = 2**16 - 1
FORMAT = f'>HHHH'
# the maximum bytes a file can send
MAXFILE = 2**16
# all the types of messages that can be sent 
TYPES = {
    'OK': 1,
    'ERRO': 2,
    'OI' : 3,
    'FLW' : 4,
    'MSG' : 5,
    'CREQ' : 6,
    'CLIST' : 7,
    'FILE' : 8,
    'FILE_CHUNK': 9
}

def sendMessage(socket, frame):
    socket.sendall(frame)

def sendOi(socket, formAssociation):
    message = input("send ")
    id = message.split(' ')[0]
    constructHeader([socket,TYPES[id.upper()],formAssociation,0,0])
    type, _, endpoint, _ = receiveHeader(socket)
    if type == 1:
        return (True, endpoint)
    elif type == 2:
        return (None, None)

# a helper function for receiving all the headers
def receiveHeader(socket):
    data = socket.recv(8192)
    if data:
        received = struct.unpack(FORMAT, data[:8])
        # handling messages OK | ERROR | OI | FLW | CREQ
        if (received[0] >= 1 and received[0] <= 4) or received[0] == 6:
            type, origin, endpoint, sequence = received
            return (type, origin, endpoint, sequence)
        # handling messages CLIST | MSG | FILE
        elif received[0] == 5 or received[0] == 7 or received[0] == 8:
            format = f'>5H'
            type, origin, endpoint, sequence, length = struct.unpack(format, data[:10])
            # MSG
            if received[0] == 5:
                while len(data[10:]) < length:
                    info = socket.recv(10)
                    data += info
                message = data[10:]
                return (type, origin, endpoint, sequence, length, message)
            # FILE
            elif received[0] == 8:
                format = f'>2H'
                divChunk, lenExtension = struct.unpack(format, data[10:14])
                while len(data[14:]) < lenExtension:
                    info = socket.recv(14)
                    data += info
                extension = data[14:]
                return (type, origin, endpoint, sequence, length, divChunk, lenExtension, extension)
            # CLIST
            else:
                format = f'>{length}H'
                message = struct.unpack(format, data[10:])
                return (type, origin, endpoint, sequence, length, message)

# mounting headers to be sent 
def constructHeader(params):
    socket,type, origin, endpoint, sequence = params[:5]
    frame = None
    format = None
    # handling messages OK | ERROR | OI | FLW | CREQ
    if (type >= 1 and type <= 4) or type == 6:
        frame = [type, origin, endpoint, sequence]
        frame = struct.pack(FORMAT, *frame)
    # handling messages CLIST | MSG 
    elif type == 5 or type == 7:
        length, data = params[5:7]
        if type == 5:
            format = f'>HHHHH{len(data)}s'
            frame = [type, origin, endpoint,sequence, length, data]
        else:
            format = format = f'>HHHHH{len(data)}H'
            frame = [type, origin, endpoint,sequence, length, *data]
        frame = struct.pack(format,  *frame)
    # mount file
    elif type == 8:
        fileId, divChunk, lenExtension, extension = params[7:11]
        format = f'>7H{lenExtension}s'
        frame = [type, origin, endpoint, sequence, fileId, divChunk, lenExtension, extension] 
        frame = struct.pack(format,  *frame)
    sendMessage(socket, frame)

# sending chunks fo file
def sendFileChunk(params):
    socket, type, origin, endpoint, sequence, fileId, lenChunk, chunck = params
    format = f'>6H{lenChunk}s'
    frame = [type, origin, endpoint, sequence, fileId, lenChunk, chunck]
    frame = struct.pack(format, *frame)
    sendMessage(socket, frame)

# receiving chunk of files
def receiveFileChunk(socket):
    data = socket.recv(MAXFILE)
    if data:
        format = f'>6H'
        type, origin, endpoint, sequence, fileId, lenChunk = struct.unpack(format, data[:12])
        while len(data[12:]) < lenChunk:
            info = socket.recv(12)
            data += info
        message = data[12:]
        return (type, origin, endpoint, sequence, fileId, lenChunk, message)

# get the next chunk to be sent
def getNextFrameData(index):
    start = index * (MAXFILE-1)
    end = (index + 1) * (MAXFILE-1)
    return (start, end)

def emptyWritable(out):
    if len(out) == 0:
        print("ERROR")
        return True
    else: return False

def errorHeader(serv, origin, sequence):
    print("ERROR")
    constructHeader([serv, 2, SERVER_ID, origin, sequence])


def sendMessageToSpecifiedExhibitor(serv, endpoint,  association, type, origin, sequence, length, message):
    if endpoint in association:
        connection = association[endpoint]
        constructHeader([connection, type, origin, endpoint, sequence, length, message])
        type, _, _, _ = receiveHeader(connection)
        if type == 1:
            print("Ok")
            constructHeader([serv, 1, SERVER_ID, origin, sequence])
        else:
            errorHeader(serv,origin,sequence)
    else:
        errorHeader(serv, origin, sequence)

def sendChunckToSpecifiedExhibitor(connection, serv, origin, sequence,divChunck):
    type, _, _, _ = receiveHeader(connection)
    if type == 1:
        print("OK")
        constructHeader([serv, 1, origin, 0, 1])
        totalLength = 0
        while True:
            type, origin, endpoint, sequence, fileId, lenChunk, messaage = receiveFileChunk(serv)
            sendFileChunk([connection, 9, origin, endpoint, sequence, fileId, lenChunk, messaage])
            type, _, _, _ = receiveHeader(connection)
            if type == 1:
                print('OK')
                totalLength += 1
                constructHeader([serv, 1, SERVER_ID, origin, sequence])
            else:
                errorHeader(serv,origin,sequence)
            if totalLength > divChunck:
                break                        
    else:
        errorHeader(serv, origin, sequence)