# %%
from jetfuelburn import ureg
from importlib import resources
import json
import csv
from jetfuelburn import ureg


class aeromaps:
    r"""
    This class implements the the reduced-order fuel burn model of the AeroMaps software tool by Planès et al. (2023).

    In this model, fuel burn calculations are based on statistical average values in MJ/ASK (megajoule per available seat-kilometer) for three representative aircraft types,
    with future efficiency improvements modelled over time:

    > The corresponding historical fleet was calibrated using mean fuel consumption per market and representative aircraft.
    > - [Planès et al. (2023), P.5](https://doi.org/10.59490/joas.2023.7147)

    > The future aircraft fleet can be modelled using two solutions: a top-down approach  based on annual efficiency gains, and a bottom-up approach in which aircraft data (performance,  entry-into-service...) are coupled with fleet renewal models.
    > - [Planès et al. (2023), P.5](https://doi.org/10.59490/joas.2023.7147)

    Here, the `drop_in` (fuel) data is used, although the model also data on future `hydrogen` aircraft.

    Warnings
    --------
    It is unclear from the model documentation whether the fuel consumption values given in MJ/ASK for future years are based on the top-down or bottom-up approach.

    References
    ----------
    Planès, T., Delbecq, S., & Salgas, A. (2023).
    AeroMAPS: a framework for performing multidisciplinary assessment of prospective scenarios for air transport.
    In _Journal of Open Aviation Science_.
    doi:[10.59490/joas.2023.7147](https://doi.org/10.59490/joas.2023.7147)

    Planès, T., Salgas, A., Pollet, F., & Delbecq, S. (2025).
    Simulation and analyses of ICAO transition scenarios using the AeroMAPS open-source framework.
    In _Towards Sustainable Aviation Summit (TSAS 2025)_.
    doi:[10.60711/TSAS25.20250216.7026241092200926](https://doi.org/10.60711/TSAS25.20250216.7026241092200926)

    Pollet, F., Planès, T., & Delbecq, S. (2024).
    A Comprehensive Methodology for Performing Prospective Life Cycle Assessments of Future Air Transport Scenarios.
    In _34th Congress of the International Council of the Aeronautical Sciences_.
    hal:[hal-04703961](https://hal.science/hal-04703961v1)

    See Also
    --------
    Excel in the AeroMAPS repository containing the statistical average energy use of aircraft types (short/medium/long-haul): [`aeromaps/resources/data/data.xlsx`](https://github.com/AeroMAPS/AeroMAPS/blob/5febefe03cd69815a6949b67c43906c209ac14b1/aeromaps/resources/data/data.xlsx)

    Example
    -------
    ```pyodide install='jetfuelburn'
    import jetfuelburn
    from jetfuelburn import ureg
    from jetfuelburn.statistics import aeromaps
    aeromaps.calculate_fuel_consumption(
        acft_type='long_range',
        year=2020,
        R=4000*ureg('km')
    )
    ```
    """

    _statistical_data = {}
    with resources.open_text(
        "jetfuelburn.data.AeroMaps", "aeromaps_data.csv", encoding="utf-8-sig"
    ) as file:
        csv_reader = csv.DictReader(file)
        for row in csv_reader:
            year = int(row.pop("year"))
            _statistical_data[year] = {k: float(v) for k, v in row.items()}

    @staticmethod
    def available_years() -> list[int]:
        """
        Returns a sorted list of available years included in the model.
        """
        return sorted(aeromaps._statistical_data.keys())

    @staticmethod
    def available_aircraft(
        year: int,
    ) -> list[str]:
        """
        Given a year, returns a sorted list of available aircraft types
        (e.g., ['long_range', 'medium_range', 'short_range']).
        """
        if year not in aeromaps._statistical_data:
            return []
        return sorted(aeromaps._statistical_data[year].keys())

    @staticmethod
    @ureg.check(
        None,  # acft_type
        None,  # year
        "[length]",
    )
    def calculate_fuel_consumption(
        acft_type: str,
        year: int,
        R: float,
    ) -> float:
        r"""
        Given an aircraft type and range, calculates the fuel burned during flight **per passenger**.

        Data is based on averages of fuel consumption per weight-distance flown by specific aircraft types,
        in specific years, based on the assumptions of the AeroMaps software framework by Planès et al. (2023):

        $$
        F/pax = MJ/ASK * R / e
        $$

        where:

        | Symbol     | Dimension     | Description                              |
        | ---------- | ------------- | ---------------------------------------- |
        | $F$        | [weight]      | Fuel burned during flight                |
        | $pax$      | [1]           | Number of passengers                     |
        | $R$        | length        | Range of the flight                      |
        | $MJ/ASK$   | energy/length | Average energy use per available seat-km |
        | $e$        | energy/weight | Specific energy content of jet fuel      |

        Warnings
        --------
        This model does not

        Parameters
        ----------
        year : int
            Year of the data to be used for the calculation.
        acft_type : str
            Aircraft type (short/medium/long-haul) to be used for the calculation.
        R : pint.Quantity
            Range of the flight.

        Returns
        -------
        pint.Quantity
            Fuel burned during flight per passenger.

        Raises
        ------
        ValueError
            If the range is negative.
            If the year is not available in the model.
            If the aircraft type (short/medium/long-haul) is not available in the model for the given year.

        """
        if R.magnitude < 0:
            raise ValueError(f"Range must not be negative.")
        else:
            R = R.to("km")
        if year not in aeromaps._statistical_data:
            raise ValueError(
                f"Year '{year}' not found in model data. Please select one of the following: {aeromaps.available_years()}"
            )
        if acft_type not in aeromaps._statistical_data[year]:
            raise ValueError(
                f"Aircraft type '{acft_type}' not found in model data for year '{year}'. Please select one of the following: {aeromaps.available_aircraft(year)}"
            )

        specific_energy = 43.15 * ureg(
            "MJ/kg"
        )  # https://en.wikipedia.org/wiki/Jet_fuel#Types
        fuel_burn_MJ = (
            aeromaps._statistical_data[year][acft_type] * R.magnitude
        )  # in MJ
        fuel_burn_kg = fuel_burn_MJ * ureg("MJ") / specific_energy  # in kg
        return fuel_burn_kg.to("kg")


class usdot:
    """
    This class contains methods to access statistical data on aircraft fuel consumption
    reported by "large certified air carriers" to the US Department of Transport (US DOT).

    Data is based on averages of fuel consumption per passenger mile and per ton mile,
    reported by the US DOT in table T2, summarizing Form 41 Schedule T-100.

    Terminology
    -----
    - **Form 41**

        Form 41 is a report that the U.S. Department of Transportation (DOT) generates
        based on data which large certified air carriers are required to provide.

        The _Financial Report_ part of this form includes balance sheet, cash flow, employment, income statement, fuel cost and consumption,
        aircraft operating expenses, and operating expenses.
        The _Air Carrier Statistics_ part of this form includes data on
        passengers, freight and mail transported.
        It also includes aircraft type, service class, available capacity and seats, and aircraft hours ramp-to-ramp and airborne.

        The reporting requirements of Schedule T-100 of Form 41 are defined in federal law:

        | Reglation | Scope |
        | --------- | ----- |
        | [14 CFR 291.45](https://www.ecfr.gov/current/title-14/section-291.45) | General |
        | [Appendix A to Subpart E of Part 291, Title 14](https://www.ecfr.gov/current/title-14/part-291/appendix-Appendix%20A%20to%20Subpart%20E%20of%20Part%20291) | US Air Carriers |
        | [Appendix A to Part 217, Title 14](https://www.ecfr.gov/current/title-14/part-217/appendix-Appendix A to Part 217) | Foreign Air Carriers |

    - **Schedule** (in the context of Form 41)

        A schedule is a specific section of the Form 41 that contains
        a particular type of data:

        > "the Air Carrier Financial Reports (Form 41 Financial Data) (...)
        > Each table in this database contains a different type of financial report or “schedule” (...)"

        [Durso (2007) "An Introduction to DOT Form 41 web resources for airline financial analysis"](https://rosap.ntl.bts.gov/view/dot/16264/dot_16264_DS1.pdf)

    - Schedule **T100**

        > "Form 41 Schedule T-100(f) provides flight stage data covering both passenger/cargo
        > and all cargo operations in scheduled and nonscheduled services.
        > The schedule is used to report all flights which serve points in the United States
        > or its territories as defined in this part."

        [Appendix A to Part 217, Title 14](https://www.ecfr.gov/current/title-14/part-217/appendix-Appendix A to Part 217)

    - (table) **T2**

        > This table summarizes the T-100 traffic data reported by U.S. air carriers. The quarterly summary (...)

    - (table) **T1**

        > This table summarizes the T-100 traffic data reported by U.S. air carriers. The monthly summary (...)

    Warning
    -------
    Form 41 Schedule T-100 data can be downloaded from the US Department of Transportation website.
    Unfortunately, there are no permalinks to the data files, and even regular URLs are
    generated dynamically based on some JavaScript magic.

    The best way to obtain the correct files is to search by name, according to the following hierarchy:

    ```
    Database Name: Air Carrier Summary Data (Form 41 and 298C Summary Data)
    T2: U.S. Air Carrier TRAFFIC And Capacity Statistics by Aircraft Type
    ```

    On the `T2: (...)` page, click on the "Download" button in the "Data Tools" sidebar (left).
    The download should be an archive, containing the following file:

    ```
    T_SCHEDULE_T2.csv
    ```

    Aircraft types in this file are labeled using integer codes. These codes are
    defined in a separate file, which must also be downloaded.
    On the `T2: (...)` page, click on the `AircraftType` Field Name entry
    and on the following page, click on the "Download Lookup Table" button.

    See Also
    --------
    Working links to the relevant files (05-2025):

    - [US DOT: BTS: Air Carrier Summary Data (Form 41 and 298C Summary Data)](https://www.transtats.bts.gov/Tables.asp?QO_VQ=EGD&QO)
    - [US DOT: BTS: Air Carrier Summary Data: T2 (U.S. Air Carrier Traffic And Capacity Statistics by Aircraft Type)](https://www.transtats.bts.gov/Fields.asp?gnoyr_VQ=FIH)

    Example
    --------
    ```pyodide install='jetfuelburn'
    import jetfuelburn
    from jetfuelburn import ureg
    from jetfuelburn.statistics import usdot
    usdot.available_years()
    usdot.available_aircraft(usdot.available_years()[0])
    usdot.calculate_fuel_consumption_per_weight(
        year=2024,
        acft='B787-800 Dreamliner',
        R=1000 * ureg.nmi,
        W=1000 * ureg.kg
    )
    ```
    """

    _years = [2013, 2018, 2024]
    _aircraft_data = {}
    for year in _years:
        with resources.open_text(
            "jetfuelburn.data.USDOT", f"USDOT_data_{year}.json"
        ) as file:
            _aircraft_data[year] = json.load(file)

    @staticmethod
    def available_years() -> list[int]:
        """
        Returns a sorted list of available years included in the model.
        """
        return sorted(usdot._years)

    @staticmethod
    def available_aircraft(
        year: int,
    ) -> list[str]:
        """
        Given a year, returns a sorted list of available ICAO aircraft designators included in the model.
        """
        return sorted(usdot._aircraft_data[year].keys())

    @staticmethod
    @ureg.check(
        None,
        None,
        "[length]",
        "[mass]",
    )
    def calculate_fuel_consumption_per_weight(
        year: int,
        acft: str,
        R: float,
        W: float,
    ) -> dict:
        r"""
        Given an aircraft name, range and payload weight, calculates the fuel burned during flight.

        Data is based on averages of fuel consumption per weight-distance flown by specific aircraft types,
        in specific years, reported by "large certified air carriers" to the US Department of Transport (US DOT):

        $$
        f = \frac{F(t=\text{year})}{WD(t=\text{year})} * R * W
        $$

        where:

        | Symbol | Units   | Description                                                                 |
        | ------ | ------- | --------------------------------------------------------------------------- |
        | $f$    | [kg]    | Fuel burned during flight                                                   |
        | $F$    | [kg]    | Total fuel consumption for a specific aircraft type in a specific year      |
        | $WD$   | [kg*km] | Total weight-distance flown on a specific aircraft type in a specific year  |
        | $R$    | [km]    | Range of the flight                                                         |
        | $W$    | [kg]    | Payload weight of the flight                                                |

        Warnings
        --------
        Aircraft types here are given as full-length strings, e.g. "Boeing 737-800", instead of the ususal ICAO designator "B738".
        These are taken directly from the `L_AIRCRAFT_TYPE.csv` US DOT lookup table file.

        Parameters
        ----------
        year : int
            Year of the data to be used for the calculation.
        acft : str
            Aircraft type to be used for the calculation.
        R : pint.Quantity
            Range of the flight.
        W : pint.Quantity
            Payload weight of the flight.

        Returns
        -------
        pint.Quantity
            Fuel burned during flight.

        Raises
        ------
        ValueError
            If the range or weight is negative.
            If the year is not available in the model.
            If the aircraft type is not available in the model for the given year.
        """
        if R.magnitude < 0 or W.magnitude < 0:
            raise ValueError(f"Range and/or weight must not be negative.")
        else:
            R = R.to("km")
            W = W.to("kg")

        if year not in usdot._years:
            raise ValueError(f"No data available for year '{year}'.")
        if acft not in usdot._aircraft_data[year]:
            raise ValueError(
                f"US DOT Aircraft Designator '{acft}' not found in model data."
            )
        else:
            aircraft_data = usdot._aircraft_data[year][acft]

        fuelburn = (
            (aircraft_data["Fuel/Revenue Weight Distance"] * ureg("1/km")) * R * W
        )
        fuelburn = fuelburn.to("kg")

        return fuelburn

    @staticmethod
    @ureg.check(
        None,
        None,
        "[length]",
    )
    def calculate_fuel_consumption_per_seat(
        year: int,
        acft: str,
        R: float,
    ) -> dict:
        r"""
        Given an aircraft name and range, calculates the fuel burned during flight per passenger.

        Data is based on averages of fuel consumption per passenger-distance flown by specific aircraft types,
        in specific years, reported by "large certified air carriers" to the US Department of Transport (US DOT):

        $$
        f = \frac{F(t=\text{year})}{paxD(t=\text{year})} * R
        $$

        where:

        | Symbol | Units   | Description                                                                   |
        | ------ | ------- | ----------------------------------------------------------------------------- |
        | $f$    | [kg]    | Fuel burned during flight                                                     |
        | $F$    | [kg]    | Total fuel consumption for a specific aircraft type in a specific year        |
        | $paxD$ | [kg*km] | Total passenger-distance flown on a specific aircraft type in a specific year |
        | $R$    | [km]    | Range of the flight                                                           |

        Warnings
        --------
        Aircraft types here are given as full-length strings, e.g. "Boeing 737-800", instead of the ususal ICAO designator "B738".
        These are taken directly from the `L_AIRCRAFT_TYPE.csv` US DOT lookup table file.

        Parameters
        ----------
        year : int
            Year of the data to be used for the calculation.
        acft : str
            Aircraft type to be used for the calculation.
        R : pint.Quantity
            Range of the flight.

        Returns
        -------
        pint.Quantity
            Fuel burned per passenger during flight.

        Raises
        ------
        ValueError
            If the range is negative.
            If the year is not available in the model.
            If the aircraft type is not available in the model for the given year.
        """
        if R.magnitude < 0:
            raise ValueError(f"Range must not be negative.")
        else:
            R = R.to("km")

        if year not in usdot._years:
            raise ValueError(f"No data available for year '{year}'.")
        if acft not in usdot._aircraft_data[year]:
            raise ValueError(
                f"US DOT Aircraft Designator '{acft}' not found in model data."
            )
        else:
            aircraft_data = usdot._aircraft_data[year][acft]

        fuelburn = (aircraft_data["Fuel/Revenue Seat Distance"] * ureg("kg/km")) * R
        fuelburn = fuelburn.to("kg")
        return fuelburn
