class MessageProcessor(threading.Thread):
    """The message processor processes all incoming messages, and handles all 
        election and host-related messages. It also handles checking of hosts/players
        leaving
    """
    
    MOVE, CHAT, JOIN, WELCOME, LEAVE, FREEZE, UNFREEZE, NONE= range(8)
    HISTORY_MESSAGE_TYPE = 'HISTORY_MESSAGE'
    KEEP_ALIVE_TYPE = 'KEEP-ALIVE'
    SERVER_MOVE_TYPE = 'SERVER-MESSAGE'
    SERVER_ELECTED_TYPE = 'SERVER-ELECTED'
    SERVER_RESPONSE_TYPE = 'SERVER-RESPONSE'
    
    def __init__(self, service, sessionUUID, senderUUID, peerWaitingTime=30, messageWaitingTime=2):
        # MulticastMessaging subclass.
        if not isinstance(service, Service.OneToManyService):
            raise OneToManyServiceError
        self.service = service

        # Identifiers.
        self.sessionUUID = sessionUUID
        self.senderUUID  = senderUUID
        self.players = None
        
        #The last time the keep-alive message was sent (in seconds)
        #self.keepAliveSentTime = 0
        #The time to wait between keep-alive messages
        #self.keepAliveTreshold = 1
        #self.keepAliveLeftTime = 5 #the time to wait for a keep-alive message to arrive before we disconnect the player 
        #self.keepAliveMessages = {} # Contains the last time a keep-alive message was received per player

        # Message storage.
        self.inbox       = Queue.Queue()
        self.outbox      = Queue.Queue()

        # Settings.
        self.peerWaitingTime    = int(peerWaitingTime)
        self.messageWaitingTime = int(messageWaitingTime)

        # Thread state variables.
        self.alive = True
        self.die = False
        self.lock = threading.Condition()
        
        #Keep-alive.
        
        self.playerRTT = {}
        self.playerRTT['max'] = -1
        self.lastKeepAliveSendTime = time.time()
        self.sendAfterFirstKeepAlive = False
        self.receivedAliveMessages = {}
        
        # election based variables
        self.host = None
        # the time at which a new host was selected. If we get any new elected messages, we can ignore them
        # if they happened before this time
        self.hostElectedTime = 0
        # list of the moves and joins we sent out, and haven't been replied to yet
        # if we don't get a reply in 5 roundtrip times, the host has left, and the 
        # message wasn't delivered
        self.messagesAwaitingApproval = []

        # Other things.
        self._startedWaitingForMessages = None
        self.NTPoffset = 0

        # Register this Global State's session UUID with the service as a
        # valid destination.
        self.service.registerDestination(self.sessionUUID)

        super(MessageProcessor, self).__init__()



    ##########################################################################
    # Message processing.                                                    #
    ##########################################################################

    def sendMessage(self, message, sendToSelf = True):
        """Enqueue a message to be sent."""
        with self.lock:
            # print "\tGlobalState.sendMessage()", message
            envelope = self._wrapMessage(message)
            if sendToSelf:
                self.inbox.put((self.senderUUID, message))
            with self.service.lock:
                # print "\tGlobalState._sendMessage()", envelope
                self.service.sendMessage(self.sessionUUID, envelope)
                self.service.lock.notifyAll()

    def countReceivedMessages(self):
        with self.lock:
            return self.inbox.qsize()


    def receiveMessage(self):
        with self.lock:
            return self.inbox.get()

    def sendKeepAliveMessage(self):
        with self.service.lock:
            # send a keepalive message which contains the NTP time at which it was sent, so we can calculate
            # Roundtrip time, and see if a player leaves the game
            if self.useNTP:
                self.sendMessage({'type' : self.KEEP_ALIVE_TYPE, 'originUUID':self.senderUUID, 'timestamp' : time.time() + self.NTPoffset}, False)
            else:
                self.sendMessage({'type' : self.KEEP_ALIVE_TYPE, 'originUUID':self.senderUUID, 'timestamp' : 0}, False)
            
    def receiveKeepAliveMessage(self, message):
        with self.lock:
            uuid = message['originUUID']
            #calculate the time difference between the time the message was sent and received
            timeDiff = (time.time() + self.NTPoffset) - message['timestamp']
            #calculate the roundtrip time
            rtt = 2 * timeDiff
            
            if self.useNTP and message['timestamp'] != 0:            
                self.receivedAliveMessages[uuid] = message['timestamp']
            else:
                rtt = 2
                self.receivedAliveMessages[uuid] = time.time()
            
            if not uuid in self.playerRTT.keys():
                self.playerRTT[uuid] = rtt
            else:
                # calculate the average between the previous rtt for this player, and the current one
                self.playerRTT[uuid] = (self.playerRTT[uuid] + rtt) / 2
            
            max = 0
            #calculate the maximum of all the roundtrip times
            if not 'avg' in self.playerRTT.keys():
                self.playerRTT['max'] = rtt
            else:
                for key in self.playerRTT.keys():
                    if (key != 'max' and key != 'avg'):    
                        if self.playerRTT[key] > max:
                            max = self.playerRTT[key]
                if max > self.playerRTT['max']:
                    self.playerRTT['max'] = (max + self.playerRTT['max'])/2
            
            #calculate the average roundtrip time
            if not 'avg' in self.playerRTT.keys():
                self.playerRTT['avg'] = rtt
            else:
                avg = 0
                count = 0
                for key in self.playerRTT.keys():
                    if (key != 'max' and key != 'avg'):       
                        avg += self.playerRTT[key]
                        count += 1
                avg = avg / count
                self.playerRTT['avg'] = avg
            

    #checks if any players disconnected
    def checkKeepAlive(self):
        with self.lock:
            for key in self.receivedAliveMessages.keys():
                #a player has left if he hasn't send a message in 5 roundtrip times
                if  5 * self.playerRTT['max'] + self.receivedAliveMessages[key] + 1 < time.time() + self.NTPoffset:
                    
                    del self.receivedAliveMessages[key]
                    del self.playerRTT[key]
                    
                    # if the player who left was the host, we have to determine the new host
                    if self.host != None and key == self.host:
                        # this player becomes the host if he has the highest UUID of all players
                        if len(self.receivedAliveMessages) < 1 or self.senderUUID > max (self.receivedAliveMessages.keys()):
                            electedMessage = {'type' : self.SERVER_ELECTED_TYPE, 'host' : self.senderUUID, 'timestamp' : time.time() + self.NTPoffset}
                            self.sendMessage(electedMessage)
                            self.receiveElectedMessage({'message':electedMessage})
                    
                    self.hostLeftAt = time.time() + self.NTPoffset
                    self.inbox.put((key, {'type':4}))
                    
                    
    def receiveElectedMessage(self, envelope):
        if envelope['message']['timestamp'] > self.hostElectedTime:
            self.hostElectedTime = envelope['message']['timestamp']
            self.host = envelope['message']['host']
                  
    def receiveLeaveMessage(self, envelope):            
        players = copy.deepcopy(self.players)
        del players[envelope['originUUID']]
        
        if not self.useNTP and self.host != None and envelope['originUUID'] == self.host:
            if len(self.players) == 1 or self.senderUUID > max (players.keys()):
                electedMessage = {'type' : self.SERVER_ELECTED_TYPE, 'host' : self.senderUUID, 'timestamp' : time.time() + self.NTPoffset}
                self.sendMessage(electedMessage)
                self.receiveElectedMessage({'message':electedMessage})
        self.inbox.put((envelope['originUUID'], {'type':4}))
        # if we can't use ntp, we can't rely on the keep-alive messages
        # to pick a new host, so manually select a new host
    
    def _wrapMessage(self, message):
        """Wrap a message in an envelope to prepare it for sending."""

        envelope = {}
        # Add the timestamp to the envelope.
        envelope['timestamp'] = time.time() + self.NTPoffset
        # Add the sender UUUID and the original sender UUID to the envelope
        # (which is always ourselves).
        envelope['senderUUID'] = envelope['originUUID'] = self.senderUUID
        # Store the message in the envelope.
        envelope['message'] = message
        return envelope
        
    def messageApproval(self, message):
        """ Approves a message sent by this user """
        
        if message['target'] == self.senderUUID:
            # If we received a history message, it means we have correctly joined the game
            if message['type'] == self.HISTORY_MESSAGE_TYPE:
                for m in self.messagesAwaitingApproval:
                    # Remove the join message
                    if m['type'] == self.JOIN:
                        self.messagesAwaitingApproval.remove(m)
            # If we received a server response message, it means the move we sent was processed
            if message['type'] == self.SERVER_RESPONSE_TYPE:
                for m in self.messagesAwaitingApproval:
                    # Delete the move message
                    if m['type'] == self.SERVER_MOVE_TYPE and m['col'] == message['col']:
                        self.messagesAwaitingApproval.remove(m)
                        
                        
    def checkApproval(self):
        """ Check if a message sent by this user was approved and sends it again if it timed out """
        for m in self.messagesAwaitingApproval:
            # wait till a new host was elected before sending the message again
            if m['timestamp'] < self.hostElectedTime:
                # a message times out if it wasn't approved after 5 roundtrip times
                if  5 * self.playerRTT['max'] + m['timestamp'] + 1 < time.time() + self.NTPoffset:
                    # send the message again
                    self.sendMessage(m)
                    self.messagesAwaitingApproval.remove(m)
                    if self.senderUUID != self.host:
                        m['timestamp'] = time.time() + self.NTPoffset
                        self.messagesAwaitingApproval.append(m)


    def processMessage(self, envelope):
        """Process a message and put it in the inbox if its meant for us"""

        # If the message was a request to make a move, do the move, and let the player who
        # did the move know his message was processed
        if envelope['message']['type'] == self.SERVER_MOVE_TYPE:
            if envelope['message']['target'] == self.senderUUID:
                with self.lock:
                    self.sendMessage({'type' : self.SERVER_RESPONSE_TYPE, 'col' : envelope['message']['col'], 'target' : envelope['originUUID']})
                    self.inbox.put((envelope['originUUID'], envelope['message']))
        # If the message was an approval of a message sent by this user, approve that message
        elif envelope['message']['type'] == self.SERVER_RESPONSE_TYPE:
            self.messageApproval(envelope['message'])
        # Process a keep-alive message
        elif envelope['message']['type'] == self.KEEP_ALIVE_TYPE:
            self.receiveKeepAliveMessage(envelope)
        # Process an elected message
        elif envelope['message']['type'] == self.SERVER_ELECTED_TYPE:
            self.receiveElectedMessage(envelope)
        #if the message is a LEAVE message
        elif envelope['message']['type'] == 4:
            self.receiveLeaveMessage(envelope)
        else:    
            # Move the message to the inbox queue, so it can be retrieved.
            with self.lock:
                self.inbox.put((envelope['originUUID'], envelope['message']))

    def erase(self):
        """Erase the global state."""
        # Remove this Global State's session UUID as a valid destination.
        self.service.removeDestination(self.sessionUUID)
        # Reset the database.
        self._dbCur.execute("DELETE FROM MessageHistory")
        self._dbCon.commit()
        self.clock = VectorClock()
        self.clock.add(self.senderUUID)
        
    def getNTPoffset(self):
        # get the NTP offset of this computers clock
        try:
            client = ntplib.NTPClient()
            response = client.request('europe.pool.ntp.org', version=3)
            if response.leap != 3:
                self.NTPoffset = response.offset
            self.useNTP = True
        except:
            print 'Warning! NTP server could not be reached'
            self.useNTP = False
            pass
            
        # print 'NTPoffset is now: ' + str(self.NTPoffset)

    def run(self):
        self.getNTPoffset()

        while self.alive:
            # Check if it's time to send liveness messages.
            # TODO

            # Retrieve incoming messages and store them in the waiting room.            
            with self.lock:
                with self.service.lock:
                    if self.service.countReceivedMessages(self.sessionUUID) > 0:
                        envelope = self.service.receiveMessage(self.sessionUUID)
                        # Ignore our own messages.
                        
                        if envelope['senderUUID'] != self.senderUUID:
                            self.processMessage(envelope)
                    # If there are other players, send keepalive messages with an interval suitable
                    # to their roundtrip time
                    if self.useNTP and ('avg' in self.playerRTT.keys()):
                        if(float(min(self.playerRTT['avg'], 1)) < float(time.time() - self.lastKeepAliveSendTime)):
                            self.sendKeepAliveMessage()
                            self.checkKeepAlive()
                            # check if move and join requests were received
                            self.checkApproval()
                            self.lastKeepAliveSendTime = time.time()
                    elif time.time() - self.lastKeepAliveSendTime > 1:
                        self.keepAliveSent = True
                        self.sendKeepAliveMessage()
                        self.checkKeepAlive()
                        self.checkApproval()
                        self.lastKeepAliveSendTime = time.time()

            # 100 refreshes per second is plenty.
            time.sleep(0.01)


    def kill(self):
        # Let the thread know it should commit suicide. But first let it send
        # all remaining messages.
        # while self.outbox.qsize() > 0:
        #     if 
        with self.lock:
            self._commitSuicide()


    def _commitSuicide(self):
        """Commit suicide when asked to. The lock must be acquired before
        calling this method.
        """

        # Stop us from running any further.
        self.alive = False