use crate::CreatedFunction;
use crate::Error;
use crate::function_parser::parser::S;

#[derive(Debug, Clone, PartialEq)]
pub enum CreationError {
    UnknownIdentifier(String, String),
    EmptyOperand(String),
    UnknownSingleOperator(String),
    UnknownDualOperator(String),
}

impl std::error::Error for CreationError {}

impl std::fmt::Display for CreationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CreationError::UnknownIdentifier(name, iden) => write!(
                f,
                "Unknown identifier {name}, expected a function only containing {iden}"
            ),
            CreationError::EmptyOperand(op) => write!(f, "Empty operand: {op}"),
            CreationError::UnknownSingleOperator(op) => {
                write!(f, "Unknown single argument operand: {op}")
            }
            CreationError::UnknownDualOperator(op) => {
                write!(f, "Unknown double argument operand: {op}")
            }
        }
    }
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum EvalError {
    NegativeSqrt,
    NonPositiveLog,
}

impl std::error::Error for EvalError {}

impl std::fmt::Display for EvalError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EvalError::NegativeSqrt => write!(f, "Attempted to take a negative square root"),
            EvalError::NonPositiveLog => write!(f, "Attempted to take a non positive log"),
        }
    }
}

pub fn try_eval(ast: &S, iden: &str, val: f64) -> Result<f64, Error> {
    let f = ast_to_func(ast, iden)?;
    Ok(f(val)?)
}

#[allow(clippy::redundant_closure)]
pub fn ast_to_func(ast: &S, iden: &str) -> CreatedFunction {
    match ast {
        S::AtomIden(name) => {
            if name == iden {
                Ok(Box::new(|x| Ok(x)))
            } else {
                Err(CreationError::UnknownIdentifier(
                    name.to_string(),
                    iden.to_string(),
                ))
            }
        }
        S::AtomNum(num) => {
            let n = *num;
            Ok(Box::new(move |_x| Ok(n)))
        }
        S::Cons(op, args) => {
            let match_op = op.as_str();
            if args.is_empty() {
                Err(CreationError::EmptyOperand(op.into()))
            } else if args.len() == 1 {
                // postfix, prefix, function call
                let ihs = &args[0];
                let inner_func = ast_to_func(ihs, iden)?;
                match match_op {
                    "(" => Ok(Box::new(inner_func)),
                    "+" => Ok(Box::new(inner_func)),
                    "-" => Ok(Box::new(move |x| Ok(-inner_func(x)?))),
                    "sin" => Ok(Box::new(move |x| Ok(inner_func(x)?.sin()))),
                    "cos" => Ok(Box::new(move |x| Ok(inner_func(x)?.cos()))),
                    "sqrt" => Ok(Box::new(move |x| {
                        if inner_func(x)? < 0.0 {
                            return Err(EvalError::NegativeSqrt);
                        }
                        Ok(inner_func(x)?.sqrt())
                    })),
                    "exp" => Ok(Box::new(move |x| Ok(inner_func(x)?.exp()))),
                    "ln" => Ok(Box::new(move |x| {
                        if inner_func(x)? <= 0.0 {
                            return Err(EvalError::NonPositiveLog);
                        }
                        Ok(inner_func(x)?.ln())
                    })),
                    "tan" => Ok(Box::new(move |x| Ok(inner_func(x)?.tan()))),
                    _ => Err(CreationError::UnknownSingleOperator(op.into())),
                }
            } else {
                // infix operators
                let lhs = &args[0];
                let rhs = &args[1];

                let left_function = ast_to_func(lhs, iden)?;
                let right_function = ast_to_func(rhs, iden)?;
                match match_op {
                    "^" => Ok(Box::new(move |x| {
                        Ok(left_function(x)?.powf(right_function(x)?))
                    })),
                    "+" => Ok(Box::new(
                        move |x| Ok(left_function(x)? + right_function(x)?),
                    )),
                    "-" => Ok(Box::new(
                        move |x| Ok(left_function(x)? - right_function(x)?),
                    )),
                    "*" => Ok(Box::new(
                        move |x| Ok(left_function(x)? * right_function(x)?),
                    )),
                    "/" => Ok(Box::new(
                        move |x| Ok(left_function(x)? / right_function(x)?),
                    )),
                    _ => Err(CreationError::UnknownDualOperator(op.into())),
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse;
    use std::f64::consts;
    macro_rules! assert_approx_eq {
        ($a:expr, $b:expr) => {
            assert!(
                ($a - $b).abs() < 1e-10,
                "Expected {} ≈ {}, but difference was {}",
                $a,
                $b,
                ($a - $b).abs()
            )
        };
    }

    #[test]
    fn test_eval() {
        let s = parse!("x").unwrap();
        let res = try_eval(&s, "x", 3.0).unwrap();
        assert_eq!(res, 3.0);

        let s = parse!("cos(0)").unwrap();
        println!("{s}");
        let res = try_eval(&s, "", 0.0).unwrap(); // No variables, value irrelevant
        assert_eq!(res, 1.0); // cos(0) = 1

        let s = parse!("sin(x)").unwrap();
        let res = try_eval(&s, "x", 0.0).unwrap();
        assert_eq!(res, 0.0);

        // waiting implementation for constants
        // let s = parse!("sin(pi/2)");
        // let res = try_eval(&s, "", 0.0);
        // assert_approx_eq!(res, 1.0); // Allow floating-point error

        // Test with variables
        let s = parse!("cos(x)").unwrap();
        let res = try_eval(&s, "x", 0.0).unwrap();
        assert_eq!(res, 1.0);

        let s = parse!("sin(2 * x)").unwrap();
        let res = try_eval(&s, "x", consts::PI / 4.0).unwrap(); // sin(pi/2) = 1
        assert!(res.abs() - 1.0 < 0.01);
    }

    #[test]
    fn arithmetic_with_functions() {
        // Basic arithmetic
        let s = parse!("x + 1").unwrap();
        assert_eq!(try_eval(&s, "x", 2.0).unwrap(), 3.0);

        let s = parse!("3 * x - 2").unwrap();
        assert_eq!(try_eval(&s, "x", 4.0).unwrap(), 10.0); // 3*4 - 2 = 10

        let s = parse!("x / 2 + 1").unwrap();
        assert_eq!(try_eval(&s, "x", 4.0).unwrap(), 3.0); // 4/2 + 1 = 3

        // Functions with arithmetic
        let s = parse!("sin(x) + 1").unwrap();
        assert_approx_eq!(try_eval(&s, "x", 0.0).unwrap(), 1.0); // sin(0) + 1 = 1

        let s = parse!("2 * cos(x)").unwrap();
        assert_approx_eq!(try_eval(&s, "x", 0.0).unwrap(), 2.0); // 2 * cos(0) = 2

        // Mixed operations
        let s = parse!("sin(x) * 2 + cos(x) / 2").unwrap();
        let res = try_eval(&s, "x", 0.0).unwrap();
        assert_approx_eq!(res, 0.5);

        let s = parse!("sin(x * 2) - cos(x / 2)").unwrap();
        let res = try_eval(&s, "x", consts::PI).unwrap();
        assert_approx_eq!(res, 0.0);

        // Functions with ln and sqrt
        let s = parse!("ln(x) + 1").unwrap();
        assert_approx_eq!(try_eval(&s, "x", consts::E).unwrap(), 2.0);

        let s = parse!("2 * sqrt(x*x)").unwrap();
        assert_approx_eq!(try_eval(&s, "x", 2.0).unwrap(), 4.0); // 2 * cos(0) = 2
    }

    #[test]
    fn precedence() {
        // Operator precedence (* before +)
        let s = parse!("x + 2 * 3").unwrap();
        assert_eq!(try_eval(&s, "x", 1.0).unwrap(), 7.0); // 1 + (2*3) = 7

        // Parentheses override
        let s = parse!("(x + 2) * 3").unwrap();
        assert_eq!(try_eval(&s, "x", 1.0).unwrap(), 9.0); // (1+2)*3 = 9

        // Function precedence (sin/cos bind tightest)
        let s = parse!("sin(x + 1) * 2").unwrap();
        println!("{s}");
        let res = try_eval(&s, "x", 0.0).unwrap();
        assert_approx_eq!(res, 2.0 * 1.0_f64.sin()); // sin(1) * 2
    }

    #[test]
    fn edge_cases_zero_mul() {
        // Zero multiplication
        let s = parse!("0 * cos(x)").unwrap();
        assert_eq!(try_eval(&s, "x", 123.0).unwrap(), 0.0);
    }

    #[test]
    fn pow() {
        // Basic power
        let s = parse!("x^2").unwrap();
        assert_eq!(try_eval(&s, "x", 3.0).unwrap(), 9.0);

        let s = parse!("2^x").unwrap();
        assert_eq!(try_eval(&s, "x", 3.0).unwrap(), 8.0);

        let s = parse!("(x+1)^2").unwrap();
        assert_eq!(try_eval(&s, "x", 2.0).unwrap(), 9.0);

        // Power with zero
        let s = parse!("x^0").unwrap();
        assert_eq!(try_eval(&s, "x", 5.0).unwrap(), 1.0);

        let s = parse!("0^x").unwrap();
        assert_eq!(try_eval(&s, "x", 3.0).unwrap(), 0.0);

        // Power with one
        let s = parse!("x^1").unwrap();
        assert_eq!(try_eval(&s, "x", 7.0).unwrap(), 7.0);

        let s = parse!("1^x").unwrap();
        assert_eq!(try_eval(&s, "x", 100.0).unwrap(), 1.0);

        // Nested powers
        let s = parse!("2^(x^2)").unwrap();
        assert_eq!(try_eval(&s, "x", 3.0).unwrap(), 2f64.powf(9.0));

        // Power with negative exponent
        let s = parse!("2^(-x)").unwrap();
        assert_eq!(try_eval(&s, "x", 3.0).unwrap(), 2f64.powf(-3.0));

        // Power with floating point exponent
        let s = parse!("9^0.5").unwrap();
        assert_approx_eq!(try_eval(&s, "x", 0.0).unwrap(), 3.0);

        // Power with expressions in exponent
        let s = parse!("2^(x+1)").unwrap();
        assert_eq!(try_eval(&s, "x", 2.0).unwrap(), 8.0); // 2^(2+1) = 8

        // Power with negative base and even exponent
        let s = parse!("(-2)^2").unwrap();
        assert_eq!(try_eval(&s, "x", 0.0).unwrap(), 4.0);

        // Power with negative base and odd exponent
        let s = parse!("(-2)^3").unwrap();
        assert_eq!(try_eval(&s, "x", 0.0).unwrap(), -8.0);
    }
}
