#input
values = #value#;
import math
import copy
import numpy as np
#input number
arrival_mean = values[0]
serve_range = [values[1],values[2]]
end_time = values[3]
if not (( arrival_mean > 0 and end_time > 0 ) and serve_range[1] > serve_range[0] ):
    show('Please enter proper parameters.')
    assert()
class Queue:
#class constructor: initialize queue
    def __init__(self,arrival_mean,serve_range,end_time):
        self.curr_time = 0
        self.queue = []
        self.customer_num = 0
        
        self.arrival_mean = arrival_mean
        self.serve_range = serve_range
        
        self.next_event = 'arrive'
        self.next_arrive = math.inf
        self.served = 0
        self.serving = {}
        self.serving_time_left = math.inf
        self.end_time = end_time
        self.served_cus_list = []
        self.queue_time_list = [[0,0]]
        self.events = []
    #end of constructor
    
    # randomly draw a arrival time
    # change self.next_arrive
    # no return value
    def draw_arrive(self):
        show(html('Draw an interarival time.'))
        Rand_Num = randint(0, 99)
        show(html('Random number drawn: ' + str(Rand_Num)))
        
        Cal_Rand_Num = Rand_Num/100
        arrival_time = -1 * self.arrival_mean * log(1 - Cal_Rand_Num)
        show(html('Next interarival time is ' + str(n(arrival_time,digits=4)) + ' minutes.'))
        
        self.next_arrive = arrival_time
        
	# draw serving time
    # return serve_time for function start_serving
    def draw_serve_time(self):
        a = self.serve_range[0]
        b = self.serve_range[1]
        
        show(html('Draw a service time.'))
        Rand_Num = randint(0, 99)
        show(html('Random number drawn: ' + str(Rand_Num)))
        
        Cal_Rand_Num = Rand_Num/100
        serve_time = a + (b-a) * Cal_Rand_Num
        show(html('Service will last ' + str(n(serve_time,digits=4)) + ' minutes.'))
        return serve_time
    
    # action when customer arrive
    def arrive(self):
        self.customer_num += 1
        customer = {}
        customer['Num'] = self.customer_num
        customer['arrive_time'] = self.curr_time
        self.queue += [customer]
        show(html('-'))
        show(html('Customer number ') + str(self.customer_num) + ' has arrived')
    
    #action when head of queue being start serving
    def start_service(self):
        customer = copy.deepcopy(self.queue[0])
        self.queue.pop(0)
        customer['start_service_time'] = self.curr_time
        customer['wait_time'] = customer['start_service_time'] - customer['arrive_time']
        # show message customer is being served
        show(html('-'))
        show(html('Customer number ' + str(customer['Num']) + ' is being served'))
        self.serving_time_left = self.draw_serve_time()
        customer['service_time'] = self.serving_time_left
        customer['serve_end_time'] = self.curr_time + self.serving_time_left
        self.serving = customer
    
    #action when service end
    def end_service(self):
        show(html('-'))
        show(html('Service of Customer number ' + str(self.serving['Num']) + ' is ended'))
        customer = copy.deepcopy(self.serving)
        customer['end_service_time'] = self.curr_time
        customer['service_time'] = customer['end_service_time'] - customer['start_service_time']
        self.served_cus_list += [customer]
        self.serving = {}
        self.serving_time_left = math.inf
        self.served += 1
        
    
    # show current time
    def show_curr_time(self):
        # display current time
        show(html('-'))
        show('Current time is ' + str(n(self.curr_time,digits=4)) + ' minutes after start.')
        if self.next_arrive != math.inf:
            show(html('Next customer will arrive in ' + str(n(self.next_arrive,digits=4)) + ' minutes later.'))
        
        if self.serving != {}:
            show(html('serving time left: ' + str(n(self.serving_time_left,digits=4)) + ' minutes.'))
    
    
    
    # determine time to pass and pass time
    def det_pass_time(self):
        #debug: announce this is running
        #show('det_pass running')
        # comapare next arrive time and serving time left
        #show('service time left: ' + str(n(self.serving_time_left)))
        #show('next_arrive: ' + str(n(self.next_arrive)))
        if (self.serving_time_left > self.next_arrive):
            self.next_event = 'arrive'
            time_to_pass = self.next_arrive
            #show(1)
        else:
            self.next_event = 'serv_end'
            time_to_pass = self.serving_time_left
            #show(2)
        
        #pass time
        show(html('-'))
        show(str(n(time_to_pass,digits=4)) + ' minutes passed.')
        show(html('-'))
        self.curr_time = self.curr_time + time_to_pass
        self.next_arrive = self.next_arrive - time_to_pass
        self.serving_time_left = self.serving_time_left - time_to_pass
    # show queue
    def show_curr_queue(self):
        show(html('-'))
        begin_code = '\\\\[ \\\\begin{array}{}'
        end_code = ' \\\\end{array} \\\\]'
        
        #server code
        server_code = '\\\\begin{array}{} '
        server_code += '\\\\colorbox{cyan}{Serving:} '
        server_code += '\\\\\\\\ \\\\colorbox{cyan}{Service end time:} '
        server_code += '\\\\end{array} '
        if self.serving != {}:
            server_code += '& \\\\begin{array}{} '
            server_code += '\\\\colorbox{LightGreen}{C' + str(self.serving['Num']) + '}'
            server_code += '\\\\\\\\ \\\\colorbox{LightGreen}{ '+ str(n(self.serving['serve_end_time'],digits=4)) + '} '
            server_code += '\\\\end{array}'
        server_code += '\\\\\\\\ \\\\\\\\'
        #queue code
        queue_row_count = 0
        queue_code = '\\\\begin{array}{} '
        queue_code += '\\\\colorbox{orange}{Queue:}'
        queue_code += '\\\\\\\\ \\\\colorbox{orange}{Arrival time:}'
        queue_code += '\\\\end{array} '
        for customer in self.queue:
            queue_code += '& \\\\begin{array}{} '
            queue_code += '\\\\colorbox{yellow}{C' + str(customer['Num']) + '} '
            queue_code += '\\\\\\\\ \\\\colorbox{yellow}{' + str(n(customer['arrive_time'],digits=4)) + '} '
            queue_code += '\\\\end{array} '
            queue_row_count += 1
            if queue_row_count > 11:
                queue_code += '\\\\\\\\ '
                queue_row_count = 0
        
        show(html(begin_code + server_code + queue_code + end_code))
    
    #add event to events list
    def add_event(self,type):
        if type == 'first':
            event = {}
            event['time'] = 0
            event['type'] = 'first'
            event['interarrival_time'] = n(self.next_arrive,digits=4)
            event['next_arrival_time'] = str(n(self.next_arrive,digits=4)) + '(C1)'
            event['server_status'] = 'idle'
            event['queue_length'] = 0
            self.events += [event]
        
        if type == 'serv_end':
            event = {}
            event['time'] = n(self.curr_time,digits=4)
            event['type'] = 'serv_end'
            event['server_status'] = 'idle'
            event['queue_length'] = len(self.queue)
            self.events += [event]
        
        if type == 'serv_end_start':
            event = {}
            event['time'] = n(self.curr_time,digits=4)
            event['type'] = 'serv_end_start'
            event['server_status'] = 'busy'
            event['service_time'] = n(self.serving_time_left,digits=4)
            event['service_ending_time'] = n(self.serving['serve_end_time'],digits=4)
            event['queue_length'] = len(self.queue)
            event['wait_time'] = str(n(self.serving['wait_time'],digits=4)) + '(C' + str(self.serving['Num']) +')'
            self.events += [event]
        
        if type == 'arrive':
            next_arrive_time = self.next_arrive + self.curr_time
            event = {}
            event['time'] = n(self.curr_time,digits=4)
            event['type'] = 'arrive'
            event['interarrival_time'] = n(self.next_arrive,digits=4)
            event['next_arrival_time'] = str(n(next_arrive_time,digits=4)) + '(C' + str(self.customer_num + 1) + ')'
            event['server_status'] = 'busy'
            event['queue_length'] = len(self.queue)
            
            if next_arrive_time > self.end_time:
                event['next_arrival_time'] += ' (not accepted)'
            self.events += [event]
        
        if type == 'arrive_start':
            next_arrive_time = self.next_arrive + self.curr_time
            event = {}
            event['time'] = n(self.curr_time,digits=4)
            event['type'] = 'arrive_start'
            event['interarrival_time'] = n(self.next_arrive,digits=4)
            event['next_arrival_time'] = str(n(next_arrive_time,digits=4)) + '(C' + str(self.customer_num + 1) + ')'
            event['server_status'] = 'busy'
            event['service_time'] = n(self.serving_time_left,digits=4)
            event['service_ending_time'] = n(self.serving['serve_end_time'],digits=4)
            event['queue_length'] = len(self.queue)
            event['wait_time'] = str(n(self.serving['wait_time'],digits=4)) + '(C' + str(self.serving['Num']) +')'
            if next_arrive_time > self.end_time:
                event['next_arrival_time'] += ' (not accepted)'
            
            self.events += [event]
    
#main
P = Queue(arrival_mean,serve_range,end_time)
#first arrive
P.show_curr_time()
P.show_curr_queue()
P.draw_arrive()
P.add_event('first')
while True:
    P.det_pass_time()
    if P.curr_time > P.end_time:
        break
    P.show_curr_time()
    if P.next_event == 'serv_end':
        P.end_service()
        event_type = 'serv_end'
        P.show_curr_queue()
        P.queue_time_list += [[P.curr_time,len(P.queue)]]
        if len(P.queue) > 0:
            P.start_service()
            event_type = 'serv_end_start'
            P.show_curr_time()
            P.show_curr_queue()
            P.queue_time_list += [[P.curr_time,len(P.queue)]]
    if P.next_event == 'arrive':
        P.arrive()
        P.draw_arrive()
        event_type = 'arrive'
        P.show_curr_queue()
        P.queue_time_list += [[P.curr_time,len(P.queue)]]        
        if P.serving == {}:
            P.start_service()
            event_type = 'arrive_start'
            P.show_curr_time()
            P.show_curr_queue()
            P.queue_time_list += [[P.curr_time,len(P.queue)]]
    
    P.add_event(event_type)
# passed queue up time
# change arrive time to infinity
P.next_arrive = math.inf
P.show_curr_time()
show(html('-'))
show('Current time exceeds Queue up ending time, no more customer is allowed to enter the queue.')
show('Customers in the queue will still be served.')
show(html('-'))
# serve customers still serving and left in queue
if P.next_event == 'arrive':
    P.show_curr_time()
    if P.serving != {}:
        P.det_pass_time()
        P.next_arrive = math.inf
        P.show_curr_time()
        P.end_service()
        event_type = 'serv_end'
else:
    P.show_curr_time()
    P.end_service()
    event_type = 'serv_end'
while True:
    P.show_curr_time()
    P.show_curr_queue()
    if len(P.queue) == 0:
        break
    P.start_service()
    P.add_event('serv_end_start')
    P.show_curr_queue()
    P.queue_time_list += [[P.curr_time,len(P.queue)]]
    P.det_pass_time()
    P.next_arrive = math.inf
    P.show_curr_time()
    P.end_service()
P.add_event('serv_end')
show('Service session has ended, Here are the list of events and statistics.')
#show(P.queue_time_list)
#calculate average wait time and average service time by cus list
cus_table = []
for cus in P.served_cus_list:
    cus_table += [[cus['Num'],cus['arrive_time'],cus['start_service_time'],cus['end_service_time'],cus['wait_time'],cus['service_time']]]
#show(table(cus_table))
#calculate average wait time to variable 'wait_mean'
list_wait = [cus[4] for cus in cus_table]
wait_mean = np.mean(list_wait)
#calculate average service time to variable 'serv_mean'
list_serv = [cus[5] for cus in cus_table]
serv_mean = np.mean(list_serv)
#calculate average queue length from P.queue_time_list to variable 'aver_queue_length'
queue_time_list = copy.deepcopy(P.queue_time_list)
queue_time_list += [queue_time_list[-1]]
list_timelength_queuelength = []
for i in range(len(P.queue_time_list)):
    list_timelength_queuelength += [[queue_time_list[i+1][0] - queue_time_list[i][0] , queue_time_list[i][1]]]
total_time = queue_time_list[-1][0]
if end_time > total_time:
    total_time = end_time
weight_sum = 0
for vl in list_timelength_queuelength:
    weight_sum += vl[0] * vl[1]
aver_queue_length = weight_sum / total_time
#construct event table
event_table = []
for ev in P.events:
    if ev['type'] == 'first':
        event_table += [[ ev['time'] , ev['interarrival_time'] , ev['next_arrival_time'] , ev['server_status'],'','',ev['queue_length'],'']]
    
    if ev['type'] == 'serv_end':
        event_table += [[ ev['time'],'','',ev['server_status'],'','',ev['queue_length'],'']]
    
    if ev['type'] == 'serv_end_start':
        event_table += [[ ev['time'],'','',ev['server_status'],ev['service_time'],ev['service_ending_time'],ev['queue_length'],ev['wait_time']]]
    
    if ev['type'] == 'arrive':
        event_table += [[ ev['time'] , ev['interarrival_time'] , ev['next_arrival_time'] , ev['server_status'] ,'','',ev['queue_length'],'']]
    
    if ev['type'] == 'arrive_start':
        event_table += [[ ev['time'] , ev['interarrival_time'] , ev['next_arrival_time'] , ev['server_status'] ,ev['service_time'],ev['service_ending_time'],ev['queue_length'],ev['wait_time']]]
event_table = [['Time','Interarrival time','Next arrival time','Server state','Service time','Service ending time','Queue \n length','waiting time']] + event_table
#convert custable to readable format
read_cus_table = copy.deepcopy(cus_table)
for row in read_cus_table:
    for i in range(5):
        row[i+1] = n(row[i+1],digits=4)
read_cus_table = [['Customer','Arrival Time','Start service time','End service time','Wait time','Service time']] + read_cus_table
#show event table
show(html('-'))
show(html('Events Table'))
show(table(event_table,frame = True))
#show customer table 
show(html('-'))
show(html('Customers Table'))
show(table(read_cus_table,frame = True))
#show number of served customers, mean waiting time, mean service time and average queue length
show(html('-'))
show(html('Number of customers served: ' + str(P.served)))
show(html('Average waiting time is ' + str(n(wait_mean,digits=4)) + ' minutes.'))
show(html('Average service time is ' + str(n(serv_mean,digits=4)) + ' minutes.'))
show(html('Average queue length is ' + str(n(aver_queue_length,digits=4)) + '.'))