""" Implementing binary trees as embedded list
"""

from random import randint, random
from PrioQueue import PrioQueue, PrioQueueError
from SQueue import SQueue, QueueUnderflow

car_arrive_interval = (1, 2)
car_check_time = (3, 5)

class Simulation:
    def __init__(self, duration):
        self._eventq = PrioQueue()
        self._time = 0
        self._duration = duration

    def run(self):
        while not self._eventq.is_empty():
            event = self._eventq.dequeue()
            self._time = event.time()
            if self._time > self._duration:
                break
            event.run() # may cause new event(s)

    def add_event(self, event): self._eventq.enqueue(event)
    def cur_time(self): return self._time
        
class Customs:
    def __init__(self, gate_num, duration):
        self.simulation = Simulation(duration)
        self.waitline = SQueue()
        self.duration = duration
        self.gates = [0]*gate_num
        self.total_wait_time = 0
        self.total_used_time = 0
        self.car_num = 0

    def wait_time_acc(self, n): self.total_wait_time += n
    def total_time_acc(self, n): self.total_used_time += n
    def car_count_1(self): self.car_num += 1
    def add_event(self, event): self.simulation.add_event(event)

    def cur_time(self): return self.simulation.cur_time()
    def enqueue(self, car): self.waitline.enqueue(car)
    def has_queued_car(self): return not self.waitline.is_empty()
    def next_car(self) : return self.waitline.dequeue()

    def find_gate(self):
        for i in range(len(self.gates)):
            if self.gates[i] == 0:
                self.gates[i] = 1
                return i
        return None
    def free_gate(self, i):
        if self.gates[i] == 1:
            self.gates[i] = 0
        else:
            raise ValueError("Clear gate error.")

    def simulate(self):
        Arrive(0, self) # initially generate one car
        self.simulation.run()
        self.statistics()
        
    def statistics(self):
        print("Simulate " + str(self.duration) + " minutes, for "
              + str(len(self.gates)) + " gates")
        print(self.car_num, "cars pass the customs")
        print("Avarage waiting time:",
              self.total_wait_time/self.car_num)
        print("Avarage passing time:",
              self.total_used_time/self.car_num)
        i = 0
        while not self.waitline.is_empty():
            self.waitline.dequeue()
            i += 1
        print(i, "cars are in waiting line.")

class Car:
    def __init__(self, arrive_time):
        self.time = arrive_time
    def arrive_time(self):
        return self.time

def event_log(time, name): pass
#    print("Event: " + name + ", happens at " + str(time)) 

class Event:
    def __init__(self, event_time, customs):
        self.ctime = event_time
        self.customs = customs

    def __lt__(self, other_event):
        return self.ctime < other_event.ctime
    def __le__(self, other_event):
        return self.ctime <= other_event.ctime

    def time(self): return self.ctime
    def run(self, time, event_name): pass

# a run of coming event will enter the next coming event and
# maybe a check and leave event
class Arrive(Event):
    def __init__(self, arrive_time, customs):
        Event.__init__(self, arrive_time, customs)
        self.customs.add_event(self)

    def run(self):
        time, customs = self.ctime, self.customs
        event_log(time, "car arrive")
        # We genarate the next Arrive event
        Arrive(time + randint(*car_arrive_interval), customs)
        # dealing with current Arrive car
        car = Car(time)
        i = customs.find_gate()
        if i is not None:
            event_log(time, "car check")
            Leave(time + randint(*car_check_time), i, car, customs)
        else:
            customs.enqueue(car)
            
# a run of leaving event will cause some calculations
class Leave(Event):
    def __init__(self, leave_time, gate_num, car, customs):
        Event.__init__(self, leave_time, customs)
        self.car = car
        self.gate_num = gate_num
        self.customs.add_event(self)

    def run(self):
        time, customs = self.ctime, self.customs
        event_log(time, "car leave")
        customs.free_gate(self.gate_num)
        customs.car_count_1()
        customs.total_time_acc(time - self.car.arrive_time())
        if customs.has_queued_car():
            car = customs.next_car()
            i = customs.find_gate()
            event_log(time, "car check")
            customs.wait_time_acc(time - car.arrive_time())
            Leave(time + randint(*car_check_time), i, car, customs)

if __name__ == '__main__':

    cus = Customs(2, 480)
    cus.simulate()
    pass