use std::rc::Rc;

use rug::Float;

use super::{constant::Constant, multiply::Multiply, Environment, Error, Node, NodeEnum, Precedence};

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Subtract {
    left: Rc<NodeEnum>,
    right: Rc<NodeEnum>,
}

impl Node for Subtract {
    fn evaluate(&self, env: &mut super::Environment) -> Result<Rc<super::NodeEnum>, Error> {
        let evaluated_left = self.left.evaluate(env)?;
        let evaluated_right = self.right.evaluate(env)?;

        match (evaluated_left.as_ref(), evaluated_right.as_ref()) {
            // Zero rule
            (NodeEnum::Constant(zero), _) if zero.get_value() == &0.0 => Ok(evaluated_right),
            (_, NodeEnum::Constant(zero)) if zero.get_value() == &0.0 => Ok(evaluated_left),

            // Constant - Constant = Constant
            (NodeEnum::Constant(a), NodeEnum::Constant(b)) => {
                Ok(Rc::new(NodeEnum::from(Constant::new(Float::with_val_64(
                    env.get_float_precision(),
                    a.get_value() - b.get_value(),
                )))))
            }
            // Symbol - Constant we just return the same
            (NodeEnum::Symbol(_), NodeEnum::Constant(_)) => Ok(Rc::new(
                Subtract::new(evaluated_left, evaluated_right).into(),
            )),
            // Constant - Symbol we switch them around so the constant is last
            (NodeEnum::Constant(_), NodeEnum::Symbol(_)) => Ok(Rc::new(
                Subtract::new(evaluated_right, evaluated_left).into(),
            )),

            // (NodeEnum::Multiply(m1), NodeEnum::Multiply(m2)) => {
            //     match (
            //         (m1.get_left().as_ref(), m1.get_right().as_ref()),
            //         (m2.get_left().as_ref(), m2.get_right().as_ref()),
            //     ) {
            //         ((NodeEnum::Symbol(s1), o1), (NodeEnum::Symbol(s2), o2))
            //         | ((o1, NodeEnum::Symbol(s1)), (NodeEnum::Symbol(s2), o2))
            //         | ((NodeEnum::Symbol(s1), o1), (o2, NodeEnum::Symbol(s2)))
            //         | ((o1, NodeEnum::Symbol(s1)), (o2, NodeEnum::Symbol(s2))) => {
            //             if s1 == s2 {
            //                 Multiply::new_rc(
            //                     Subtract::new(Rc::new(o1.clone()), Rc::new(o2.clone()))
            //                         .evaluate(env)?,
            //                     Rc::new(s1.clone().into()),
            //                 )
            //                 .evaluate(env)
            //             } else {
            //                 Ok(Rc::new(
            //                     Subtract::new(evaluated_left, evaluated_right).into(),
            //                 ))
            //             }
            //         }
            //
            //         ((NodeEnum::Exponent(e1), o1), (NodeEnum::Exponent(e2), o2))
            //         | ((o1, NodeEnum::Exponent(e1)), (NodeEnum::Exponent(e2), o2))
            //         | ((NodeEnum::Exponent(e1), o1), (o2, NodeEnum::Exponent(e2)))
            //         | ((o1, NodeEnum::Exponent(e1)), (o2, NodeEnum::Exponent(e2))) => {
            //             if e1 == e2 {
            //                 Multiply::new_rc(
            //                     Subtract::new(Rc::new(o1.clone()), Rc::new(o2.clone()))
            //                         .evaluate(env)?,
            //                     Rc::new(e1.clone().into()),
            //                 )
            //                 .evaluate(env)
            //             } else {
            //                 Ok(Rc::new(
            //                     Subtract::new(evaluated_left, evaluated_right).into(),
            //                 ))
            //             }
            //         }
            //
            //         _ => Ok(Rc::new(
            //             Subtract::new(evaluated_left, evaluated_right).into(),
            //         )),
            //     }
            // }
            (NodeEnum::Multiply(m1), NodeEnum::Multiply(m2)) => {
                if m1.symbols_match(m2) {
                    return Ok(Multiply::new_rc(
                        Subtract::new_rc(m1.get_left(), m2.get_left()).evaluate(env)?,
                        m1.get_right(),
                    ));
                }
                Ok(Subtract::new_rc(evaluated_left, evaluated_right))
            }

            _ => {
                if evaluated_left == evaluated_right {
                    Ok(Rc::new(Constant::new_from_float(0.0, &env).into()))
                } else {
                    Ok(Rc::new(
                        Subtract::new(evaluated_left, evaluated_right).into(),
                    ))
                }
            }
        }
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        let left_string = if self.left.precedence() <= self.precedence() {
            format!("({})", self.left.as_string(env))
        } else {
            self.left.as_string(env)
        };
        let right_string = if self.right.precedence() <= self.precedence() {
            format!("({})", self.right.as_string(env))
        } else {
            self.right.as_string(env)
        };
        format!("{}-{}", left_string, right_string)
    }

    fn precedence(&self) -> Precedence {
        Precedence::Term
    }
}

impl Subtract {
    pub fn new(left: Rc<NodeEnum>, right: Rc<NodeEnum>) -> Self {
        Self { left, right }
    }

    pub fn new_rc(left: Rc<NodeEnum>, right: Rc<NodeEnum>) -> Rc<NodeEnum> {
        Rc::new(Self { left, right }.into())
    }

    pub fn get_left(&self) -> Rc<NodeEnum> {
        self.left.clone()
    }

    pub fn get_right(&self) -> Rc<NodeEnum> {
        self.right.clone()
    }
}
