//! Math functions: abs, sqrt, round, etc.

use std::any::TypeId;

use crate::func::def::{FunctionDef, ParameterBindingProvider, Parameters};
use crate::func::utils::drop_decimal_scale;
use crate::types::matcher::{ArrayMatcher, ExactMatcher, NumericMatcher};
use crate::types::{Type, DOUBLE, INT};

/// Absolute value: abs(x)
#[derive(Default, Clone)]
pub struct Abs;

impl FunctionDef for Abs {
    fn name(&self) -> &'static str {
        "abs"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Cube root: cbrt(x)
#[derive(Default, Clone)]
pub struct Cbrt;

impl FunctionDef for Cbrt {
    fn name(&self) -> &'static str {
        "cbrt"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Ceiling: ceil(x) or ceiling(x)
#[derive(Default, Clone)]
pub struct Ceil;

impl FunctionDef for Ceil {
    fn name(&self) -> &'static str {
        "ceil"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Convert radians to degrees: degrees(x)
#[derive(Default, Clone)]
pub struct Degrees;

impl FunctionDef for Degrees {
    fn name(&self) -> &'static str {
        "degrees"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Euler's number: e()
#[derive(Default, Clone)]
pub struct Euler;

impl FunctionDef for Euler {
    fn name(&self) -> &'static str {
        "e"
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Exponential: exp(x)
#[derive(Default, Clone)]
pub struct Exp;

impl FunctionDef for Exp {
    fn name(&self) -> &'static str {
        "exp"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Floor: floor(x)
#[derive(Default, Clone)]
pub struct Floor;

impl FunctionDef for Floor {
    fn name(&self) -> &'static str {
        "floor"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Natural logarithm: ln(x)
#[derive(Default, Clone)]
pub struct Ln;

impl FunctionDef for Ln {
    fn name(&self) -> &'static str {
        "ln"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Logarithm with base: log(base, x)
#[derive(Default, Clone)]
pub struct Log;

impl FunctionDef for Log {
    fn name(&self) -> &'static str {
        "log"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("b", NumericMatcher::default())
            .with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Base-10 logarithm: log10(x)
#[derive(Default, Clone)]
pub struct Log10;

impl FunctionDef for Log10 {
    fn name(&self) -> &'static str {
        "log10"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Base-2 logarithm: log2(x)
#[derive(Default, Clone)]
pub struct Log2;

impl FunctionDef for Log2 {
    fn name(&self) -> &'static str {
        "log2"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Pi constant: pi()
#[derive(Default, Clone)]
pub struct Pi;

impl FunctionDef for Pi {
    fn name(&self) -> &'static str {
        "pi"
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Power: pow(x, p) or power(x, p)
#[derive(Default, Clone)]
pub struct Pow;

impl FunctionDef for Pow {
    fn name(&self) -> &'static str {
        "pow"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("x", NumericMatcher::default())
            .with("p", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Convert degrees to radians: radians(x)
#[derive(Default, Clone)]
pub struct Radians;

impl FunctionDef for Radians {
    fn name(&self) -> &'static str {
        "radians"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Round to nearest integer: round(x)
#[derive(Default, Clone)]
pub struct Round1;

impl FunctionDef for Round1 {
    fn name(&self) -> &'static str {
        "round"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        drop_decimal_scale(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Round to d decimal places: round(x, d)
#[derive(Default, Clone)]
pub struct Round2;

impl FunctionDef for Round2 {
    fn name(&self) -> &'static str {
        "round"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("x", NumericMatcher::default())
            .with("d", ExactMatcher::of(INT))
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Sign of a number: sign(x)
#[derive(Default, Clone)]
pub struct Sign;

impl FunctionDef for Sign {
    fn name(&self) -> &'static str {
        "sign"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Square root: sqrt(x)
#[derive(Default, Clone)]
pub struct Sqrt;

impl FunctionDef for Sqrt {
    fn name(&self) -> &'static str {
        "sqrt"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(DOUBLE)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Truncate towards zero: truncate(x)
#[derive(Default, Clone)]
pub struct Truncate;

impl FunctionDef for Truncate {
    fn name(&self) -> &'static str {
        "truncate"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        drop_decimal_scale(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Width bucket (4-arg): width_bucket(x, bound1, bound2, n)
#[derive(Default, Clone)]
pub struct WidthBucket4;

impl FunctionDef for WidthBucket4 {
    fn name(&self) -> &'static str {
        "width_bucket"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("x", NumericMatcher::default())
            .with("bound1", NumericMatcher::default())
            .with("bound2", NumericMatcher::default())
            .with("n", ExactMatcher::of(INT))
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(INT)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Width bucket (2-arg): width_bucket(x, bins)
#[derive(Default, Clone)]
pub struct WidthBucket2;

impl FunctionDef for WidthBucket2 {
    fn name(&self) -> &'static str {
        "width_bucket"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("x", NumericMatcher::default())
            .with("bins", ArrayMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(INT)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}
