#!/user/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Galaxy Technologies
Licensed under the Apache License, Version 2.0
"""
import numpy as np
from pricelib.common.date_handler import global_evaluation_date
from pricelib.common.pricing_engine_base import FdmGrid
from pricelib.common.utilities.enums import StatusType
from .fdm_autocallable_engine import FdmAutoCallableEngine


class FdmPhoenixEngine(FdmAutoCallableEngine):
    """ 凤凰类 AutoCallable PDE 有限差分法定价引擎：
            较雪球式AutoCallable，增加派息价格，少了敲出和红利票息
            每个派息（敲出）观察日，如果价格高于派息价格，派固定利息，如果发生敲出，合约提前结束；
            发生敲入后，派息方式不变，到期如果未敲出，结构为看跌空头或熊市价差空头
        FCN(Fixed Coupon Note, 固定票息票据)/DCN(Dynamic Coupon Note, 动态票息票据)/Phoenix凤凰票据
            FCN：无派息价格（派息价格为0），每月固定派息；到期日观察是否敲入
            DCN：有派息价格，每个观察日如果价格高于派息价格，派固定利息；到期日观察是否敲入
            Phoenix：有派息价格，每个观察日如果价格高于派息价格，派固定利息；每日观察是否敲入
    """

    def calc_present_value(self, prod, t=None, spot=None):
        """计算现值
        Args:
            prod: Product产品对象
            t: datetime.date，估值日; 如果是None，则使用全局估值日globalEvaluationDate
            spot: float，估值日标的价格，如果是None，则使用随机过程的当前价格
        Returns: float，现值
        """
        self.prod = prod
        calculate_date = global_evaluation_date() if t is None else t
        _maturity_business_days = prod.trade_calendar.business_days_between(calculate_date,
                                                                            prod.end_date) / prod.t_step_per_year
        if spot is None:
            spot = self.process.spot()

        self.t_step = prod.trade_calendar.business_days_between(calculate_date, prod.end_date)
        self.dt = 0 if self.t_step == 0 else 1 / prod.t_step_per_year  # 时间步长
        self.t_vec = np.linspace(0, _maturity_business_days, self.t_step + 1)

        if self.t_step == 0:  # 如果估值日是到期日
            self.smax = smin = spot  # 价格网格上下界都取估值日标的价格 - 直接计算到期日payoff
        else:
            smin = 0
            self.smax = self.n_smax * prod.s0  # 价格网格上界, 默认为n_smax倍初始价格
        # 初始化已敲入、未敲入的FdmGrid对象

        self.fd_not_in = FdmGrid(smax=self.smax, maturity=_maturity_business_days,
                                 t_step_per_year=prod.t_step_per_year, s_step=self.s_step,
                                 smin=smin)  # 未敲入
        self.fd_knockin = FdmGrid(smax=self.smax, maturity=_maturity_business_days,
                                  t_step_per_year=prod.t_step_per_year, s_step=self.s_step,
                                  smin=smin)  # 已敲入
        # 截取子产品（选取end_date大于calculate_date的产品）
        sub_products = [sub_product for sub_product in prod.sub_products if sub_product.end_date >= calculate_date]
        # 子产品定价
        for i, sub_product in enumerate(sub_products):
            sub_engine = sub_product.engine
            sub_engine.s_step = self.s_step
            sub_engine.n_smax = self.n_smax
            sub_engine.fdm_theta = self.fdm_theta
            sub_value = sub_product.price(t=t, spot=spot)
            # logging.info(f"{i}, {sub_product.__class__.__name__}, {sub_value}")
            """ np.pad
            将矩阵 pde_engine.fd_not_in.v_grid 的列数扩展到与 self.fd_not_in.v_grid 相同的宽度（通过右侧填充零），
            然后将这个扩展后的矩阵加到 mat1上。
                第一个元组 (0, 0)：表示在行方向（第一个维度）不进行填充
                第二个元组 (0, t_step + 1 - column_num)：表示在列方向（第二个维度）：
                    左侧填充 0 列, 右侧填充 t_step + 1 - column_num列
            使用常数填充模式, 填充值为 0
            """
            if sub_product.__class__.__name__ == "DigitalOption":
                # 二元期权子产品
                column_num = sub_engine.fdm.v_grid.shape[1]
                self.fd_not_in.v_grid += np.pad(sub_engine.fdm.v_grid, ((0, 0), (0, self.t_step + 1 - column_num)),
                                                mode='constant', constant_values=0)
                self.fd_knockin.v_grid += np.pad(sub_engine.fdm.v_grid, ((0, 0), (0, self.t_step + 1 - column_num)),
                                                 mode='constant', constant_values=0)
            else:
                # 欧式雪球或限亏雪球子产品
                column_num = sub_engine.fd_not_in.v_grid.shape[1]
                self.fd_not_in.v_grid += np.pad(sub_engine.fd_not_in.v_grid,
                                                ((0, 0), (0, self.t_step + 1 - column_num)), mode='constant',
                                                constant_values=0)
                if sub_product.__class__.__name__ == "EuropeanSnowball":
                    self.fd_knockin.v_grid += np.pad(sub_engine.fd_not_in.v_grid,
                                                     ((0, 0), (0, self.t_step + 1 - column_num)), mode='constant',
                                                     constant_values=0)
                elif sub_product.__class__.__name__ == "FlooredSnowball":
                    self.fd_knockin.v_grid += np.pad(sub_engine.fd_knockin.v_grid,
                                                     ((0, 0), (0, self.t_step + 1 - column_num)), mode='constant',
                                                     constant_values=0)
                else:
                    raise ValueError(
                        f"错误的子产品类型{sub_product.__class__.__name__}，应该是EuropeanSnowball或FlooredSnowball")

        if prod.status == StatusType.DownTouch:
            result = self.fd_knockin.functionize(self.fd_knockin.v_grid[1:-1, 0], kind="cubic")(spot)
        else:
            result = self.fd_not_in.functionize(self.fd_not_in.v_grid[1:-1, 0], kind="cubic")(spot)
        return np.float64(result)

    def _backward_induction(self):
        pass

    def _init_boundary_condition(self):
        pass

    def _init_terminal_condition(self):
        pass
