# Author     : Steve Miller
# Course     : 22C:016 SCA
# Student Id : 478465327

# Phase 1 of Project 2

def log(msg, t=0.0) :
    ''' Logs a message. Inserts a timestamp if provided. '''
    
    if t :
        msg = ("%4.2f" % t).rjust(7) + ' ' + msg
    print msg
    
def insert(enroute, id, arrivalTime) :
    ''' Inserts a flight in the enroute list in order of arrival time. '''
        
    # This makes the simulation much faster since the arriving flights
    # are always at the front of the enroute list.
    
    # Would it be even faster to sort enroute by descending
    # arrival time and remove flights from the end of the list?
    
    # Insert the flight in front of the first flight that arrives later
    aTime = arrivalTime[id]
    for i in range(len(enroute)) :
        if aTime < arrivalTime[enroute[i]] :
            enroute.insert(i,id)
            return
        
    # handle the case where the flight is the latest flight so far
    enroute.append(id)

def readInputs(filename, aircraftType, numberPassengers, 
                 arrivalTime, phaseStartTime, enroute) :
    ''' Reads flights enroute in filename and loads data structures. '''
    
    for line in open(filename) :         # for each flight enroute
        a = line.split()
        id = a[1]                          # extract flight id
        arrivalTime[id]      = float(a[0]) # set arrival time of aircraft
        phaseStartTime[id]   = 0.0         # set start time of flight phase
        aircraftType[id]     = a[2]        # set type of aircraft
        numberPassengers[id] = int(a[3])   # set number of passengers
        insert(enroute, id, arrivalTime)   # add flight to enroute list

def getNewArrivals(t, enroute, holding, arrivalTime, phaseStartTime) :
    ''' Moves arrivals at time t into a holding pattern. '''
    
    for id in enroute :              # for each flight enroute
        if arrivalTime[id] >= t :    # if this flight arrives in future
            return                        # stop - remaining flights arrive later
        holding.append(id)              # move to holding pattern
        phaseStartTime[id] = t          # set phase start time to now   
        enroute.remove(id)              # remove from enroute
        log(id + ' arriving')           # log arrival
        
def nextLanding(t, holding, phaseStartTime) :
    ''' Returns the id of the next flight cleared to land. '''
    
    result = None                        # assume no flight is ready to land
    longestHold = -0.1                   # init longest holding time 
    for id in holding :                  # for each aircraft in holding pattern
        holdTime = t - phaseStartTime[id]
        if holdTime >  longestHold:      # if holding longest time
            result = id                    # remember as candidate
            longestHold = holdTime         # remember longest hold time
    return result                        # return flight waiting the longest
            
def landFlights(t, runways, holding, standing, phaseStartTime) :
    ''' Assigns flights to runways for landing. '''
    
    if holding:                          # if there are flights holding
        for r in runways.keys() :           # for each runway
            if not runways[r]:                # if runway is not in use
                id = nextLanding(t, holding, phaseStartTime)
                if id :                       # if a flight is ready to land
                    runways[r] = id             # assign flight to runway r
                    phaseStartTime[id] = t      # set phase start time to now
                    holding.remove(id)          # remove from holding pattern
                    log(id + ' cleared to land on runway ' + r)
                
def parkFlights(t, runways, holding, standing, phaseStartTime) :
    ''' Parks flights that have landed at their gate. '''

    for r in runways.keys() :           # for each runway
        if runways[r] :                   # if runway is in use
            id = runways[r] 
            if t-phaseStartTime[id] >= 10.0: # if flight has landed 
                runways[r] = None              # free the runway
                standing.append(id)            # move flight to gate
                phaseStartTime[id] = t         # set phase start time to now
                log(id + ' standing at gate')

def simulation(filename, timeStep = 0.01) :
              
    aircraftType     = {}  # flight id -> aircraft type
    numberPassengers = {}  # flight id -> number of passengers
    arrivalTime      = {}  # flight id -> arrival time of flight
    phaseStartTime   = {}  # flight id -> start time of current flight phase
    enroute          = []  # flight ids of aircraft enroute to arrive in future
    holding          = []  # flight ids of aircraft holding to land
    standing         = []  # flight ids of aircraft standing at gate

    runways = {'09/27':None,   # runway -> flight id of aircraft using runway
               '13/31':None }  # None when not in use

    # read file of scheduled arrivals and load data structures
    readInputs(filename, aircraftType, numberPassengers, 
                 arrivalTime, phaseStartTime, enroute)
    
    t = 0.0  # initial simulation time

    n = len(enroute)     # number of flights in simulation run
    
    # while a flight hasn't reached its gate
    while len(standing) < n :  
        
        # move new arrivals to holding pattern 
        getNewArrivals(t, enroute, holding, arrivalTime, phaseStartTime)  
        
        # assign flights to runways for landing
        landFlights(t, runways, holding, standing, phaseStartTime) 
        
        # park flights that have landed at their gate
        parkFlights(t, runways, holding, standing, phaseStartTime) 
                         
        # increment the simulation time 
        t = t + timeStep
     
    return
