import time

# State machine: arrival heuristics (MSL vs airport DB elevation, sim baro slop)
_ARRIVAL_ALT_FACTOR = 1.2
# Extra feet above (arr_elev * factor) so borderline MSL (e.g. 605 vs 600) still counts as "at field"
_ARRIVAL_MSLAGL_BUFFER_FT = 400
# Must be meaningfully below cruise before we use "near arrival MSL" (avoid climb-out from high fields)
_CRUISE_CLEARANCE_BELOW_FT = 2000


class airport:
    def __init__(self, airport_name, altitude, runways: list, gates: list):
        self.airport_name = airport_name
        self.altitude = altitude
        self.runways = self.check_if_list(runways)
        self.gates = self.check_if_list(gates)
    def get_airport_name(self):
        return self.airport_name
    def get_altitude(self):
        return self.altitude
    def get_runways(self):
        return self.runways
    def get_gates(self):
        return self.gates
    def check_if_list(self,test_list):
        """Check if the passed list is a list and return it if it is. We need to sanitize runways and gates to be a list of strings."""
        #actually I want to raise an exception if the passed runways or gates are not a list
        if not isinstance(test_list, list):
            raise Exception("Error: " + test_list + " must be a list")
        return [str(item) for item in test_list]
class aircraft:
    """Aircraft class to represent an aircraft and its status."""
    def __init__(self, aircraft_number, departure_airport: airport, arrival_airport: airport, departure_time, arrival_time, pilot, cruise_altitude=30000, action="",gate="",runway=""):
        self.aircraft_number = aircraft_number
        self.departure_airport = departure_airport
        self.arrival_airport = arrival_airport
        self.departure_time = departure_time
        self.arrival_time = arrival_time
        self.pilot = pilot
        self.action = action
        self.gate = gate
        self.runway = runway
        self.state = "ground"
        self.speed = 0
        self.altitude = departure_airport.get_altitude()
        self.cruise_altitude = cruise_altitude
        self.cruise_altitude_margin = 10 # 10% of the cruise altitude
    def get_flight_data(self):
        #here we need to wire up with Abflug Client to get the flight data
        pass
    def set_state(self, state):
        self.state = state
    def set_speed(self, speed):
        self.speed = speed
    def set_cruise_altitude(self, cruise_altitude):
        self.cruise_altitude = cruise_altitude
    def set_altitude(self, altitude):
        self.altitude = altitude
    def get_altitude(self):
        return self.altitude
    def set_descending(self, descending):
        self.descending = descending
    def set_near_airport(self, near_airport):
        self.near_airport = near_airport
    def set_landed(self, landed):
        self.landed = landed
    def get_state(self):
        return self.state
    def get_speed(self):
        return self.speed
    def _arrival_landing_ceiling_msl(self) -> float:
        """MSL altitude at or below which we consider the aircraft in the arrival/landing band."""
        return self.arrival_airport.get_altitude() * _ARRIVAL_ALT_FACTOR + _ARRIVAL_MSLAGL_BUFFER_FT

    def aircraft_state_machine(self):
        """calculates the flight status of the aircraft."""
        if self.speed == 0:
            self.set_state("ground")
            return "ground"  # parked / stopped; taxi uses 0 < speed <= 60 below
        if self.state == "ground" or self.state == "unknown":
            if self.speed == 0:
                self.set_state("ground")
                return "ground"
            if self.speed > 60: #need to set a reasonable taxi speed; depending on the aircraft, this may be different
                self.set_state("departing") 
                return "departing"
            if self.speed > 0 and self.speed <= 60:
                self.set_state("taxiing")
                return "taxiing"
        elif self.state == "taxiing":
            if self.speed > 60:
                self.set_state("departing")
                return "departing"
            else:
                self.set_state("taxiing")
                return "taxiing"
        elif self.state == "departing":
            if self.altitude > self.departure_airport.get_altitude() + 1000: #need to check if absolute altitude; depending on the airport, this may be different
                self.set_state("airborne")
                return "airborne"
            else:
                return "departing"
        elif self.state == "airborne":
            # Can sit in "airborne" forever if altitude never enters the narrow cruise band (~+/-10% FL).
            # On short finals / parking, MSL is near arrival field elevation — must route to descending.
            arr_ceiling = self._arrival_landing_ceiling_msl()
            near_arrival_msl = self.altitude <= arr_ceiling
            well_below_cruise = self.altitude < self.cruise_altitude - _CRUISE_CLEARANCE_BELOW_FT
            if near_arrival_msl and well_below_cruise:
                self.set_state("descending")
                return "descending"
            if self.altitude < self.cruise_altitude + (self.cruise_altitude * self.cruise_altitude_margin / 100) and self.altitude > self.cruise_altitude - (self.cruise_altitude * self.cruise_altitude_margin / 100):
                self.set_state("cruising")
                return "cruising"
            self.set_state("airborne")
            return "airborne"
        elif self.state == "cruising": 
            # in the future we will need to look for the descending using the vertical speed and the altitude change
            if self.altitude < self.cruise_altitude - (self.cruise_altitude * self.cruise_altitude_margin / 100): #need to check if absolute altitude; depending on the airport, this may be different
                self.set_state("descending")
                return "descending"
            else:
                self.set_state("cruising")
                return "cruising"
        elif self.state == "descending":
            if self.altitude <= self._arrival_landing_ceiling_msl():
                self.set_state("landed")
                return "landed"
            else:
                #print(f"current altitude: {self.altitude}, arrival altitude: {self.arrival_airport.get_altitude()} detection altitude: {self.arrival_airport.get_altitude()*1.2}")
                self.set_state("descending")
                return "descending"
        elif self.state == "landed":
            self.set_state("landed")
            return "landed"
        else:
            self.set_state("unknown")
            return "unknown"
    def get_flight_status(self):
        return self.aircraft_state_machine()
    def get_flight_number(self):
        return self.flight_number
    def get_departure_airport(self):
        return self.departure_airport
    def get_arrival_airport(self):
        return self.arrival_airport
    def get_departure_airport_name(self):
        return self.departure_airport.get_airport_name()
    def get_arrival_airport_name(self):
        return self.arrival_airport.get_airport_name()
    def get_departure_time(self):
        return self.departure_time

        
        
if __name__ == "__main__":
    departure_airport = airport("LOWG", 100, ["09L", "09R"], ["B24", "B25"])
    arrival_airport = airport("EDDF", 100, ["09L", "09R"], ["B24", "B25"])
    myflight = aircraft("AZ321", departure_airport, arrival_airport, "15 mins", "15 mins", "merritt130", cruise_altitude=30000, action="departure", gate="B24", runway="09L")
    #myflight.get_flight_data()
    myflight.set_state("ground")
    myflight.set_speed(0)
    myflight.set_altitude(0)
    vr_speed = 140 #this is a reasonable speed in knots for an A320
    # in the while loop we will simualte a bit of state changes triggered by the aircraft itself
    # the changed values are artificial and not based on real data, and are not generated by the aircraft. This data will come from Abflug Client.
    while True:
        current_state = myflight.aircraft_state_machine()
        if myflight.get_speed() < 320:
            myflight.set_speed(myflight.get_speed() + 1)
            #if myflight.get_speed() % 10 == 0:  
                #print(f"Speed: {myflight.get_speed()}")
        if myflight.get_speed() > vr_speed:
            flag_rotate = True
            if flag_rotate:
                #print(f"Rotate speed: {myflight.get_speed()} knots")
                flag_rotate = False
            #print(f"Altitude: {myflight.altitude} feet")
            if myflight.altitude < 30000:
                
                myflight.set_altitude(myflight.altitude + (2700 / 60)) #this is not based on real data, it is just for the sake of the example - at a certain speed, the aircraft will start to climb
                #print(f"Altitude: {myflight.altitude} feet")
        time.sleep(0.1)
        new_state = myflight.aircraft_state_machine()
        if new_state != current_state:
            myflight.set_state(new_state)
            #print(f"State changed from {current_state} to {new_state}")
        