#!/usr/bin/env python

from threading import Thread, Lock
import sys
import time

from aman.com import Weather
from aman.com.Euroscope import Euroscope
from aman.config.Airport import Airport
from aman.config.AirportSequencing import AirportSequencing
from aman.sys.aco.Colony import Colony
from aman.sys.aco.Configuration import Configuration
from aman.sys.aco.Node import Node
from aman.sys.WeatherModel import WeatherModel
from aman.sys.RecedingHorizonControl import RecedingHorizonControl
from aman.types.Inbound import Inbound
from aman.types.PerformanceData import PerformanceData

class Worker(Thread):
    def __init__(self, icao : str, configuration : Airport, weather : Weather,
                 performance : PerformanceData, euroscope : Euroscope):
        Thread.__init__(self)
        self.StopThread = None
        self.Icao = icao
        self.Configuration = configuration
        self.SequencingConfiguration = configuration.DefaultSequencing
        self.PerformanceData = performance
        self.UpdateLock = Lock()
        self.ReportQueue = {}
        if None != weather:
            self.WeatherModel = WeatherModel(configuration.GaforId, weather)
        else:
            self.WeatherModel = WeatherModel(0, None)
        self.RecedingHorizonControl = RecedingHorizonControl(configuration.RecedingHorizonControl)
        self.Euroscope = euroscope

        # merge the constraint information with the GNG information
        for runway in self.Configuration.GngData.ArrivalRoutes:
            for star in self.Configuration.GngData.ArrivalRoutes[runway]:
                for name in self.Configuration.ArrivalRouteConstraints:
                    if name == star.Name:
                        for constraint in self.Configuration.ArrivalRouteConstraints[name]:
                            foundWaypoint = False

                            for waypoint in star.Route:
                                if constraint.Name == waypoint.Name:
                                    waypoint.Altitude = constraint.Altitude
                                    waypoint.Speed = constraint.Speed
                                    waypoint.BaseTurn = constraint.BaseTurn
                                    waypoint.FinalTurn = constraint.FinalTurn
                                    foundWaypoint = True
                                    break

                            if False == foundWaypoint:
                                sys.stderr.write('Unable to find ' + constraint.Name + ' in ' + name)
                                sys.exit(-1)
                        break

        self.setDaemon(True)
        self.start()

    def acquireLock(self):
        if None != self.UpdateLock:
            self.UpdateLock.acquire()

    def releaseLock(self):
        if None != self.UpdateLock:
            self.UpdateLock.release()

    def run(self):
        counter = 0

        while None == self.StopThread:
            time.sleep(1)
            counter += 1
            if 0 != (counter % 60):
                continue

            self.acquireLock()

            # perform some book-keeping
            self.RecedingHorizonControl.cleanupWindows()

            # update the aircraft information in RHC
            for callsign in self.ReportQueue:
                report = self.ReportQueue[callsign]

                if 0 != report.distanceToIAF and '' != report.initialApproachFix:
                    inbound = Inbound(report, self.PerformanceData)
                    Node(inbound, inbound.ReportTime, self.WeatherModel, self.Configuration, self.SequencingConfiguration)
                    if None != inbound.InitialArrivalTime:
                        self.RecedingHorizonControl.updateReport(inbound)
                    else:
                        print('Unable to find all data of ' + report.aircraft.callsign)

            self.ReportQueue.clear()

            # search the ACO relevant aircrafts
            relevantInbounds, earliestArrivalTime = self.RecedingHorizonControl.optimizationRelevantInbounds()
            if None != relevantInbounds:
                start = time.process_time()

                # get the last landing aircrafts per runway before the RHC stage to check for constraints
                # this is required to handle the overlap between windows
                preceedingInbounds = {}
                for runway in self.SequencingConfiguration.ActiveArrivalRunways:
                    inbound = self.RecedingHorizonControl.lastFixedInboundOnRunway(runway.Runway.Name)
                    if None != inbound:
                        preceedingInbounds[runway.Runway.Name] = Node(inbound, None, None, None, None)

                # configure the ACO run
                acoConfig = Configuration(constraints = self.SequencingConfiguration, config = self.Configuration,
                                          earliest = earliestArrivalTime, weather = self.WeatherModel,
                                          preceeding = None if 0 == len(preceedingInbounds) else preceedingInbounds,
                                          ants = 5 * len(relevantInbounds), generations = 5 * len(relevantInbounds))

                # perform the ACO run
                aco = Colony(relevantInbounds, acoConfig)
                aco.optimize()
                if None != aco.Result:
                    for node in aco.Result:
                        self.RecedingHorizonControl.resequenceInbound(node.Inbound)

                # measure the exuction time of the overall optimization process
                executionTime = time.process_time() - start
                if 60.0 <= executionTime:
                    print('Optimized ' + str(len(aco.Result)) + ' inbounds in ' + str(executionTime) + ' seconds')
            else:
                print('No relevant inbounds found for the optimization in ' + self.Icao)

            self.releaseLock()

    def inboundSequence(self):
        self.acquireLock()
        sequence = self.RecedingHorizonControl.sequence()
        self.releaseLock()
        return sequence

    def configure(self, configuration : AirportSequencing):
        self.acquireLock()
        self.SequencingConfiguration = configuration
        self.releaseLock()
