#!/usr/bin/env python

import pytz

from datetime import datetime, timedelta

from aman.com import AircraftReport_pb2
from aman.config.AirportSequencing import AirportSequencing
from aman.formats.SctEseFormat import SctEseFormat
from aman.sys.WeatherModel import WeatherModel
from aman.types.PerformanceData import PerformanceData
from aman.types.Waypoint import Waypoint

class Inbound:
    def __init__(self, report : AircraftReport_pb2.AircraftReport, sequencingConfig : AirportSequencing, navData : SctEseFormat,
                 performanceData : PerformanceData, weatherModel : WeatherModel):
        self.Report = report
        self.CurrentPosition = report.position
        self.ReportTime = datetime.strptime(report.reportTime + '+0000', '%Y%m%d%H%M%S%z').replace(tzinfo = pytz.UTC)

        # search performance data -> fallback to A320
        if self.Report.aircraft.type in performanceData.Aircrafts:
            self.PerformanceData = performanceData.Aircrafts[self.Report.aircraft.type]
        if None == self.PerformanceData:
            self.PerformanceData = performanceData.Aircrafts['A320']

        self.findArrivalRunway(sequencingConfig)
        self.findArrivalRoute(navData)

        duration = self.secondsUntilTouchdown(weatherModel)
        self.InitialArrivalTime = self.ReportTime + duration
        self.EstimatedArrivalTime = self.InitialArrivalTime
        self.EstimatedStarEntryTime = None

    def findArrivalRunway(self, sequencingConfig : AirportSequencing):
        self.PlannedRunway = None

        # find the nearest runway for an initial guess
        distance = 100000.0
        currentPosition = Waypoint(latitude = self.Report.position.latitude, longitude = self.Report.position.longitude)
        for runway in sequencingConfig.ActiveArrivalRunways:
            candidateDistance = runway.Runway.Start.haversine(currentPosition)
            if distance > candidateDistance:
                self.PlannedRunway = runway
                distance = candidateDistance

    def findArrivalRoute(self, navData : SctEseFormat):
        self.PlannedStar = None
        if None == self.PlannedRunway:
            return

        for arrivalRunway in navData.ArrivalRoutes:
            if arrivalRunway == self.PlannedRunway.Runway.Name:
                stars = navData.ArrivalRoutes[arrivalRunway]
                for star in stars:
                    if 0 != len(star.Route) and self.Report.initialApproachFix == star.Iaf.Name:
                        self.PlannedStar = star
                        return

    def secondsUntilTouchdown(self, weather : WeatherModel):
        if None == self.PlannedRunway or None == self.PlannedStar:
            return timedelta(seconds = 0)

        # calculate remaining trackmiles
        remainingDistanceNM = self.Report.distanceToIAF
        start = self.PlannedStar.Route[0]
        for i in range(1, len(self.PlannedStar.Route)):
            remainingDistanceNM += start.haversine(self.PlannedStar.Route[i]) * 0.539957
            start = self.PlannedStar.Route[i]

        # calculate descend profile
        flightTimeSeconds = 0
        currentHeading = Waypoint(latitude = self.Report.position.latitude, longitude = self.Report.position.longitude).bearing(self.PlannedStar.Route[0])
        distanceToWaypoint = self.Report.distanceToIAF
        nextWaypointIndex = 0
        currentPosition = [ self.Report.dynamics.altitude, self.Report.dynamics.groundSpeed ]
        while 0 < currentPosition[0]:
            lastDistance = remainingDistanceNM

            # TODO integrate speed and altitude constraints

            # calculate the next position after 10 seconds
            if (currentPosition[0] / 1000 * 3) > remainingDistanceNM:
                oldGroundspeed = currentPosition[1]
                descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6
                newAltitude = currentPosition[0] - descendRate
                if 0 > newAltitude:
                    newAltitude = 0

                # get the planned IAS and calculate the new altitude and GS out of the predicted information
                # we assume that the aircraft only decelerates
                ias = self.PerformanceData.ias(newAltitude, remainingDistanceNM)
                currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, int(ias), currentHeading), currentPosition[1]) ]

                # use the average between last and current speed to estimate the remaining distance
                remainingDistanceNM -= (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6
            else:
                remainingDistanceNM -= currentPosition[1] / 60 / 6

            flightTimeSeconds += 10
            if 0 > remainingDistanceNM:
                break

            # check if we follow a new waypoint pair
            distanceToWaypoint -= abs(lastDistance - remainingDistanceNM)
            if 0 >= distanceToWaypoint:
                nextWaypointIndex += 1
                if nextWaypointIndex < len(self.PlannedStar.Route):
                    currentHeading = self.PlannedStar.Route[nextWaypointIndex - 1].bearing(self.PlannedStar.Route[nextWaypointIndex])

        return timedelta(seconds = flightTimeSeconds)