import random
import sys
sys.path.append("../../src/")

from DEVS import AtomicDEVS, CoupledDEVS

from mpi4py import MPI

class CircleNodeState(object):
    def __init__(self):
        self.event = None
        self.queue = []
        self.nr = 0
        self.generated = 0

    def copy(self):
        a = CircleNodeState()
        a.event = self.event
        a.queue = list(self.queue)
        a.nr = self.nr
        a.generated = self.generated
        return a

    def __hash__(self):
        return self.event + sum(self.queue)

    def __eq__(self, other):
        # For memoization
        if self.event == other.event and self.queue == other.queue and self.nr == other.nr and self.generated == other.generated:
            return True
        else:
            return False

    def __str__(self):
        return "%s (%s)" % (self.event, self.queue)

class CircleNode(AtomicDEVS):
    def __init__(self, nr, count, multiplier):
        AtomicDEVS.__init__(self, "CircleNode" + str(nr))
        self.inport = self.addInPort("input")
        self.outport = self.addOutPort("output")
        self.state = CircleNodeState()
        #self.state.event = random.randint(2, 100)
        self.state.nr = nr
        self.state.generated = 0
        self.state.event = nr
        self.count = count
        self.multiplier = multiplier

    def intTransition(self):
        if 0.6 * self.count < self.state.nr < 0.7 * self.count:
            for _ in xrange(self.state.nr*self.multiplier):
                pass
        if self.state.queue:
            self.state.event = self.state.queue.pop()
        else:
            self.state.event = None
        self.state.generated += 1
        if self.state.generated == 10:
            self.state.nr = (self.state.nr + 1) % self.count
            self.state.generated = 0
        return self.state

    def extTransition(self, inputs):
        if self.state.event is None:
            self.state.event = inputs[self.inport][0]
        else:
            self.state.queue.append(inputs[self.inport][0])
        return self.state

    def timeAdvance(self):
        if self.state.event is None:
            return float('inf')
        else:
            #return self.state.event
            return 1.0

    def outputFnc(self):
        return {self.outport: [self.state.event]}

class MovingCircle(CoupledDEVS):
    def __init__(self, count, multiplier):
        import math
        CoupledDEVS.__init__(self, "Circle")
        nodes = []
        try:
            from mpi4py import MPI
            pernode = float(count) / MPI.COMM_WORLD.Get_size()
        except ImportError:
            pernode = float(count)
        for i in range(count):
            nodes.append(self.addSubModel(CircleNode(i, count, multiplier), math.floor(i/pernode)))
        for index in range(len(nodes)):
            self.connectPorts(nodes[index-1].outport, nodes[index].inport)

if __name__ == "__main__":
    random.seed(1)
    from simulator import Simulator
    model = MovingCircle(int(sys.argv[1]), int(sys.argv[2]))
    sim = Simulator(model)
    sim.setTerminationTime(int(sys.argv[3]))
    #sim.setVerbose(True)
    sim.setMessageCopy('none')
    sim.setStateSaving('custom')
    sim.setGVTInterval(1 if int(argv[1]) < 500 else 5)
    from allocator import MyAllocator
    sim.setInitialAllocator(MyAllocator())
    #sim.setDrawModel(True, 'model.dot', True)
    if sys.argv[4] == "True":
        sim.setActivityRelocatorBasicBoundary(1.1)
    sim.setMemoization(True)
    sim.setSchedulerSortedList()
    #sim.setActivityTracking(True)
    #sim.setShowProgress()
    sim.simulate()
