from datetime import datetime, date, timedelta
from functions import Optimize, PayloadOperation, TrackingType, BalloonSystemType
from functions import BalloonSimulation
from functions import calculate_radius_sphere, forecast_download
from functions import g0
import pickle
import json
from math import pi
import numpy as np

# Mapping for BalloonSystemType Enum
balloon_system_type_mapping = {
    "Tandem Balloon - Compressed Helium": BalloonSystemType.TANDEM_HE,
    "Tandem Balloon - Air Ballast": BalloonSystemType.TANDEM_AIR,
    "Balloon in Balloon - Compressed Helium": BalloonSystemType.BINB_HE,
    "Balloon in Balloon - Air Ballast": BalloonSystemType.BINB_AIR,
}

# Mapping for PayloadOperation Enum
payload_operation_mapping = {
    "Day": PayloadOperation.DAY,
    "Night": PayloadOperation.NIGHT,
    "Always": PayloadOperation.BOTH,
}

# Mapping for TrackingType Enum
tracking_type_mapping = {
    "No": TrackingType.NO_TRACKING,
    "1-axis": TrackingType.ONE_AXIS,
    "2-axis": TrackingType.TWO_AXIS,
}


## Models
class OptimizationModel:
    def __init__(self, settings_model):
        self.settings = settings_model
        ## Optimize parameters
        self.optimize_input = {
            "latitude": None,  #
            "longitude": None,  #
            "desired_max_altitude": None,
            "desired_min_altitude": None,  #
            "flight_date": None,  # Default date in dd.mm.yyyy format
            "flight_duration": None,  #
            "mass": None,  #
            "average_power_requirement": None,  #
            "when_active": "Day",  #
            "tracking": "No",  #
            "efficiency": None,  #
            "density": None,  #
            "solar_panel_safety_factor": None,  #
            "specific_power": None,  #
            "specific_energy": None,  #
            "max_depth_of_discharge": None,  #
            "battery_capacity_safety_factor": None,  #
            "accessories_weight": None,  #
            "maximal_descend_rate": None,  #
            "desired_altitude_change": None,  #
            "night_altitude_change_ratio": None,  #
            "maximal_upwind_speed": None,  #
            "maximal_temp_difference": None,  #
            "max_p_diff": None,
            "p_diff": None,
            "float_offset": None,
            "balloon_system_type": None,
            "balloon_1_film_density": None,
            "balloon_1_tape_density": None,
            "balloon_1_no_tapes": None,
            "balloon_1_film_strength": None,
            "balloon_1_tape_strength": None,
            "balloon_2_film_density": None,
            "balloon_2_tape_density": None,
            "balloon_2_no_tapes": None,
            "balloon_2_film_strength": None,
            "balloon_2_tape_strength": None,
        }
        self.optimize_definition_mission = {
            "latitude": ("Latitude:", "Enter latitude"),
            "longitude": ("Longitude:", "Enter longitude"),
            "desired_max_altitude": (
                "Desired Max. Altitude (m):",
                "Enter desired maximum altitude",
            ),
            "desired_min_altitude": (
                "Desired Min. Altitude (m):",
                "Enter desired minimal altitude",
            ),
            "flight_date": ("Flight Date:", "Select flight date", None, "date"),
            "flight_duration": (
                "Flight Duration (days):",
                "Enter minimal flight duration",
            ),
            "desired_altitude_change": (
                "Desired Daily Altitude Change (m):",
                "Enter desired daily altitude change in meters",
            ),
            "night_altitude_change_ratio": (
                "Night Altitude Change Ratio:",
                "Enter ratio of altitude change of night to day",
            ),
            "maximal_descend_rate": (
                "Maximal Descend Rate (m/s):",
                "Enter maximal descend rate in meters per second",
            ),
            "maximal_upwind_speed": (
                "Max. Up Wind Speed (m/s):",
                "Enter maximal vertical wind speed",
            ),  #
            "maximal_temp_difference": (
                "Max. Temp. Difference (K):",
                "Enter max. temp. difference of balloon gas to ambient air",
            ),
        }
        self.optimize_definition_balloon = {
            "balloon_system_type": (
                "Balloon System Type:",
                "Select what balloon system to optimize",
                [
                    "Tandem Balloon - Compressed Helium",
                    "Tandem Balloon - Air Ballast",
                    "Balloon in Balloon - Compressed Helium",
                    "Balloon in Balloon - Air Ballast",
                ],
            ),
            "balloon_1_film_density": (
                "Balloon 1 film density: (kg/m2)",
                "Enter film density of balloon 1",
            ),
            "balloon_1_tape_density": (
                "Balloon 1 tape density: (kg/m)",
                "Enter tape density of balloon 1",
            ),
            "balloon_1_no_tapes": (
                "Balloon 1 no. tapes:",
                "Enter number of tapes in balloon 1",
            ),
            "balloon_1_film_strength": (
                "Balloon 1 film strength: (N/m):",
                "Enter film strength of selected skin material of balloon 1",
            ),
            "balloon_1_tape_strength": (
                "Balloon 1 tape strength: (N):",
                "Enter strength of selected tape material of balloon 1",
            ),
            "balloon_2_film_density": (
                "Balloon 2 film density: (kg/m2)",
                "Enter film density of balloon 2",
            ),
            "balloon_2_tape_density": (
                "Balloon 2 tape density: (kg/m)",
                "Enter tape density of balloon 2",
            ),
            "balloon_2_no_tapes": (
                "Balloon 2 no. tapes:",
                "Enter number of tapes in balloon 2",
            ),
            "balloon_2_film_strength": (
                "Balloon 2 film strength: (N/m):",
                "Enter film strength of selected skin material of balloon 2",
            ),
            "balloon_2_tape_strength": (
                "Balloon 2 tape strength: (N):",
                "Enter strength of selected tape material of balloon 2",
            ),
        }
        self.optimize_definition_payload = {
            "mass": ("Mass (kg):", "Enter payload mass"),
            "average_power_requirement": (
                "Average Power Requirement (W):",
                "Enter average power requirement of payload",
            ),
            "when_active": (
                "When Active:",
                "Select when the payload is active",
                ["Day", "Night", "Always"],
            ),
            "accessories_weight": (
                "Accessories Weight (%):",
                "Enter accessories weight as a percentage of payload",
            ),
        }

        self.optimize_definition_solar_panel = {
            "tracking": (
                "Tracking:",
                "Select the tracking mechanism",
                ["No", "1-axis", "2-axis"],
            ),
            "efficiency": ("Efficiency (%):", "Enter efficiency percentage"),
            "density": ("Density (kg/m2):", "Enter density in kg per square meter"),
            "solar_panel_safety_factor": (
                "Solar Panel Safety Factor:",
                "Enter the safety factor of solar panel.",
            ),
        }

        self.optimize_definition_battery = {
            "specific_power": (
                "Specific Power (W/kg):",
                "Enter specific power in Watts per kilogram",
            ),
            "specific_energy": (
                "Specific Energy (Wh/kg):",
                "Enter specific energy in Watt-hours per kilogram",
            ),
            "max_depth_of_discharge": (
                "Max Depth of Discharge (%):",
                "Enter maximum depth of discharge percentage",
            ),
            "battery_capacity_safety_factor": (
                "Battery Capacity Safety Factor:",
                "Enter safety factor for battery capacity",
            ),
        }

        self.optimize_definition_others = {
            "max_p_diff": (
                "Max. Internal Overpressure: (Pa)",
                "Enter maximal internal overpressure.",
            ),
            "p_diff": ("Internal Overpressure: (Pa)", "Enter internal overpressure."),
            "float_offset": (
                "Enter Float Altitude Offset: (m)",
                "Enter altitude offset from max. altitude.",
            ),
        }
        self.optimize_validation_rules = {
            "latitude": {"type": "range", "min": -90, "max": 90, "inclusive": True},
            "longitude": {"type": "range", "min": -180, "max": 180, "inclusive": True},
            "desired_max_altitude": {
                "type": "range",
                "min": 10000,
                "max": 45000,
                "inclusive": True,
            },
            "desired_min_altitude": {
                "type": "range",
                "min": 10000,
                "max": 45000,
                "inclusive": True,
            },
            "flight_date": {"type": "date", "format": "%d.%m.%Y"},
            "flight_duration": {
                "type": "range",
                "min": 1,
                "max": 365,
                "inclusive": True,
            },
            "night_altitude_change_ratio": {
                "type": "range",
                "min": 0,
                "max": 1,
                "inclusive": True,
            },
            "maximal_upwind_speed": {
                "type": "range",
                "min": 0,
                "max": 100,
                "inclusive": False,
            },
            "maximal_temp_difference": {
                "type": "range",
                "min": 0,
                "max": 100,
                "inclusive": True,
            },
            "mass": {"type": "range", "min": 0.01, "max": 2500, "inclusive": True},
            "average_power_requirement": {
                "type": "range",
                "min": 0.01,
                "max": 200000,
                "inclusive": False,
            },
            "when_active": {"type": "list", "options": ["Day", "Night", "Always"]},
            "solar_panel_setup": {
                "type": "list",
                "options": ["Setup 1", "Setup 2", "Setup 3"],
            },
            "tracking": {
                "type": "list",
                "options": ["No", "1-axis", "2-axis", "3-axis"],
            },
            "efficiency": {"type": "range", "min": 5, "max": 100, "inclusive": True},
            "density": {"type": "range", "min": 0.01, "max": 50, "inclusive": True},
            "solar_panel_safety_factor": {
                "type": "range",
                "min": 0.25,
                "max": 10,
                "inclusive": False,
            },
            "specific_power": {
                "type": "range",
                "min": 1,
                "max": 100000,
                "inclusive": False,
            },
            "specific_energy": {
                "type": "range",
                "min": 10,
                "max": 5000,
                "inclusive": False,
            },
            "max_depth_of_discharge": {
                "type": "range",
                "min": 10,
                "max": 100,
                "inclusive": True,
            },
            "battery_capacity_safety_factor": {
                "type": "range",
                "min": 0.25,
                "max": 10,
                "inclusive": False,
            },
            "accessories_weight": {
                "type": "range",
                "min": 0,
                "max": 100,
                "inclusive": True,
            },
            "maximal_descend_rate": {
                "type": "range",
                "min": 0,
                "max": 100,
                "inclusive": False,
            },
            "desired_altitude_change": {
                "type": "range",
                "min": 50,
                "max": 100000,
                "inclusive": True,
            },
            "max_p_diff": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": False,
            },
            "p_diff": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": False,
            },
            "float_offset": {
                "type": "range",
                "min": 0,
                "max": 15000,
                "inclusive": False,
            },
            "balloon_system_type": {
                "type": "list",
                "options": [
                    "Tandem Balloon - Compressed Helium",
                    "Tandem Balloon - Air Ballast",
                    "Balloon in Balloon - Compressed Helium",
                    "Balloon in Balloon - Air Ballast",
                ],
            },
            "balloon_1_film_density": {
                "type": "range",
                "min": 0,
                "max": 0.1,
                "inclusive": False,
            },
            "balloon_1_tape_density": {
                "type": "range",
                "min": 0,
                "max": 0.1,
                "inclusive": False,
            },
            "balloon_1_no_tapes": {
                "type": "range",
                "min": 0,
                "max": 1000,
                "inclusive": True,
            },
            "balloon_1_film_strength": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": False,
            },
            "balloon_1_tape_strength": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": False,
            },
            "balloon_2_film_density": {
                "type": "range",
                "min": 0,
                "max": 0.1,
                "inclusive": False,
            },
            "balloon_2_tape_density": {
                "type": "range",
                "min": 0,
                "max": 0.1,
                "inclusive": False,
            },
            "balloon_2_no_tapes": {
                "type": "range",
                "min": 0,
                "max": 1000,
                "inclusive": True,
            },
            "balloon_2_film_strength": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": False,
            },
            "balloon_2_tape_strength": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": False,
            },
        }
        self.optimize_output_general_params = {
            "min_altitude_calc": None,
            "balloon_ratio_calc": None,
            "float_altitude": None,
            "energy_consumption_down_day": None,
            "energy_consumption_down_night": None,
        }

        self.optimize_output_general_params_names = {
            "min_altitude_calc": "Lowest possible altitude: (m)",
            "balloon_ratio_calc": "Final balloon ratio:",
            "float_altitude": "Float altitude: (m)",
            "energy_consumption_down_day": "Energy consumption for day descend: (J/m)",
            "energy_consumption_down_night": "Energy consumption for night descend: (J/m)",
        }

        self.optimize_output_balloon_1_params = {
            "balloon_height": None,
            "max_radius": None,
            "volume": None,
            "skin_length": None,
            "skin_weight": None,
            "gores_weight": None,
            "gas_weight": None,
        }

        self.optimize_output_balloon_1_shape = {"radius": None, "height": None}

        self.optimize_output_balloon_1_stress = {
            "skin_length": None,
            "sigma_meridional": None,
            "sigma_film": None,
            "sigma_tape": None,
            "sigma_total": None,
        }

        self.optimize_output_balloon_2_params = {
            "balloon_height": None,
            "max_radius": None,
            "volume": None,
            "skin_length": None,
            "skin_weight": None,
            "gores_weight": None,
            "gas_weight": None,
        }

        self.optimize_output_balloon_2_shape = {"radius": None, "height": None}

        self.optimize_output_balloon_2_stress = {
            "skin_length": None,
            "sigma_meridional": None,
            "sigma_film": None,
            "sigma_tape": None,
            "sigma_total": None,
        }
        self.output_balloon_param_names = {
            "balloon_height": "Height of lifting balloon: (m)",
            "max_radius": "Max radius of lifting balloon: (m)",
            "volume": "Volume of lifting balloon: (m³)",
            "skin_length": "Length of skin of lifting balloon: (m)",
            "skin_weight": "Weight of skin: (N)",
            "gores_weight": "Weight of gores: (N)",
            "gas_weight": "Weight of balloon gas: (N)",
        }
        # Altitude arrays
        self.output_balloon_arrays = {
            "altitudes": None,
            "balloon_1_volume": None,
            "balloon_1_gas_weight": None,
            "balloon_2_volume": None,
            "balloon_2_gas_weight": None,
        }
        # Solar Panel
        self.optimize_output_solar_panel_param = {
            "weight": None,
            "surface": None,
            "energy": None,
            "power": None,
            "day_low_light": None,
        }
        # Energy Balance
        self.optimize_output_energy_balance = {
            "charge_state": None,
            "battery_capacity": None,
            "min_discharge_level": None,
            "power_balance": None,
            "solar_power_array": None,
            "payload_power_array": None,
            "altitude_power_array": None,
            "heating_auxiliary_power_array": None,
        }
        self.optimize_output_solar_panel_param_names = {
            "weight": "Weight: (kg)",
            "surface": "Surface Area: (m2)",
            "energy": "Produced Energy Daily: (Wh)",
            "power": "Maximal power: (W)",
            "day_low_light": "Date with least sunshine:",
        }
        self.optimize_output_solar_panel_power = {"power_curve": None}
        # Battery
        self.optimize_output_battery_param = {
            "weight": None,
            "energy": None,
            "power": None,
        }
        self.optimize_output_battery_param_names = {
            "weight": "Weight: (kg)",
            "energy": "Capacity: (Wh)",
            "power": "Maximal power: (W)",
        }
        # Other
        self.optimize_output_other_param = {
            "force_up_wind": None,
            "force_balance": None,
            "max_stabilizing_force": None,
            "max_force_altitude": None,
            "max_overpressure": None,
        }
        self.optimize_output_other_param_names = {
            "force_up_wind": "Force from Up Draft (N)",
            "force_balance": "Force Balance: (N)",
            "max_stabilizing_force": "Max. stabilizing force from float: (N)",
            "max_force_altitude": "Max. stabilizing force altitude: (m)",
            "max_overpressure": "Max. overpressure: (Pa)",
        }
        # Output message
        self.optimize_output_message = None
        # Optimization
        self.optimize_output_iter_balloon = {
            "balloon1_radius": None,
            "balloon1_height": None,
            "balloon2_radius": None,
            "balloon2_height": None,
        }
        self.optimize_output_iter_balance = {"balance": None}
        ## Design parameters
        self.design_input = {}

        ## Fly parameters
        self.fly_input = {}

    def long_running_calculation(self):
        # Retrieve settings from the settings model
        SP_initial_theta = self.settings.get_setting("theta0_SP")
        ZP_initial_theta = self.settings.get_setting("theta0_ZP")
        SP_incremental_parameter = self.settings.get_setting("increment_SP")
        ZP_incremental_parameter = self.settings.get_setting("increment_ZP")
        SP_theta_end_error_tolerance = self.settings.get_setting("epsilon_SP")
        ZP_theta_end_error_tolerance = self.settings.get_setting("epsilon_ZP")
        SP_max_iterations_balloon_shape = self.settings.get_setting(
            "max_iter_balloon_shape_SP"
        )
        ZP_max_iterations_balloon_shape = self.settings.get_setting(
            "max_iter_balloon_shape_ZP"
        )
        SP_max_iterations_optimization = self.settings.get_setting(
            "max_iter_balloon_optimization_SP"
        )
        ZP_max_iterations_optimization = self.settings.get_setting(
            "max_iter_balloon_optimization_ZP"
        )
        BinB_init_ratio = self.settings.get_setting("init_balloon_ratio_binb")
        tandem_init_ratio = self.settings.get_setting("init_balloon_ratio_tandem")
        solar_panel_power_fraction = self.settings.get_setting(
            "solar_panel_power_fraction"
        )
        power_heater = self.settings.get_setting("power_heater")
        power_auxiliaries = self.settings.get_setting("power_auxiliaries")

        # Setting enums
        balloon_system_type_enum = balloon_system_type_mapping.get(
            self.optimize_input["balloon_system_type"], BalloonSystemType.TANDEM_HE
        )
        tracking_type_enum = tracking_type_mapping.get(
            self.optimize_input["tracking"], TrackingType.NO_TRACKING
        )
        payload_operation_enum = payload_operation_mapping.get(
            self.optimize_input["when_active"], PayloadOperation.DAY
        )
        # Setting date for full datetime object
        flight_date = datetime.combine(
            self.optimize_input["flight_date"], datetime.min.time()
        )
        # Convert settings angles from degrees to radians
        SP_initial_theta_rad = SP_initial_theta / 180 * pi
        ZP_initial_theta_rad = ZP_initial_theta / 180 * pi

        # Initialize the optimization class with parameters pulled from self.optimize_input
        optimizer = Optimize(
            balloon_system_type=balloon_system_type_enum,
            balloon_1_film_density_kg_m2=self.optimize_input["balloon_1_film_density"],
            balloon_1_tape_density_kg_m=self.optimize_input["balloon_1_tape_density"],
            balloon_1_no_tapes=int(self.optimize_input["balloon_1_no_tapes"]),
            balloon_2_film_density_kg_m2=self.optimize_input["balloon_2_film_density"],
            balloon_2_tape_density_kg_m=self.optimize_input["balloon_2_tape_density"],
            balloon_2_no_tapes=int(self.optimize_input["balloon_2_no_tapes"]),
            balloon_1_max_stress_film=self.optimize_input["balloon_1_film_strength"],
            balloon_1_max_stress_tape=self.optimize_input["balloon_1_tape_strength"],
            balloon_2_max_stress_film=self.optimize_input["balloon_2_film_strength"],
            balloon_2_max_stress_tape=self.optimize_input["balloon_2_tape_strength"],
            SP_initial_theta=SP_initial_theta_rad,
            ZP_initial_theta=ZP_initial_theta_rad,
            SP_incremental_parameter=SP_incremental_parameter,
            ZP_incremental_parameter=ZP_incremental_parameter,
            SP_theta_end_error_tolerance=SP_theta_end_error_tolerance,
            ZP_theta_end_error_tolerance=ZP_theta_end_error_tolerance,
            SP_max_iterations_balloon_shape=SP_max_iterations_balloon_shape,
            ZP_max_iterations_balloon_shape=ZP_max_iterations_balloon_shape,
            SP_max_iterations_optimization=SP_max_iterations_optimization,
            ZP_max_iterations_optimization=ZP_max_iterations_optimization,
            solar_panel_surface_density=self.optimize_input["density"],
            solar_panel_efficiency=self.optimize_input["efficiency"] / 100,
            solar_panel_safety_coefficient=self.optimize_input[
                "solar_panel_safety_factor"
            ],
            solar_panel_tracking_type=tracking_type_enum,
            power_heater=power_heater,
            power_auxiliaries=power_auxiliaries,
            solar_panel_power_fraction=solar_panel_power_fraction,
            battery_power_density=self.optimize_input["specific_power"],
            battery_specific_energy=self.optimize_input["specific_energy"],
            battery_max_discharge=self.optimize_input["max_depth_of_discharge"] / 100,
            battery_safety_coefficient=self.optimize_input[
                "battery_capacity_safety_factor"
            ],
            latitude=self.optimize_input["latitude"],
            longitude=self.optimize_input["longitude"],
            maximal_altitude_m=self.optimize_input["desired_max_altitude"],
            minimal_altitude_m=self.optimize_input["desired_min_altitude"],
            flight_date=flight_date,
            flight_duration=int(self.optimize_input["flight_duration"]),
            desired_daily_altitude_change=self.optimize_input[
                "desired_altitude_change"
            ],
            night_altitude_change=self.optimize_input["night_altitude_change_ratio"],
            maximal_descend_rate=self.optimize_input["maximal_descend_rate"],
            maximal_upwind_speed=self.optimize_input["maximal_upwind_speed"],
            maximal_temp_difference=self.optimize_input["maximal_temp_difference"],
            payload_mass_kg=self.optimize_input["mass"],
            auxiliaries_weight=self.optimize_input["accessories_weight"] / 100,
            payload_power_requirement=self.optimize_input["average_power_requirement"],
            payload_operation=payload_operation_enum,
            init_balloon_ratio_tandem=tandem_init_ratio,
            init_balloon_ratio_binb=BinB_init_ratio,
            p_max_diff=self.optimize_input["max_p_diff"],
            p_diff_normal=self.optimize_input["p_diff"],
            float_offset=self.optimize_input["float_offset"],
        )

        # Run initialization and optimization methods
        optimizer.initialization()
        optimizer.optimization()

        # Saving General Parameters
        self.optimize_output_general_params = {
            "min_altitude_calc": optimizer.balloon.real_min_altitude,
            "balloon_ratio_calc": optimizer.balloon.balloon_ratio,
            "float_altitude": optimizer.balloon.float_altitude,
            "energy_consumption_down_day": optimizer.day_energy_per_m,
            "energy_consumption_down_night": optimizer.night_energy_per_m,
        }
        # Saving parameters of Balloons
        self.optimize_output_balloon_1_params = {
            "balloon_height": optimizer.balloon.balloon_1.max_height,
            "max_radius": optimizer.balloon.balloon_1.max_radius,
            "volume": optimizer.balloon.balloon_1.volume,
            "skin_length": optimizer.balloon.balloon_1.length,
            "skin_weight": optimizer.balloon.balloon_1.skin_weight,
            "gores_weight": optimizer.balloon.balloon_1.gores_weight,
            "gas_weight": optimizer.balloon.balloon_1.gas_weight,
        }
        self.optimize_output_balloon_2_params = {
            "balloon_height": optimizer.balloon.balloon_2.max_height,
            "max_radius": optimizer.balloon.balloon_2.max_radius,
            "volume": optimizer.balloon.balloon_2.volume,
            "skin_length": optimizer.balloon.balloon_2.length,
            "skin_weight": optimizer.balloon.balloon_2.skin_weight,
            "gores_weight": optimizer.balloon.balloon_2.gores_weight,
            "gas_weight": optimizer.balloon.balloon_2.gas_weight,
        }
        # Saving Balloons Shape
        self.optimize_output_balloon_1_shape = {
            "radius": optimizer.balloon.balloon_1.radius,
            "height": optimizer.balloon.balloon_1.height,
        }
        self.optimize_output_balloon_2_shape = {
            "radius": optimizer.balloon.balloon_2.radius,
            "height": optimizer.balloon.balloon_2.height,
        }
        # Saving Balloons Stress
        self.optimize_output_balloon_1_stress, self.optimize_output_balloon_2_stress = (
            optimizer.get_stress_plot_parameters()
        )
        # Saving altitude arrays
        self.output_balloon_arrays = {
            "altitudes": optimizer.altitude_array,
            "balloon_1_volume": optimizer.V_balloon_1_array,
            "balloon_1_gas_weight": optimizer.m_gas_balloon_1_array,
            "balloon_2_volume": optimizer.V_balloon_2_array,
            "balloon_2_gas_weight": optimizer.m_gas_balloon_2_array,
        }
        # Saving Energy Balance
        self.optimize_output_energy_balance = (
            optimizer.get_energy_balance_plot_parameters()
        )
        # Saving Solar Panel
        self.optimize_output_solar_panel_param = {
            "weight": optimizer.solar_panel.weight,
            "surface": optimizer.solar_panel.surface,
            "energy": optimizer.solar_panel.total_energy,
            "power": optimizer.solar_panel.total_power,
            "day_low_light": optimizer.solar_panel.date.strftime("%d.%m.%Y"),
        }
        self.optimize_output_solar_panel_power = {
            "power_curve": optimizer.solar_panel.power_curve_real
        }
        # Saving Battery
        self.optimize_output_battery_param = {
            "weight": optimizer.battery.weight,
            "energy": optimizer.battery.capacity,
            "power": optimizer.battery.power,
        }
        # Saving Other
        self.optimize_output_other_param = {
            "force_up_wind": optimizer.balloon.wind_force,
            "force_balance": optimizer.balloon.force_balance_total,
            "max_stabilizing_force": optimizer.balloon.force,
            "max_force_altitude": optimizer.balloon.max_force_altitude,
            "max_overpressure": optimizer.balloon.p_max_diff,
        }
        # Saving output massage
        self.optimize_output_message = optimizer.get_output_massage()
        # Saving iterations
        self.optimize_output_iter_balloon = {
            "balloon1_radius": optimizer.balloon1_radius_iterations,
            "balloon1_height": optimizer.balloon1_height_iterations,
            "balloon2_radius": optimizer.balloon2_radius_iterations,
            "balloon2_height": optimizer.balloon2_height_iterations,
        }

        self.optimize_output_iter_balance = {
            "balance": np.array(optimizer.balance_iterations)
        }

    def set_parameter(self, category, key, value):
        """
        Sets the value of a parameter within a specified category.

        Parameters:
        - category: The category of the parameter (e.g., 'optimize_input').
        - key: The key of the parameter to set.
        - value: The new value for the parameter.
        """
        category_dict = self._get_category_dict(category)
        if key in category_dict:
            category_dict[key] = value
        else:
            raise ValueError(f"Parameter '{key}' not found in category '{category}'.")

    def get_parameter(self, category, key):
        """
        Gets the value of a parameter within a specified category.

        Parameters:
        - category: The category of the parameter (e.g., 'optimize_input').
        - key: The key of the parameter to get.

        Returns:
        - The value of the requested parameter.
        """
        category_dict = self._get_category_dict(category)
        if key in category_dict:
            return category_dict[key]
        else:
            raise ValueError(f"Parameter '{key}' not found in category '{category}'.")

    def _get_category_dict(self, category):
        """
        Helper method to retrieve the dictionary for a given category of parameters.

        Parameters:
        - category: The category of the parameters (e.g., 'optimize_input').

        Returns:
        - The dictionary object for the specified category.
        """
        if hasattr(self, category):
            return getattr(self, category)
        else:
            raise ValueError(f"Category '{category}' not found in ParameterModel.")

    def get_balloon_output_for_simulation(self):
        # General parameters
        balloon_type = self.optimize_input["balloon_system_type"]
        min_altitude = self.optimize_output_general_params["min_altitude_calc"]
        max_altitude = self.optimize_output_general_params["float_altitude"]
        payload_mass = self.optimize_input["mass"]
        # For the first balloon
        balloon_1_envelope_mass = (
            self.optimize_output_balloon_1_params["skin_weight"]
            + self.optimize_output_balloon_1_params["gores_weight"]
        ) / g0
        balloon_1_gas_mass = self.optimize_output_balloon_1_params["gas_weight"]
        balloon_1_volume = self.optimize_output_balloon_1_params["volume"]

        balloon_1_diameter = 2 * (calculate_radius_sphere(balloon_1_volume))
        #  For the second balloon
        balloon_2_envelope_mass = (
            self.optimize_output_balloon_2_params["skin_weight"]
            + self.optimize_output_balloon_2_params["gores_weight"]
        ) / g0
        balloon_2_gas_mass = self.optimize_output_balloon_2_params["gas_weight"]
        balloon_2_volume = self.optimize_output_balloon_2_params["volume"]

        balloon_2_diameter = 2 * (calculate_radius_sphere(balloon_2_volume))

        # For solar panel
        solar_panel_mass = self.optimize_output_solar_panel_param["weight"]
        solar_panel_surface = self.optimize_output_solar_panel_param["surface"]
        solar_panel_efficiency = self.optimize_input["efficiency"] / 100
        solar_panel_tracking = self.optimize_input["tracking"]
        # For battery
        battery_mass = self.optimize_output_battery_param["weight"]
        battery_capacity = self.optimize_output_battery_param["energy"]
        battery_max_SOD = self.optimize_input["max_depth_of_discharge"]

        # Total mass
        accessories_weight = self.optimize_input["accessories_weight"]
        total_mass = (payload_mass + solar_panel_mass + battery_mass) * (
            1 + accessories_weight / 100
        )

        # For payload
        payload_operation = self.optimize_input["when_active"]
        payload_power = self.optimize_input["average_power_requirement"]

        return BalloonSimulation(
            min_altitude=min_altitude,
            max_altitude=max_altitude,
            payload_accessories_mass=total_mass,
            balloon_type=balloon_type,
            balloon_1_diameter=balloon_1_diameter,
            balloon_1_envelope_mass=balloon_1_envelope_mass,
            balloon_1_gas_mass=balloon_1_gas_mass,
            balloon_2_diameter=balloon_2_diameter,
            balloon_2_envelope_mass=balloon_2_envelope_mass,
            balloon_2_gas_mass=balloon_2_gas_mass,
            solar_panel_surface=solar_panel_surface,
            solar_panel_efficiency=solar_panel_efficiency,
            solar_panel_tracking=solar_panel_tracking,
            battery_capacity=battery_capacity,
            battery_max_SOD=battery_max_SOD,
            payload_operation=payload_operation,
            payload_power=payload_power,
        )

    # Saving and loading
    # def serialize_data(self):
    #     # Convert current settings into a dictionary suitable for JSON serialization
    #     return {key: self.optimize_input[key] for key in self.optimize_input}

    # def deserialize_data(self, data):
    #     # Update the model based on loaded data
    #     for key, value in data.items():
    #         if key in self.optimize_input:
    #             self.set_parameter("optimize_input", key, value)

    # def save_to_file(self, filename):
    #     data = self.serialize_data()
    #     with open(filename, "w") as file:
    #         json.dump(data, file, indent=4)

    # def load_from_file(self, filename):
    #     with open(filename, "r") as file:
    #         data = json.load(file)
    #     self.deserialize_data(data)
    def serialize_data(self, data):
        serialized_data = {}
        for key, value in data.items():
            if isinstance(value, date):
                serialized_data[key] = value.strftime(
                    "%Y-%m-%d"
                )  # Serialize datetime to string
            else:
                serialized_data[key] = value
        return serialized_data

    def deserialize_data(self, data):
        for key, value in data.items():
            if key == "flight_date" and value is not None:
                data[key] = datetime.strptime(
                    value, "%Y-%m-%d"
                ).date()  # Convert string back to datetime
            # Add other specific keys if necessary
        return data

    def save_parameters(self, file_path):
        data = {
            "optimize_input": self.serialize_data(self.optimize_input),
            "settings": self.serialize_data(self.settings.get_settings()),
        }
        with open(file_path, "w") as file:
            json.dump(data, file, indent=4)

    def load_parameters(self, file_path):
        with open(file_path, "r") as file:
            data = json.load(file)
        self.optimize_input.update(self.deserialize_data(data["optimize_input"]))
        self.settings.load_settings(self.deserialize_data(data["settings"]))

    def save_balloon_simulation(self, file_path):
        """
        Saves an instance of BalloonForSimulation to a file using pickle.

        Parameters:
        - balloon_simulation: The BalloonSimulation instance to save.
        - file_path: The path to the file where the data will be saved.
        """
        balloon_simulation = self.get_balloon_output_for_simulation()
        with open(file_path, "wb") as file:
            pickle.dump(balloon_simulation, file)


class FlyModel:
    def __init__(self, settings_model, optimization_model):
        self.settings = settings_model
        self.optimization = optimization_model

        self.fly_input = dict(
            fly_type=None,
            starting_latitude=None,
            starting_longitude=None,
            starting_altitude=None,
            float_altitude_balance=None,
            battery_SOC=None,
            flight_date=None,
            flight_duration=None,
        )
        self.fly_input_definitions = dict(
            fly_type=(
                "Fly Type:",
                "Select kind of flight simulation.",
                ["Ascend flight", "Simulation flight"],
            ),
            starting_latitude=(
                "Starting Latitude: (°)",
                "Enter the starting latitude.",
            ),
            starting_longitude=(
                "Starting Longitude: (°)",
                "Enter the starting longitude.",
            ),
            starting_altitude=(
                "Starting Altitude: (m)",
                "Enter the starting altitude.",
            ),
            float_altitude_balance=(
                "Float Altitude: (m)",
                "Enter the float altitude.",
            ),
            battery_SOC=(
                "Battery State of Charge: (%)",
                "Enter the battery state of charge.",
            ),
            flight_date=("Flight Date:", "Select flight date", None, "date"),
            flight_duration=("Flight Duration: (hours)", "Enter the flight duration."),
        )
        self.balloon_parameters = dict(
            min_altitude=None,
            max_altitude=None,
            payload_accessories_mass=None,
            balloon_type=None,
            balloon_1_diameter=None,
            balloon_1_envelope_mass=None,
            balloon_1_gas_mass=None,
            balloon_2_diameter=None,
            balloon_2_envelope_mass=None,
            balloon_2_gas_mass=None,
            solar_panel_surface=None,
            solar_panel_efficiency=None,
            solar_panel_tracking=None,
            battery_capacity=None,
            battery_max_SOD=None,
            payload_operation=None,
            payload_power=None,
        )
        self.balloon_parameters_definitions = dict(
            min_altitude="Minimum Altitude of Balloon: (m)",
            max_altitude="Maximum Altitude of Balloon: (m)",
            payload_accessories_mass="Total Mass of Payload and Accessories: (kg)",
            balloon_type="Balloon Type:",
            balloon_1_diameter="Balloon 1 Diameter: (m)",
            balloon_1_envelope_mass="Balloon 1 Envelope Mass: (kg)",
            balloon_1_gas_mass="Balloon 1 Gas Mass: (kg)",
            balloon_2_diameter="Balloon 2 Diameter: (m)",
            balloon_2_envelope_mass="Balloon 2 Envelope Mass: (kg)",
            balloon_2_gas_mass="Balloon 2 Gas Mass: (kg)",
            solar_panel_surface="Solar Panel Surface Area: (m2)",
            solar_panel_efficiency="Solar Panel Efficiency: (%)",
            solar_panel_tracking="Solar Panel Tracking Type:",
            battery_capacity="Battery Capacity: (Wh)",
            battery_max_SOD="Battery Max Depth of Discharge: (%)",
            payload_operation="Payload Operation:",
            payload_power="Payload Power Requirement: (W)",
        )

        self.fly_validation_rules = dict(
            fly_type={
                "type": "list",
                "options": ["Ascend flight", "Simulation flight"],
            },
            starting_latitude={
                "type": "range",
                "min": -90,
                "max": 90,
                "inclusive": True,
            },
            starting_longitude={
                "type": "range",
                "min": -180,
                "max": 180,
                "inclusive": True,
            },
            starting_altitude={
                "type": "range",
                "min": 0,
                "max": self.balloon_parameters["max_altitude"],
                "inclusive": True,
            },
            float_altitude_balance={
                "type": "range",
                "min": self.balloon_parameters["min_altitude"],
                "max": self.balloon_parameters["max_altitude"],
                "inclusive": True,
            },
            battery_SOC={
                "type": "range",
                "min": 0,
                "max": 100,
                "inclusive": True,
            },
            flight_date={
                "type": "datetime",
                "format": "%d.%m.%Y %H:%M",
                "min": datetime.now(),
                "max": datetime.now() + timedelta(days=7),
                "inclusive": True,
            },
            flight_duration={
                "type": "range",
                "min": 1,
                "max": 72,
                "inclusive": True,
            },
        )

    def set_balloon_data(self, balloon_data):
        self.balloon_parameters = dict(
            min_altitude=balloon_data.min_altitude,
            max_altitude=balloon_data.max_altitude,
            payload_accessories_mass=balloon_data.payload_accessories_mass,
            balloon_type=balloon_data.balloon_type,
            balloon_1_diameter=balloon_data.balloon_1_diameter,
            balloon_1_envelope_mass=balloon_data.balloon_1_envelope_mass,
            balloon_1_gas_mass=balloon_data.balloon_1_gas_mass,
            balloon_2_diameter=balloon_data.balloon_2_diameter,
            balloon_2_envelope_mass=balloon_data.balloon_2_envelope_mass,
            balloon_2_gas_mass=balloon_data.balloon_2_gas_mass,
            solar_panel_surface=balloon_data.solar_panel_surface,
            solar_panel_efficiency=balloon_data.solar_panel_efficiency,
            solar_panel_tracking=balloon_data.solar_panel_tracking,
            battery_capacity=balloon_data.battery_capacity,
            battery_max_SOD=balloon_data.battery_max_SOD,
            payload_operation=balloon_data.payload_operation,
            payload_power=balloon_data.payload_power,
        )

    def check_balloon_data(self):
        for _, value in self.balloon_parameters.items():
            if value is None:
                return False
        return True

    def load_balloon_simulation(self, file_path):
        """
        Loads an instance of BalloonSimulation from a file using pickle, with a class check.

        Parameters:
        - file_path: The path to the file from which to load the data.

        Returns:
        - BalloonSimulation: The loaded instance of the BalloonSimulation if it is the correct type.

        Raises:
        - ValueError: If the loaded object is not an instance of BalloonSimulation.
        """
        with open(file_path, "rb") as file:
            balloon_simulation = pickle.load(file)

        if not isinstance(balloon_simulation, BalloonSimulation):
            raise ValueError("Loaded object is not an instance of BalloonSimulation")

        return balloon_simulation

    def update_validation_rules(self):
        self.fly_validation_rules["starting_altitude"]["max"] = self.balloon_parameters[
            "max_altitude"
        ]
        self.fly_validation_rules["float_altitude_balance"]["min"] = (
            self.balloon_parameters["min_altitude"]
        )
        self.fly_validation_rules["float_altitude_balance"]["max"] = (
            self.balloon_parameters["max_altitude"]
        )

    def get_path_weather_forecast(self):
        # Get the path to the weather forecast data
        # TODO: Ranges in settings model
        self.weather_forecast_path = forecast_download(
            start_time=self.fly_input["flight_date"],
            flight_duration=self.fly_input["flight_duration"],
            latitude=self.fly_input["starting_latitude"],
            longitude=self.fly_input["starting_longitude"],
            range_lat=40,
            range_lon=40,
        )

    def set_parameter(self, category, key, value):
        """
        Sets the value of a parameter within a specified category.

        Parameters:
        - category: The category of the parameter (e.g., 'optimize_input').
        - key: The key of the parameter to set.
        - value: The new value for the parameter.
        """
        category_dict = self._get_category_dict(category)
        if key in category_dict:
            category_dict[key] = value
        else:
            raise ValueError(f"Parameter '{key}' not found in category '{category}'.")

    def _get_category_dict(self, category):
        """
        Helper method to retrieve the dictionary for a given category of parameters.

        Parameters:
        - category: The category of the parameters (e.g., 'optimize_input').

        Returns:
        - The dictionary object for the specified category.
        """
        if hasattr(self, category):
            return getattr(self, category)
        else:
            raise ValueError(f"Category '{category}' not found in FlyModel.")


class SettingsModel:
    # Stores and manages application settings
    def __init__(self):
        self.settings = {
            # Zero Pressure Balloon
            "theta0_ZP": 45,
            "epsilon_ZP": 1e-3,
            "increment_ZP": 5,
            "max_iter_balloon_shape_ZP": 100000,
            "max_iter_balloon_optimization_ZP": 100,
            # Super Pressure Balloon
            "theta0_SP": 91,
            "epsilon_SP": 1e-5,
            "increment_SP": 1,
            "max_iter_balloon_shape_SP": 100000,
            "max_iter_balloon_optimization_SP": 100,
            # Others
            "init_balloon_ratio_tandem": 0.5,
            "init_balloon_ratio_binb": 1,
            "power_auxiliaries": 1,
            "power_heater": 2,
            "solar_panel_power_fraction": 0.05,
        }
        self.validation_rules = {
            "theta0_ZP": {"type": "range", "min": 5, "max": 90, "inclusive": False},
            "theta0_SP": {"type": "range", "min": 45, "max": 100, "inclusive": False},
            "epsilon_ZP": {"type": "range", "min": 1e-10, "max": 10, "inclusive": True},
            "epsilon_SP": {"type": "range", "min": 1e-10, "max": 10, "inclusive": True},
            "increment_ZP": {
                "type": "range",
                "min": 1e-2,
                "max": 100,
                "inclusive": False,
            },
            "increment_SP": {
                "type": "range",
                "min": 1e-2,
                "max": 100,
                "inclusive": False,
            },
            "max_iter_balloon_shape_ZP": {
                "type": "range",
                "min": 1,
                "max": 10000000,
                "inclusive": False,
            },
            "max_iter_balloon_shape_SP": {
                "type": "range",
                "min": 1,
                "max": 10000000,
                "inclusive": False,
            },
            "max_iter_balloon_optimization_ZP": {
                "type": "range",
                "min": 1,
                "max": 10000,
                "inclusive": False,
            },
            "max_iter_balloon_optimization_SP": {
                "type": "range",
                "min": 1,
                "max": 10000,
                "inclusive": False,
            },
            "init_balloon_ratio_tandem": {
                "type": "range",
                "min": 0,
                "max": 1,
                "inclusive": False,
            },
            "init_balloon_ratio_binb": {
                "type": "range",
                "min": 0,
                "max": 1,
                "inclusive": True,
            },
            "power_auxiliaries": {
                "type": "range",
                "min": 0,
                "max": 100000,
                "inclusive": True,
            },
            "power_heater": {
                "type": "range",
                "min": 0,
                "max": 10000,
                "inclusive": True,
            },
            "solar_panel_power_fraction": {
                "type": "range",
                "min": 0,
                "max": 0.5,
                "inclusive": True,
            },
        }
        self.settings_definitions_zero_pressure = {
            "theta0_ZP": "Theta0:",
            "epsilon_ZP": "Epsilon (Accepted Error Range):",
            "increment_ZP": "Incremental parameter ds:",
            "max_iter_balloon_shape_ZP": "Max. iter. of balloons shape:",
            "max_iter_balloon_optimization_ZP": "Max. iter. of balloon optimization:",
        }
        self.settings_definitions_super_pressure = {
            "theta0_SP": "Theta0:",
            "epsilon_SP": "Epsilon (Accepted Error Range):",
            "increment_SP": "Incremental parameter ds:",
            "max_iter_balloon_shape_SP": "Max. iter. of balloons shape:",
            "max_iter_balloon_optimization_SP": "Max. iter. of balloon optimization:",
        }
        self.settings_definitions_others = {
            "init_balloon_ratio_tandem": "Initial Balloon Ratio for Tandem Balloons:",
            "init_balloon_ratio_binb": "Initial Balloon Ratio for BinB Balloons:",
            "power_auxiliaries": "Power of Auxiliaries (W):",
            "power_heater": "Power of Heater (W):",
            "solar_panel_power_fraction": "Fraction of Max. Solar Panel Power to consider day operation:",
        }

    def get_all_settings_definitions(self, category):
        if hasattr(self, category):
            return getattr(self, category)
        else:
            raise ValueError(f"Category '{category}' not found in ParameterModel.")

    def get_setting(self, key):
        return self.settings.get(key)

    def get_settings(self):
        return self.settings

    def set_setting(self, key, value):
        self.settings[key] = value

    def load_settings(self, settings_data):
        self.settings.update(settings_data)
