from dht_common import between, SIZE_OF_HASH, SIZE_OF_IP_ADDRESS

class Base_Node(object):
    def __init__(self, nodeID, documentSize, numItems=0, table=[]):
        self.nodeID = nodeID
        self.nextNode = None
        self.documentSize = documentSize
        self.numItems = numItems
        self.table = dict(table)

        self.numRounds = 0
        self.numMessagesSent = 0
        self.numMessagesRecv = 0
        self.numBytesSent = 0
        self.numBytesRecv = 0

        self.lastNumRounds = 0
        self.lastNumMessagesSent = 0
        self.lastNumMessagesRecv = 0
        self.lastNumBytesSent = 0
        self.lastNumBytesRecv = 0

    def insert(self):
        self.numItems += 1

        self.numRounds += 1
        self.numMessagesSent += 1
        self.numMessagesRecv += 1
        self.numBytesSent += SIZE_OF_HASH
        self.numBytesRecv += self.documentSize

    # There has to be some way of inserting finger table values,
    # so this is it (Normally only the DHT should be interfacing with it)
    def insert_relation(self, nodeID, fingerTableValue):
        # nextNode is kept up to date so that when doing finger table searches,
        # we can know when we own something or it needs to go somewhere else
        if not self.nextNode or between(nodeID, self.nodeID, self.nextNode):
            self.nextNode = nodeID
        self.table[nodeID] = fingerTableValue

    def retrieve(self):
        self.numRounds += 1
        self.numMessagesSent += 1
        self.numMessagesRecv += 1
        self.numBytesSent += self.documentSize
        self.numBytesRecv += SIZE_OF_HASH

    def get_finger_table_val(self, searchID):
        # if we should own this ID, say so
        if between(searchID, self.nodeID, self.nextNode):
            retval = (self.nodeID, -1)
        else: # otherwise, find the best option in the finger table and give that back
            options = [x for (x,y) in self.table.items() if x <= searchID]
            closest = max(options) if len(options) > 0 else max([x for (x,y) in self.table.items()])
            retval = (closest, self.table[closest])

        self.numRounds += 1
        self.numMessagesSent += 1
        self.numMessagesRecv += 1
        self.numBytesSent += SIZE_OF_HASH + SIZE_OF_IP_ADDRESS
        self.numBytesRecv += SIZE_OF_HASH

        return retval

    def get_num_rounds(self):
        self.lastNumRounds = self.numRounds
        return self.numRounds

    def get_recent_num_rounds(self):
        retval = self.numRounds - self.lastNumRounds
        self.lastNumRounds = self.numRounds
        return retval

    def get_num_messages_sent(self):
        self.lastNumMessagesSent = self.numMessagesSent
        return self.numMessagesSent

    def get_recent_num_messages_sent(self):
        retval = self.numMessagesSent - self.lastNumMessagesSent
        self.lastNumMessagesSent = self.numMessagesSent
        return retval

    def get_num_messages_recv(self):
        self.lastNumMessagesRecv = self.numMessagesRecv
        return self.numMessagesRecv

    def get_recent_num_messages_recv(self):
        retval = self.numMessagesRecv - self.lastNumMessagesRecv
        self.lastNumMessagesRecv = self.numMessagesRecv
        return retval

    def get_num_bytes_sent(self):
        self.lastNumBytesSent = self.numBytesSent
        return self.numBytesSent

    def get_recent_num_bytes_sent(self):
        retval = self.numBytesSent - self.lastNumBytesSent
        self.lastNumBytesSent = self.numBytesSent
        return retval

    def get_num_bytes_recv(self):
        self.lastNumBytesRecv = self.numBytesRecv
        return self.numBytesRecv

    def get_recent_num_bytes_recv(self):
        retval = self.numBytesRecv - self.lastNumBytesRecv
        self.lastNumBytesRecv = self.numBytesRecv
        return retval

# Normally this file is a class to be used elsewhere,
# but if you run it directly it performs some rudimentary unit tests
# TODO: Add unit tests for size calculations
if __name__ == "__main__":
    SIZE_OF_DOCUMENTS_IN_TEST = 1024
    test = Base_Node(0, SIZE_OF_DOCUMENTS_IN_TEST)
    
    [test.insert(x, x) for x in range(10)]
    retrievals = [test.retrieve(x) for x in range(10)]
    print("Insert and retrieval fires correctly.")
    
    test.retrieve("nothere")
    print("Nonexistant entries fires correctly.")

    test.insert_relation(1, 1)
    test.insert_relation(2, 2)
    test.insert_relation(4, 4)
    test.insert_relation(8, 8)
    assert test.get_finger_table_val(1) == (1, 1)
    assert test.get_finger_table_val(3) == (2, 2)
    assert test.get_finger_table_val(4) == (4, 4)
    assert test.get_finger_table_val(7) == (4, 4)
    assert test.get_finger_table_val(0) == (0, -1)
    print("Finger table stuff working correctly")