#!/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.com.WebUI import WebUI
from aman.config.Airport import Airport
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):
        Thread.__init__(self)
        self.StopThread = None
        self.Icao = None
        self.Configuration = None
        self.PerformanceData = None
        self.UpdateLock = None
        self.ReportQueue = {}
        self.WeatherModel = None
        self.RecedingHorizonControl = None
        self.WebUi = None

    def __del__(self):
        self.release()

    def acquire(self, icao : str, configuration : Airport, weather : Weather,
                performance : PerformanceData, webui : WebUI, euroscope : Euroscope):
        self.StopThread = None
        self.Icao = icao
        self.Configuration = configuration
        self.sequencingConfiguration = configuration.DefaultSequencing
        self.PerformanceData = performance
        self.UpdateLock = Lock()
        self.ReportQueue = {}
        self.WeatherModel = WeatherModel(configuration.GaforId, weather)
        self.RecedingHorizonControl = RecedingHorizonControl(configuration.RecedingHorizonControl)
        self.WebUi = webui
        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.start()

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

    def release(self):
        self.StopThread = True
        self.join()

    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 % 10):
                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.GngData, 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] = inbound

                # configure the ACO run
                acoConfig = Configuration(constraints = self.sequencingConfiguration, nav = self.Configuration.GngData,
                                          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 inbound in aco.Result:
                        self.RecedingHorizonControl.resequenceInbound(inbound)
                    print('Delays: FCFS=' + str(aco.FcfsDelay.total_seconds()) + ', ACO=' + str(aco.ResultDelay.total_seconds()))

                # measure the exuction time of the overall optimization process
                print('Execution time: ' + str(time.process_time() - start) + ' seconds')
            else:
                print('No relevant inbounds found for the optimization in ' + self.Icao)

            # send the sequence to the GUI and Euroscope
            sequence = self.RecedingHorizonControl.sequence()
            self.WebUi.sendSequence(self.Icao, sequence)
            self.Euroscope.sendSequence(self.Icao, sequence, self.WeatherModel)

            self.releaseLock()