# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
import copy
import datetime


class PvOptimizationTool(object):
    """光伏优化工具"""
    """
                    "pv_system":{
                                "user_type": "工商业", #建筑类型
                                "install_space":10000,       #安装面积m2 
                                "efficiency":0.8,         #光伏发电效率
                                "first_year_decay_rate":2.5, #首年衰减率
                                "first10_year_decay_rate":0.8, #第2-10年衰减率
                                "other_year_decay_rate":0.7, #第11-25年衰减率
                                "evaluate_year": 25,  #评估年限
                                "self_use_ratio":0.7, #自发自用比例
                                "peak_sunshine_hours": 1347.945 #年峰值日照小数数
                                },
                    "price":{
                            "rmb_per_wp":7.5元/Wp,       #建设单价
                            "self_use_price_discout":100.0,       #自发自用电价折扣
                            "sel_use_per_kwh": 1元/kWh,   #自发自用电价
                            "state_subsidy": 0.42 元/kWh, #国家补贴 
                            "local_subsidy": 0.25 元/kWh, #地方补贴 
                            "local_subsidy_year":5, #地方补贴年限
                            "first_install_subsidy": 0.0 元/Wp, #初始安装补贴 
                            "coal_in_grid": 0.43 元/kWh, #脱硫电价
                            "spfv_price":{
                                            "section_s":{"price":0, "time_range": ""}, 
                                            "section_p":{"price":0.8888, "time_range": "14:00-17:00;19:00-22:00"},
                                            "section_f":{"price":0.5503, "time_range": "8:00-14:00;17:00-19:00;22:00-24:00"},
                                            "section_v":{"price":0.2900, "time_range":"00:00-8:00"}
                            } #分时电价及对应时段小时数                            
                            }
                    env_benifit_param = {
                            "one_family_kwh": 3600, #一户家庭一年用3600度电
                            "CO2":0.822,       #二氧化碳排放kg/kWh
                            "SO2": 0.00039,   #二氧化硫排放kg/kWh
                            "NOx": 0.00036 , #氮氧化合物kg/kWh 
                            "smoke": 0.00008 , #烟尘kg/kWh 
                            "coal": 0.312, #煤耗kg/kWh 
                            "h2o": 0.0040, #纯净水m3/kWh
                            "tree": 18.3         #=0.822/18.3相当于植树棵/kWh 1棵树1年可吸收18.3千克CO2
                        }
                    inline_var:{
                                    "inline_capacity":进线容量,kVA, 
                                    "peak_kwh":峰时段电量kwh, 
                                    "peak_charge":峰时段电费元, 
                                    "flat_kwh":平时段电量kwh, 
                                    "flat_charge":平时段电费元
                                }
                    df_load :pandas.DataFrame       column_name:["quarter_time", "load_curve"]
                    df_pv: pandas.DataFrame         column_name:["quarter_time", "pv_curve"]                          
    """

    def __init__(self, pv_system, price, env_benifit_param, df_load, df_pv,
                 inline_var):
        self.pv_system = pv_system
        self.price = price
        self.env_benifit_param = env_benifit_param
        self.curve = self._merge_curve(df_load, df_pv)
        self.inline_var = inline_var

        self.self_use_price = self._cal_mean_price()
        self.time_section = self._check_time_section()
        self.income_table = 0

        self.invest_capacity = 0
        self.invest_evaluate = 0
        self.opt_analysis = 0
        self.opt_curve = 0
        self.msg = ''

    def _merge_curve(self, df_load, df_pv):
        pv_kWp = self.pv_system["install_space"] * 100 / 1000.0
        df_load1 = copy.deepcopy(df_load)
        df_pv1 = copy.deepcopy(df_pv)
        df_load1.set_index("quarter_time", inplace=True)
        df_load1 = df_load1.resample("1H", closed="left", label="left").mean()
        df_load1.reset_index(inplace=True)
        df_load1["time_point"] = df_load1["quarter_time"].apply(
            lambda x: datetime.datetime.strftime(x, "%H:%M:%S"))
        df_pv1["time_point"] = df_pv1["quarter_time"].apply(
            lambda x: datetime.datetime.strftime(x, "%H:%M:%S"))
        df_pv1["pv_curve"] = df_pv1["pv_curve"] * pv_kWp / 1000.0 * \
                             self.pv_system["efficiency"]
        df_new = pd.merge(df_load1[["time_point", "load_curve"]],
                          df_pv1[["time_point", "pv_curve"]], how="left",
                          on="time_point")
        return df_new

    def cal_invest_capacity(self):
        rst = {}
        rst["install_space"] = self.pv_system["install_space"]
        rst["capacity"] = self.pv_system["install_space"] * 100 / 1000.0
        rst["ttl_invest"] = rst["capacity"] * (
                    self.price["rmb_per_wp"] - self.price[
                "first_install_subsidy"]) * 1000.0  # 元
        rst["first_year_ttl_kwh"] = rst["capacity"] * self.pv_system[
            "efficiency"] * self.pv_system["peak_sunshine_hours"]  # 度
        return rst

    def time_15min_parse(self, time_range):
        time_sections = time_range.split(";")
        sections = []
        for section in time_sections:
            section_range = section.split("-")
            start_time = pd.Timestamp(section_range[0])
            if section_range[1] == "24:00":
                section_range[1] = "00:00"
                stop_time = pd.Timestamp(section_range[1]) + pd.Timedelta(
                    days=1)
            else:
                stop_time = pd.Timestamp(section_range[1])
            current_time = start_time  # + pd.Timedelta(seconds=900)
            while True:
                if current_time < stop_time:
                    sections.append(current_time)
                    current_time += pd.Timedelta(seconds=900)
                else:
                    break
        return sections

    def time_1hour_parse(self):
        time_index_series = self.time_section.copy(deep=True)
        time_index_series = time_index_series.set_index("quarter_time")
        time_section_resample = time_index_series.resample("1H", closed="left",
                                                           label="left").max()  # , loffset="-1H"
        time_section_resample.reset_index(inplace=True)
        time_section_resample["time_point"] = time_section_resample[
            "quarter_time"].apply(
            lambda x: datetime.datetime.strftime(x, "%H:%M:%S"))
        df_curve = copy.deepcopy(self.curve)
        # df_curve["time_point"] = df_curve["quarter_time"].apply(lambda x:datetime.datetime.strftime(x, "%H:%M:%S"))
        df_new = pd.merge(time_section_resample[["time_point", "spfv_flag"]],
                          df_curve[["time_point", "load_curve", "pv_curve"]],
                          how="left", on="time_point")
        df_new["load_pv_curve"] = df_new["load_curve"] - df_new["pv_curve"]
        df_new.set_index("time_point", inplace=True)
        # time_section_resample = time_section_resample.reset_index()
        # df = pd.concat([time_section_resample, self.curve], axis=1, ignore_index=False)
        return df_new

    def _check_time_section(self):
        param = {"section_s": 3, "section_p": 2, "section_f": 1, "section_v": 0}
        hour = []
        spfv_flag = []
        for key in self.price["spfv_price"].keys():
            time_point = self.time_15min_parse(
                self.price["spfv_price"][key]["time_range"])
            hour.extend(time_point)
            num_15mins = len(time_point)
            spfv_flag.extend([param[key]] * num_15mins)

        time_section = pd.DataFrame(
            {"quarter_time": hour, "spfv_flag": spfv_flag})
        time_section = time_section.sort_values(by="quarter_time")
        time_section.index = range(96)
        return time_section

    def _cal_mean_price(self):
        mean_price = 0.0
        if not self.price["sel_use_per_kwh"]:
            mean_price = (self.inline_var["peak_charge"] + self.inline_var[
                "flat_charge"]) / (self.inline_var["peak_kwh"] +
                                   self.inline_var["flat_kwh"] + 0.0001)
        else:
            mean_price = self.price["sel_use_per_kwh"]

        return mean_price * self.price["self_use_price_discout"] / 10.0

    def _cal_income_table(self):

        if self.pv_system["evaluate_year"] < 20:
            evaluate_year = 20
        else:
            evaluate_year = self.pv_system["evaluate_year"]
        if self.price["local_subsidy_year"] >= evaluate_year:
            self.price["local_subsidy_year"] = evaluate_year
        decay_rate = np.array([0, self.pv_system["first_year_decay_rate"]] +
                              [self.pv_system["first10_year_decay_rate"]] * 9 +
                              [self.pv_system["other_year_decay_rate"]] * (
                                          evaluate_year - 10 - 1)) / 100.0
        end_year_percent = 1 - decay_rate.cumsum()

        year_gen_kwh = self.invest_capacity[
                           "first_year_ttl_kwh"] * end_year_percent
        state_subsidy = np.array(
            [self.price["state_subsidy"]] * 20 + [0] * (evaluate_year - 20))
        local_subsidy = np.array(
            [self.price["local_subsidy"]] * self.price["local_subsidy_year"] + [
                0] * (evaluate_year - self.price["local_subsidy_year"]))
        self_use_price = np.array([
                                      self.self_use_price] * evaluate_year + state_subsidy + local_subsidy)
        in_grid_price = np.array([self.price[
                                      "coal_in_grid"]] * evaluate_year + state_subsidy + local_subsidy)
        income_table = pd.DataFrame(
            {
                "year": range(1, evaluate_year + 1),
                "decay_rate": decay_rate,
                "end_year_percent": end_year_percent,
                "year_gen_kwh": year_gen_kwh,
                "self_use_price": self_use_price,
                "in_grid_price": in_grid_price
            }
        )

        income_table["year_benifit"] = (
                    income_table["year_gen_kwh"] * self.pv_system[
                "self_use_ratio"] * income_table["self_use_price"] +
                    income_table["year_gen_kwh"] * (
                                1.0 - self.pv_system["self_use_ratio"]) *
                    income_table["in_grid_price"])
        income_table["cumsum_benifit"] = income_table["year_benifit"].cumsum() - \
                                         self.invest_capacity["ttl_invest"]
        income_table["year_benifit_ratio"] = income_table["year_benifit"] / (
                    self.invest_capacity["ttl_invest"] + 0.001)
        income_table = income_table.set_index("year")
        n_years = (income_table["cumsum_benifit"] < 0).sum()
        if n_years < 1:
            self.invest_cycle = 0
        elif n_years >= len(income_table["cumsum_benifit"]):
            self.invest_cycle = evaluate_year
        else:
            self.invest_cycle = n_years - income_table["cumsum_benifit"][
                n_years] / (income_table["year_benifit"][n_years + 1] + 0.0001)

        return income_table

    def cal_invest_evaluate(self):
        rst = {}
        rst["invest_income"] = self._cal_invest_income()
        rst["env_benifit_per_year"] = self._cal_env_benifit()
        return rst

    def _cal_invest_income(self):
        rst = {}
        self.income_table = self._cal_income_table()
        rst["first_year_income"] = self.income_table["year_benifit"][1]
        rst["first_year_income_rate"] = self.income_table["year_benifit_ratio"][
            1]
        rst["first_year_month_income"] = rst["first_year_income"] / 12.0
        rst["invest_income_year"] = self.invest_cycle
        return rst

    def _cal_env_benifit(self):
        env_benifit = {}
        for key in self.env_benifit_param.keys():
            if key == "tree":
                env_benifit[key] = self.invest_capacity["first_year_ttl_kwh"] * \
                                   self.env_benifit_param["CO2"] / (
                                               self.env_benifit_param[
                                                   key] + 0.001)
            elif key == "one_family_kwh":
                env_benifit[key] = self.invest_capacity[
                                       "first_year_ttl_kwh"] / (
                                               self.env_benifit_param[
                                                   key] + 0.00001)
            else:
                env_benifit[key] = self.invest_capacity["first_year_ttl_kwh"] * \
                                   self.env_benifit_param[key]
        return env_benifit

    def cal_opt_analysis(self):
        rst = {}
        rst["peak_clip"] = self._peak_clip()
        rst["max_demand"] = self._max_demand_control()
        rst["economic_operation"] = self._economic_operation()
        return rst

    def _peak_clip(self):
        rst = {}
        df = self.time_1hour_parse()
        pv_not_null = df[df["pv_curve"] > 0]
        pv_peak = pv_not_null[pv_not_null["spfv_flag"] >= 2]

        if pv_peak["pv_curve"].sum() > 0:
            peak_clip_flag = True
            peak_clip_kwh = pv_peak["pv_curve"].sum() * 30
        else:
            peak_clip_flag = False
            peak_clip_kwh = 0.0
        rst["peak_clip_flag"] = peak_clip_flag
        rst["peak_clip_kwh"] = self.fix_decimal_points(peak_clip_kwh,
                                                       decimal_num=2)

        opt_curve = df[["load_curve", "pv_curve", "load_pv_curve"]]
        opt_curve.index.name = "quarter_time"
        self.opt_curve = opt_curve
        return rst

    def _max_demand_control(self):
        rst = {}
        old_max_demand = self.opt_curve["load_curve"].max()
        new_max_demand = self.opt_curve["load_pv_curve"].max()
        old_new = old_max_demand - new_max_demand
        try:
            if self.inline_var["inline_capacity"]:
                percent_flag = (old_new / self.inline_var[
                    "inline_capacity"] >= 0.01)
            else:
                percent_flag = (old_new >= 10)

            if percent_flag:
                max_demand_flag = True
                max_demand_benifit = old_new * self.price["max_demand"]
            else:
                max_demand_flag = False
                max_demand_benifit = 0.0
        except Exception as e:
            max_demand_flag = False
            max_demand_benifit = 0.0
        rst["max_demand_flag"] = max_demand_flag
        rst["max_demand_benifit"] = self.fix_decimal_points(max_demand_benifit,
                                                            decimal_num=2)
        return rst

    def _economic_operation(self):
        rst = {}
        old_max_load = self.opt_curve["load_curve"].max()
        new_max_load = self.opt_curve["load_pv_curve"].max()
        old_new = old_max_load - new_max_load
        try:
            if self.inline_var["inline_capacity"]:
                percent_flag = (old_new / self.inline_var[
                    "inline_capacity"] >= 0.01)
            else:
                percent_flag = (old_new >= 10)

            if percent_flag:
                economic_flag = True
                reduce_peak = old_new
                reduce_load_factor = new_max_load / (
                            self.inline_var["inline_capacity"] + 0.001)
            else:
                economic_flag = False
                reduce_peak = 0.0
                reduce_load_factor = 0.0
        except Exception as e:
            economic_flag = False
            reduce_peak = 0.0
            reduce_load_factor = 0.0
        rst["economic_operation_flag"] = economic_flag
        rst["reduce_peak"] = self.fix_decimal_points(reduce_peak, decimal_num=2)
        rst["reduce_load_factor"] = self.fix_decimal_points(reduce_load_factor,
                                                            decimal_num=4)

        return rst

    @staticmethod
    def fix_decimal_points(v, decimal_num=4):
        """ 将浮点型的值固定小数位, 默认保留4位小数位
        :param v: 浮点型的值
        """
        rlt = v
        fmt = "%.df"
        fmt = fmt.replace('d', str(decimal_num))
        if isinstance(v, float):
            s = fmt % v
            rlt = float(s)
        return rlt

    def fix_output_point(self):
        for key in self.invest_capacity.keys():
            self.invest_capacity[key] = self.fix_decimal_points(
                self.invest_capacity[key], decimal_num=2)

        for key_top in self.invest_evaluate.keys():
            for key in self.invest_evaluate[key_top].keys():
                self.invest_evaluate[key_top][key] = self.fix_decimal_points(
                    self.invest_evaluate[key_top][key], decimal_num=2)

    def output(self):
        self.input_param_process()
        self.invest_capacity = self.cal_invest_capacity()
        self.invest_evaluate = self.cal_invest_evaluate()
        self.opt_analysis = self.cal_opt_analysis()
        self.fix_output_point()

    def input_param_process(self):
        if self.pv_system["efficiency"] <= 0:
            self.pv_system["efficiency"] = 0.1
        elif self.pv_system["efficiency"] >= 1:
            self.pv_system["efficiency"] = 1

        if self.pv_system["self_use_ratio"] <= 0:
            self.pv_system["self_use_ratio"] = 0.0
        elif self.pv_system["self_use_ratio"] >= 1:
            self.pv_system["self_use_ratio"] = 1

        if self.price["self_use_price_discout"] <= 0:
            self.price["self_use_price_discout"] = 0.0
        elif self.price["self_use_price_discout"] >= 10:
            self.price["self_use_price_discout"] = 10


if __name__ == "__main__":

    pv_system = {
        "user_type": "工商业",  # 建筑类型
        "install_space": 1200,  # 装机容量m2
        "efficiency": 0.8,  # 光伏发电效率
        "first_year_decay_rate": 2.5,  # 首年衰减率
        "first10_year_decay_rate": 0.8,  # 2-10年衰减率
        "other_year_decay_rate": 0.7,  # 11-25年衰减率
        "evaluate_year": 25,  # 评估年限
        "self_use_ratio": 0.7,  # 自发自用比例
        "peak_sunshine_hours": 1347.945  # 年峰值日照小数数
    }

    price = {
        "rmb_per_wp": 7.5,  # 建设单价
        "self_use_price_discout": 10.0,  # 自发自用电价折扣
        "sel_use_per_kwh": None,  # 自发自用电价
        # "state_subsidy": 0.42,  # 国家补贴
        "state_subsidy": 0.0,  # 国家补贴
        "local_subsidy": 0.25,  # 地方补贴
        "local_subsidy_year": 5,  # 地方补贴年限
        "first_install_subsidy": 0.0,  # 初始安装补贴
        "coal_in_grid": 0.43,  # 脱硫电价
        "max_demand": 32.0,
        "spfv_price": {
            "section_p": {"price": 0.8888,
                          "time_range": "14:00-17:00;19:00-22:00"},
            "section_f": {"price": 0.5503,
                          "time_range": "8:00-14:00;17:00-19:00;22:00-24:00"},
            "section_v": {"price": 0.2900, "time_range": "00:00-8:00"}
        }  # 分时电价及对应时段小时数
    }
    # 节能减排环境效益
    env_benifit_param = {
        "one_family_kwh": 3600,  # kwh on year
        "CO2": 0.822,  # 二氧化碳排放kg/kWh
        "SO2": 0.00039,  # 二氧化硫排放kg/kWh
        "NOx": 0.00036,  # 氮氧化合物kg/kWh
        "Smoke": 0.00008,  # 烟尘kg/kWh
        "Coal": 0.312,  # 煤耗kg/kWh
        "H2O": 0.0040,  # 纯净水m3/kWh
        "tree": 18.3  # =0.822/18.3相当于植树棵/kWh 1棵树1年可吸收18.3千克CO2
    }
    env_benifit_param = {
        "one_family_kwh": 3600,  # kwh on year
        "CO2": 0.822,  # 二氧化碳排放kg/kWh
        "SO2": 0.00039,  # 二氧化硫排放kg/kWh
        "NOx": 0.00036,  # 氮氧化合物kg/kWh
        "Smoke": 0.00008,  # 烟尘kg/kWh
        "Coal": 0.307,  # 煤耗kg/kWh
        "H2O": 0.0040,  # 纯净水m3/kWh
        "tree": 18.3  # =0.822/18.3相当于植树棵/kWh 1棵树1年可吸收18.3千克CO2
    }
    inline_var = {
        "inline_capacity": 315,
        "peak_kwh": 71.724 * 10000,
        "peak_charge": 71.724 * 10000 * 0.8888,
        "flat_kwh": 92.302 * 10000,
        "flat_charge": 92.302 * 10000 * 0.5503
    }  # 月
    stat_time = pd.date_range("2019-01-01", "2019-01-02", freq="1H")[:-1]
    stat_time1 = pd.date_range("2019-01-01", "2019-01-02", freq="0.25H")[:-1]
    # 光伏小时出力曲线
    pv_curve = pd.Series(
        [0.00, 0.00, 0.00, 0.00, 0.00, 0.83, 7.24, 18.94, 31.94, 43.13, 63.16,
         68.65, 80.03, 76.91, 70.50, 53.72, 34.43, 16.74, 5.01, 0.00, 0.00,
         0.00, 0.00, 0.00], index=range(24))
    pv_curve = pd.Series(
        [0, 0, 0, 0, 0, 7.526, 47.126, 126.118, 242.868, 360.416, 457.112,
         502.836, 513.488, 486.449, 405.981, 297.523, 175.011, 76.375, 21.156,
         0.781, 0, 0, 0, 0], index=range(24))
    # 负荷小时出力曲线
    load_curve = pd.Series(
        [3.03, 3.04, 3.03, 3.03, 3.03, 3.03, 3.18, 74.07, 89.27, 145.34, 141.61,
         141.78, 143.44, 147.09, 156.70, 129.52, 127.47, 128.37, 95.19, 47.12,
         46.36, 45.73, 44.61, 8.64], index=range(24))
    load_curve = 0.03 * pd.Series(
        [2000.0] * 24 + [2050.0, 2100.0, 2150.0, 2200.0] + [2650.0, 3100.0,
                                                            3550.0, 4000.0] + [
            4500.0, 5200.0, 6000.0] + [6800.0, 7900.0, 8600.0, 8800.0] + [
            9200.0, 8500.0, 7700.0, 7200.0] +
        [7000.0, 6900.0, 6800.0, 6700.0] + list(
            np.arange(6500.0, 7501.0, 125.0)) + list(
            np.arange(7500.0, 7001.0, -62.5)) + list(
            np.arange(7000.0, 4001.0, -375.0)) + list(
            np.arange(4000.0, 2001.0, -83.3)))
    df_load = {}
    df_pv = {}
    df_load["quarter_time"] = stat_time1
    df_load["load_curve"] = load_curve
    df_pv["quarter_time"] = stat_time
    df_pv["pv_curve"] = pv_curve
    df_load = pd.DataFrame(df_load)
    df_pv = pd.DataFrame(df_pv)
    # df_curve["load_pv_curve"] = df_curve["load_curve"] - df_curve["pv_curve"]

    obj = PvOptimizationTool(pv_system, price, env_benifit_param, df_load,
                             df_pv, inline_var)
    obj.output()
    try:
        import matplotlib.pyplot as plt

        obj.opt_curve.plot()
        plt.show()
    except Exception:
        pass

    print(
        "**********************************************************************************************************")
    print("输入参数")
    print()
    print(
        "**********************************************************************************************************")
    print("接入规模")
    print(
        "总装机容量:{capacity}kWp 安装面积:{install_space}m2 总投资:{ttl_invest}元 首年发电量:{first_year_ttl_kwh}kwh".format(
            **obj.invest_capacity))
    print(
        "**********************************************************************************************************")
    print("投资与测算")
    print("建筑类型:{user_type}".format(**obj.pv_system))
    print("投资成本:%0.2f元 = %0.2f kWp装机容量 x %0.2f元每瓦成本" % (
    obj.invest_capacity["ttl_invest"], obj.invest_capacity["capacity"],
    (obj.price["rmb_per_wp"] - obj.price["first_install_subsidy"])))
    print("安装面积:{install_space}m2".format(**obj.pv_system))
    print("辐照参数:{peak_sunshine_hours}kWh/kWp/年".format(**obj.pv_system))
    print("首年发电量:{first_year_ttl_kwh}度".format(**obj.invest_capacity))
    print("国家补贴:{state_subsidy}元/度".format(**obj.price))
    print("地方补贴:{local_subsidy}元/度".format(**obj.price))
    print("自发自用电价折扣{self_use_price_discout}折".format(**obj.price))
    print(
        "投资与收益\n建筑类型:工商业\n首年月平均收益:{first_year_month_income}元\n首年总收益:{first_year_income}元\n首年收益率:{first_year_income_rate}\n回本年限:{invest_income_year}\n".format(
            **obj.invest_evaluate["invest_income"]))
    print(
        "**********************************************************************************************************")
    print(
        "碳足迹/年\n提供电力:{one_family_kwh}户\n相当植树:{tree}棵\n节省燃煤:{Coal}kg\n减少CO2:{CO2}kg\n减少SO2:{SO2}kg\n减节省水:{H2O}m3\n".format(
            **obj.invest_evaluate["env_benifit_per_year"]))
    print(
        "**********************************************************************************************************")
    print("优化分析")
    print("经济运行 存在空间,可降低峰荷{reduce_peak}kW,最高负载率降低为{reduce_load_factor:}".format(
        **obj.opt_analysis["economic_operation"]))
    print("需量控制 存在空间,可降低用电成本{max_demand_benifit}元/月".format(
        **obj.opt_analysis["max_demand"]))
    print("移峰填谷 可减少峰段电网用电量{peak_clip_kwh}度/月,消纳光伏用电费用需根据用户与光伏电站协商电价确定".format(
        **obj.opt_analysis["peak_clip"]))
    print(obj.opt_curve)