//! # Asian
//!
//! $$
//! V_0=e^{-rT}\,\mathbb E\!\left[\left(\frac1T\int_0^T S_tdt-K\right)^+\right]
//! $$
//!
use stochastic_rs_distributions::special::norm_cdf;

use crate::traits::PricerExt;
use crate::traits::TimeExt;

#[derive(Debug, Clone)]
pub struct AsianPricer {
  /// Underlying price
  pub s: f64,
  /// Volatility
  pub v: f64,
  /// Strike price
  pub k: f64,
  /// Risk-free rate
  pub r: f64,
  /// Dividend yield
  pub q: Option<f64>,
  /// Time to maturity in years
  pub tau: Option<f64>,
  /// Evaluation date
  pub eval: Option<chrono::NaiveDate>,
  /// Expiration date
  pub expiration: Option<chrono::NaiveDate>,
}

impl AsianPricer {
  pub fn new(
    s: f64,
    v: f64,
    k: f64,
    r: f64,
    q: Option<f64>,
    tau: Option<f64>,
    eval: Option<chrono::NaiveDate>,
    expiration: Option<chrono::NaiveDate>,
  ) -> Self {
    Self {
      s,
      v,
      k,
      r,
      q,
      tau,
      eval,
      expiration,
    }
  }

  pub fn builder(s: f64, v: f64, k: f64, r: f64) -> AsianPricerBuilder {
    AsianPricerBuilder {
      s,
      v,
      k,
      r,
      q: None,
      tau: None,
      eval: None,
      expiration: None,
    }
  }
}

#[derive(Debug, Clone)]
pub struct AsianPricerBuilder {
  s: f64,
  v: f64,
  k: f64,
  r: f64,
  q: Option<f64>,
  tau: Option<f64>,
  eval: Option<chrono::NaiveDate>,
  expiration: Option<chrono::NaiveDate>,
}

impl AsianPricerBuilder {
  pub fn q(mut self, q: f64) -> Self {
    self.q = Some(q);
    self
  }
  pub fn tau(mut self, tau: f64) -> Self {
    self.tau = Some(tau);
    self
  }
  pub fn eval(mut self, eval: chrono::NaiveDate) -> Self {
    self.eval = Some(eval);
    self
  }
  pub fn expiration(mut self, expiration: chrono::NaiveDate) -> Self {
    self.expiration = Some(expiration);
    self
  }
  pub fn build(self) -> AsianPricer {
    AsianPricer {
      s: self.s,
      v: self.v,
      k: self.k,
      r: self.r,
      q: self.q,
      tau: self.tau,
      eval: self.eval,
      expiration: self.expiration,
    }
  }
}

impl PricerExt for AsianPricer {
  fn calculate_call_put(&self) -> (f64, f64) {
    let T = self.calculate_tau_in_years();
    let v = self.v / 3.0_f64.sqrt();
    let b = 0.5 * (self.r - self.q.unwrap_or(0.0) - 0.5 * v.powi(2) / 6.0);
    let d1 = ((self.s / self.k).ln() + (b + 0.5 * v.powi(2) * T)) / (v * T.sqrt());
    let d2 = d1 - v * T.sqrt();

    let call = self.s * ((b - self.r) * T).exp() * norm_cdf(d1)
      - self.k * (-self.r * T).exp() * norm_cdf(d2);
    let put = -self.s * ((b - self.r) * T).exp() * norm_cdf(-d1)
      + self.k * (-self.r * T).exp() * norm_cdf(-d2);

    (call, put)
  }

  fn calculate_price(&self) -> f64 {
    self.calculate_call_put().0
  }
}

impl TimeExt for AsianPricer {
  fn tau(&self) -> Option<f64> {
    self.tau
  }

  fn eval(&self) -> Option<chrono::NaiveDate> {
    self.eval
  }

  fn expiration(&self) -> Option<chrono::NaiveDate> {
    self.expiration
  }
}
