use rand::Rng;

use crate::prelude::*;

use super::FResult;

trait Extra
{
  fn cot(self) -> Self;
}

impl Extra for f64
{
  fn cot(self) -> Self
  {
    self.cos() / self.sin()
  }
}

macro_rules! declare_constant {
    ($name: ident, $class_name: ident, $rust_const: ident) => {
      #[derive(Debug, Default)]
      pub(super) struct $class_name {}

      impl $class_name
      {
        fn call_impl() -> FResult<f64>
        {
          Ok(std::f64::consts::$rust_const)
        }
      }

      super::declare_function!($name, $class_name, call_impl() -> f64);
    };
}

declare_constant!(pi, Pi, PI);
declare_constant!(e, E, E);

#[derive(Debug, Default)]
pub(super) struct Rand {}

impl Rand
{
  fn call_impl() -> FResult<f64>
  {
    Ok(rand::rng().random())
  }
}

super::declare_function!(rand, Rand, call_impl() -> Vec<f64>);

macro_rules! define_f64_function {
    ($name: ident, $class_name: ident, $rust_func: ident $(, $arg0: expr)*) => {
      #[derive(Debug, Default)]
      pub(super) struct $class_name {}

      impl $class_name
      {
        fn call_impl(value: f64) -> FResult<f64>
        {
          Ok(value.$rust_func($($arg0)*))
        }
      }

      super::declare_function!($name, $class_name, call_impl(f64) -> f64);

    };
    ($name: ident, $class_name: ident) => {
        define_f64_function!($name, $class_name, $name);
    };
}

define_f64_function!(exp, Exp);
define_f64_function!(log, Log, ln);
define_f64_function!(log10, Log10, log10);
define_f64_function!(sin, Sin);
define_f64_function!(cos, Cos);
define_f64_function!(tan, Tan);
define_f64_function!(cot, Cot, cot);
define_f64_function!(acos, Acos);
define_f64_function!(asin, Asin);
define_f64_function!(atan, Atan);
define_f64_function!(floor, Floor);
define_f64_function!(ceil, Ceil);
define_f64_function!(degrees, Degrees, to_degrees);
define_f64_function!(radians, Radians, to_radians);

#[derive(Debug, Default)]
pub(super) struct Atan2 {}

impl Atan2
{
  fn call_impl(x: f64, y: f64) -> FResult<f64>
  {
    Ok(x.atan2(y))
  }
}

super::declare_function!(atan2, Atan2, call_impl(f64, f64) -> f64);

#[cfg(test)]
mod test
{
  use crate::functions::math::Extra as _;
  #[test]
  fn test_cot()
  {
    assert_eq!(0.5_f64.cot(), 1.830487721712452);
  }
}
