from config import pv_config, ess_config, grid_config
import pandas as pd
class EnergySystem:
    def __init__(self, pv_type: pv_config, ess_type: ess_config, grid_type: grid_config):
        self.pv = pv_type
        self.ess = ess_type
        self.grid = grid_type
        self.day_generated = []
        self.generated = 0
        self.stored = 0
        self.hour_stored = []
        self.hour_stored_2 = []
        self.afford = True
        self.cost = self.ess.get_cost() + self.pv.get_cost()
        self.overload_cnt = 0
        self.spring_week_gen = []
        self.summer_week_gen = []
        self.autumn_week_gen = []
        self.winter_week_gen = []
        self.spring_week_soc = []
        self.summer_week_soc = []
        self.autumn_week_soc = []
        self.winter_week_soc = []
        self.factory_demand = []
        self.buy_price_kWh = []
        self.sell_price_kWh = []
        self.pv_generated_kWh = []
        self.grid_need_power_kW = []
        self.time = []
        self.ess_rest = 0
        self.granularity = 4
        self.season_step = self.granularity * 24 * 7 * 12
        self.season_start= self.granularity * 24 * 7 * 2
        self.week_length = self.granularity * 24 * 7
        self.unmet = []
        
    def get_cost(self):
        return self.ess.get_cost()+self.pv.get_cost()

    def simulate(self, data, time_interval):
        """
        The program will use the PV to supply the factory first. If the PV output can meet the factory's demand, it will be directly powered, and the excess electrical energy will be used to charge the ESS. Program will use the PV to supply the Ess.

        When the PV is insufficient, the ESS is used to supplement. If the PV output is not enough to meet the factory's demand, the required power is first obtained from the ESS.

        If the ESS is also insufficient to meet the demand, it will be obtained from the grid. When the stored power in the ESS is also insufficient to supplement, the remaining required power will be purchased from the grid.

        Args:
            data: pandas.DataFrame
                The data that contains the factory's demand, PV output, and electricity price.
            time_interval: float
                The time interval of the data in hours.

        Returns:
            tuple
                The total benefit, total netto benefit, and total generated energy.

        """
        total_benefit = 0
        total_netto_benefit = 0
        total_gen = 0
        net_grid = 0.
        for index, row in data.iterrows():
            time = row['time']
            self.time.append(time)
            # sunlight_intensity = row['sunlight']
            pv_yield = row['PV yield[kW/kWp]']
            factory_demand = row['demand']
            electricity_price = row['buy']
            sell_price = row['sell']
            # electricity_price = self.grid.get_price_for_time(time)

            # if time == '00:00':
            #     self.day_generated.append(self.generated)
            #     self.generated = 0
            # if time.endswith('14:00'):
            #     soc = self.ess.storage / self.ess.capacity
            #     self.hour_stored.append(soc)
            # if time.endswith('08:00'):
            #     soc = self.ess.storage / self.ess.capacity
            #     self.hour_stored_2.append(soc)

            # `generated_pv_power`: the power generated by the PV in kW
            # `generated_pv_energy`:  the energy generated by the PV in kWh
            generated_pv_power = self.pv.capacity * pv_yield
            generated_pv_energy = generated_pv_power * time_interval * self.pv.loss

            self.pv_generated_kWh.append(generated_pv_energy)
            self.factory_demand.append(factory_demand)
            self.buy_price_kWh.append(electricity_price)
            self.sell_price_kWh.append(sell_price)

            self.generated += generated_pv_energy
            # generated_pv_energy is larger than factory_demand energy
            if generated_pv_energy >= factory_demand * time_interval:
                """
                That means the generated energy is enough to power the factory.
                The surplus energy will be used to charge the ESS.

                surplus_energy: The energy that is left after powering the factory. 
                    formula: generated_pv_energy - factory_demand * time_interval

                charge_to_ess: The energy that will be charged to the ESS. 
                    formula: min(surplus_energy, ess.charge_power * time_interval, ess.capacity - ess.storage)

                surplus_after_ess: The energy that is left after charging the ESS.
                """
                surplus_energy = generated_pv_energy - factory_demand * time_interval
                charge_to_ess = min(surplus_energy, self.ess.charge_power * time_interval, self.ess.capacity - self.ess.storage)
                self.ess.storage += charge_to_ess
                surplus_after_ess = surplus_energy - charge_to_ess
                """
                If there is still surplus energy after charging the ESS, and the generated PV power is greater than the sum of the ESS's charge power and the factory's demand power, the surplus energy will be sold to the grid.
                """
                if surplus_after_ess > 0 and generated_pv_power > self.ess.charge_power + factory_demand:
                    sold_to_grid = surplus_after_ess
                    sell_income = sold_to_grid * sell_price
                    total_benefit += sell_income
                """
                Saved energy is the energy that is saved by using the PV to power the factory.
                """
                saved_energy = factory_demand * time_interval
                self.grid_need_power_kW.append(0)
            else:
                """
                If the generated energy is not enough to power the factory, the ESS will be used to supplement the energy.

                needed_from_ess: The energy that is needed from the ESS to power the factory.
                    formula: factory_demand * time_interval - generated_pv_energy
                """
                needed_from_ess = factory_demand * time_interval - generated_pv_energy
                """
                If the ESS has enough stored energy to power the factory, the energy will be taken from the ESS.
                """
                if self.ess.storage * self.ess.loss >= needed_from_ess:
                    if self.ess.discharge_power * time_interval * self.ess.loss < needed_from_ess:
                        discharging_power = self.ess.discharge_power * time_interval 
                    else:
                        discharging_power = needed_from_ess / self.ess.loss

                    self.ess.storage -= discharging_power
                    """
                    In this case, the energy that is needed from the grid is 0.
                    """
                    saved_energy = generated_pv_energy + discharging_power * self.ess.loss
                    self.grid_need_power_kW.append(0)
                else:
                    """
                    If the ESS does not have enough stored energy to power the factory, the energy will be taken from the grid.
                    """
                    if self.grid.capacity * time_interval + generated_pv_energy + self.ess.storage * self.ess.loss < factory_demand * time_interval:
                        self.afford = False
                        self.overload_cnt+=1
                        self.unmet.append((index,time,factory_demand,generated_pv_power))
                    saved_energy = generated_pv_energy + self.ess.storage * self.ess.loss
                    self.ess.storage = 0
                    needed_from_grid = factory_demand * time_interval - saved_energy
                    net_grid = min(self.grid.capacity * time_interval, needed_from_grid) * self.grid.loss
                    self.grid_need_power_kW.append(needed_from_grid * 4)
            total_gen += saved_energy
            benefit = (saved_energy) * electricity_price
            cost = net_grid * electricity_price
            total_netto_benefit += benefit
            total_benefit += benefit - cost
            print_season_flag = False
            if print_season_flag == True:
                week_start = self.season_start
                week_end = self.week_length + week_start
                if index in range(week_start, week_end):
                    self.spring_week_gen.append(generated_pv_power)
                    self.spring_week_soc.append(self.ess.storage / self.ess.capacity)
                self.ess_rest = self.ess.storage
                # summer
                week_start += self.season_step
                week_end += self.season_step
                if index in range(week_start, week_end):
                    self.summer_week_gen.append(generated_pv_power)
                    self.summer_week_soc.append(self.ess.storage / self.ess.capacity)
                # # autumn
                week_start += self.season_step
                week_end += self.season_step
                if index in range(week_start, week_end):
                    self.autumn_week_gen.append(generated_pv_power)
                    self.autumn_week_soc.append(self.ess.storage / self.ess.capacity)
                week_start += self.season_step
                week_end += self.season_step
                if index in range(week_start, week_end):
                    self.winter_week_gen.append(generated_pv_power)
                    self.winter_week_soc.append(self.ess.storage / self.ess.capacity)

        return (total_benefit, total_netto_benefit, total_gen)