"""CSC110 Fall 2021 Assignment 3, Part 4: Forest Fires (SOLUTIONS)

Instructions (READ THIS FIRST!)
===============================
Implement each of the functions in this file. As usual, do not change any function headers
or preconditions. You do NOT need to add doctests.

Copyright and Usage Information
===============================

This file is provided solely for the personal and private use of students
taking CSC110 at the University of Toronto St. George campus. All forms of
distribution of this code, whether as given or with any changes, are
expressly prohibited. For more information on copyright for CSC110 materials,
please consult our Course Syllabus.

This file is Copyright (c) 2021 Mario Badr and Tom Fairgrieve.
"""
import csv
import plotly.graph_objects as go

from a3_ffwi_system import WeatherMetrics, FfwiOutput
import a3_ffwi_system as ffwi


def load_data(filename: str) -> tuple[list[WeatherMetrics], list[FfwiOutput]]:
    """Return a tuple of two parallel lists based on the data in filename. The first list contains
    WeatherMetrics. The second list contains the corresponding FfwiOutput.

    The data in filename is in a csv format with 12 columns. The first six columns correspond to
    the month, day, temperature, relative humidity, wind speed, and precipitation, in that order.
    The last six columns correspond to the FFMC, DMC, DC, ISI, BUI, and FWI values that would be
    calculated based on the first six columns and the previous day's values.
    """
    # ACCUMULATOR inputs_so_far: The WeatherMetrics parsed from filename so far
    inputs_so_far = []
    # ACCUMULATOR outputs_so_far: The FfwiOutputs parsed from filename so far
    outputs_so_far = []

    with open(filename) as f:
        reader = csv.reader(f, delimiter=',')
        next(reader)  # skip the header

        for row in reader:
            assert len(row) == 12, 'Expected every row to contain 12 elements.'
            month, day, temp, humidity, wind, rain, \
                ffmc, dmc, dc, isi, bui, fwi = [float(a) for a in row]
            inputs_so_far.append(WeatherMetrics(int(month), int(day), temp, humidity, wind, rain))
            outputs_so_far.append(FfwiOutput(ffmc, dmc, dc, isi, bui, fwi))

    return inputs_so_far, outputs_so_far


def calculate_ffwi_outputs(readings: list[WeatherMetrics]) -> dict[tuple[int, int], FfwiOutput]:
    """Return a dictionary mapping (month, day) tuples to their corresponding FfwiOutput based on
    the daily weather measurements found in readings.

    Use the functions in a3_ffwi_system for initial FFMC, DMC, and DC values and to calculate each
    attribute needed for FfwiOutput.

    Preconditions:
        - Every reading in readings has a unique (month, day) pair
    """
    # Accumulator
    output: dict[tuple[int, int], FfwiOutput] = {}

    # Initial values
    # ASSUME that the input dates are CONTINUOUS
    # ASSUME that the input dates does not wrap around a year
    last = FfwiOutput(ffwi.INITIAL_FFMC, ffwi.INITIAL_DMC, ffwi.INITIAL_DC, 0, 0, 0)

    # Loop through inputs
    for x in readings:
        ffmc = ffwi.calculate_ffmc(x, last.ffmc)
        dmc = ffwi.calculate_dmc(x, last.dmc)
        dc = ffwi.calculate_dc(x, last.dc)
        isi = ffwi.calculate_isi(x, ffmc)
        bui = ffwi.calculate_bui(dmc, dc)
        fwi = ffwi.calculate_fwi(isi, bui)
        output[(x.month, x.day)] = FfwiOutput(ffmc, dmc, dc, isi, bui, fwi)

    return output


def get_xy_data(outputs: dict[tuple[int, int], FfwiOutput], attribute: str) -> \
        tuple[list[str], list[float]]:
    """Return a tuple of two parallel lists. The first list contains the keys of outputs as
    strings in the format 'month, day'. The second list contains the corresponding value of
    the attribute of FfwiOutput.

    You can access an attribute from a data class using the getattr built-in function. For example,
        >>> output = FfwiOutput(2.0, 3.0, 4.0, 5.0, 6.0, 7.0)
        >>> getattr(output, 'ffmc')
        2.0
    """
    # Accumulators
    days: list[str] = []
    data: list[float] = []

    for k in outputs.keys():
        month, day = k
        days.append(f'{month}, {day}')
        data.append(getattr(outputs[k], attribute))

    return days, data


def plot_ffwi_attribute(outputs: dict[tuple[int, int], FfwiOutput], attribute: str) -> None:
    """Plot an attribute from FfwiOutput as a time series.

    Preconditions:
        - attribute in {'ffmc', 'dmc', 'dc', 'isi', 'bui', 'fwi'}
        - outputs != {}
    """
    # Convert the outputs into parallel x and y lists
    x_data, y_data = get_xy_data(outputs, attribute)

    # Create the figure
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=x_data, y=y_data, name=attribute))

    # Configure the figure
    fig.update_layout(title=f'Time Series of {attribute}',
                      xaxis_title='(Month, Day)',
                      yaxis_title=f'Calculated {attribute}')

    # Show the figure in the browser
    fig.show()
    # Is the above not working for you? Comment it out, and uncomment the following:
    # fig.write_html('my_figure.html')
    # You will need to manually open the my_figure.html file created above.


if __name__ == '__main__':
    import python_ta
    import python_ta.contracts
    python_ta.contracts.DEBUG_CONTRACTS = False
    python_ta.contracts.check_all_contracts()

    # When you are ready to check your work with python_ta, uncomment the following lines.
    # (Delete the "#" and space before each line.)
    # IMPORTANT: keep this code indented inside the "if __name__ == '__main__'" block
    python_ta.check_all(config={
        'allowed-io': ['load_data'],
        'extra-imports': ['python_ta.contracts', 'csv', 'plotly.graph_objects', 'a3_ffwi_system'],
        'max-line-length': 100,
        'max-args': 6,
        'max-locals': 25,
        'disable': ['R1705'],
    })
