#!/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.utilities.enums import StatusType
from pricelib.common.pricing_engine_base import QuadEngine
from pricelib.common.date_handler import global_evaluation_date
from .quad_snowball_engine import QuadSnowballEngine


class QuadEuroSnowEngine(QuadSnowballEngine):
    """欧式雪球 数值积分定价引擎
    由于仅到期观察敲入，只需要按敲出观察日反向迭代，重写父类的_data_preprocess和_backward_induction方法
    """

    def _data_preprocess(self, prod, calculate_date, spot, vol):
        """预处理：生成反向迭代的步数、fft的对数价格向量及边界的对数价格向量
        每日观察敲入雪球：
            网格在时间方向的步数是按逐个交易日迭代，还会生成每个交易日对应的下一个敲出观察日对应的付息日
        欧式雪球(仅到期观察敲入): 【子类QuadEuroSnowEngine对此方法重写】
            网格在时间方向的步数是按敲出观察日迭代
        """
        # 生成反向迭代的步数
        if 0 in self.out_dates:  # 如果估值日就是敲出观察日，则积分迭代步数少一次
            self.backward_steps = len(self.out_dates) - 1
        else:
            self.backward_steps = len(self.out_dates)

        # 初始化fft的对数价格向量及边界的对数价格向量
        self.init_grid(spot, vol, self._maturity_business_days / prod.t_step_per_year)
        self.s_vec = np.exp(self.ln_s_vec)
        self.j_vec = np.linspace(0, self.backward_steps, self.backward_steps + 1)  # 生成时间格点
        self.v_not_in = np.zeros(shape=(self.n_points, self.backward_steps + 1))
        self.v_knock_in = np.zeros(shape=(self.n_points, self.backward_steps + 1))

        # 最后一次迭代的时间步长: 从第一个敲出观察日到估值日的时间差, 用于_last_step_single_pv方法
        if self.out_dates[-1] == 0 and len(self.out_dates) > 1:  # 如果估值日就是敲出观察日
            self.last_time_step = self.out_dates[-2] / prod.t_step_per_year  # 最后一步的时间差是下一个敲出观察日
        else:
            self.last_time_step = self.out_dates[-1] / prod.t_step_per_year

        # 每个迭代日就是敲出观察日，对应的付息日与观察日的自然日年化之差
        self.next_diff_obspaydate = self.diff_obs_pay_dates  # 计息时间数

        # 每个迭代日就是敲出观察日，对应的票息支付日
        self.next_paydate = self.pay_dates  # 计息时间数

    def _backward_induction(self):
        """从倒数第一个观察日向第一个观察日逆向迭代"""
        prod = self.prod
        _out_dates = np.flip(self.out_dates) / prod.t_step_per_year
        if 0 in _out_dates:  # 如果估值日就是敲出观察日，则积分迭代步数少一次
            dt_vec = np.diff(_out_dates)[1:]
        else:
            dt_vec = np.diff(_out_dates)
        # 按照敲出观察日逆向迭代，迭代的第一次是倒数第二个观察日，迭代的最后一次是正数第一个观察日
        for j in range(self.backward_steps - 1, 0, -1):
            self.v_not_in[:, j] = self.fft_step_backward(self.ln_s_vec, self.ln_s_vec,
                                                         self.v_not_in[:, j + 1], dt_vec[j - 1])
            # 敲出票息是否为绝对收益
            coupon_t = 1 if prod.trigger else self.next_paydate[j - 1]
            # 如果敲出价是浮动的，调整敲出价
            self.next_barrier_out = (self._barrier_out[j - 1]
                                     if isinstance(prod.barrier_out, (list, np.ndarray)) else prod.barrier_out)
            self.out_idxs = np.array(np.where(self.s_vec >= self.next_barrier_out)[0])
            # 如果敲出票息是浮动的，调整敲出票息
            self.next_coupon_out = (self._coupon_out[j - 1]
                                    if isinstance(prod.coupon_out, (list, np.ndarray)) else prod.coupon_out)
            # 发生敲出的payoff
            knock_out_payoff = self.itm[self.out_idxs] + (
                prod.margin_lvl + self.next_coupon_out * coupon_t) * prod.s0 * self.process.interest.disc_factor(
                self.next_paydate[j - 1], self.next_paydate[j - 1] - self.next_diff_obspaydate[j - 1])
            # 用knock_out_payoff覆盖敲出价上方的衍生品价值
            self.v_not_in[self.out_idxs, j] = self.v_knock_in[self.out_idxs, j] = knock_out_payoff



