use std::rc::Rc;

use crate::environment::Environment;

use super::{Node, NodeEnum, Precedence, constant::Constant, set::Set};

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

impl Node for Multiply {
    fn evaluate(&self, env: &mut super::Environment) -> Result<Rc<super::NodeEnum>, String> {
        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), _) | (_, NodeEnum::Constant(zero))
                if zero.get_value() == 0. =>
            {
                Ok(Rc::new(Constant::new(0.).into()))
            }

            // Constant + Constant = Constant
            (NodeEnum::Constant(a), NodeEnum::Constant(b)) => {
                Ok(Rc::new(Constant::new(a.get_value() * b.get_value()).into()))
            }

            // Symbol + Constant we just return the same
            (NodeEnum::Symbol(_), NodeEnum::Constant(_)) => Ok(Rc::new(
                Multiply::new(evaluated_right, evaluated_left).into(),
            )),

            // Constant + Symbol we switch them around so the constant is last
            (NodeEnum::Constant(_), NodeEnum::Symbol(_)) => Ok(Rc::new(
                Multiply::new(evaluated_left, evaluated_right).into(),
            )),

            // Multiply a set with a constant
            (NodeEnum::Set(s), NodeEnum::Constant(c))
            | (NodeEnum::Constant(c), NodeEnum::Set(s)) => {
                let old_values = s.get_values();
                let mut values = Vec::with_capacity(old_values.len());
                let c: Rc<NodeEnum> = Rc::new(c.clone().into());

                for value in old_values.iter() {
                    let new_value = Multiply::new(c.clone(), value.clone());
                    values.push(new_value.evaluate(env)?);
                }

                Ok(Set::new(values))
            }

            // Multiply a set with a constant
            (NodeEnum::Set(s), NodeEnum::Symbol(c)) | (NodeEnum::Symbol(c), NodeEnum::Set(s)) => {
                let old_values = s.get_values();
                let mut values = Vec::with_capacity(old_values.len());
                let c: Rc<NodeEnum> = Rc::new(c.clone().into());

                for value in old_values.iter() {
                    let new_value = Multiply::new(c.clone(), value.clone());
                    values.push(new_value.evaluate(env)?);
                }

                Ok(Set::new(values))
            }

            (NodeEnum::Constant(c), NodeEnum::Multiply(m))
            | (NodeEnum::Multiply(m), NodeEnum::Constant(c)) => {
                Self::collapse_nested_multiply(c, m, env)?.evaluate(env)
            }

            // (NodeEnum::Multiply(m1), NodeEnum::Multiply(m2)) => {
            //     Self::move_constants_to_left(evaluated_left.clone(), evaluated_right.clone())
            // }

            // Default to returning with left and right evaluated
            _ => Ok(Rc::new(
                Multiply::new(evaluated_left, evaluated_right).into(),
            )),
        }
    }

    fn as_string(&self, env: Option<&super::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::Factor
    }
}

impl Multiply {
    fn collapse_nested_multiply(
        constant: &Constant,
        multiply: &Multiply,
        env: &mut Environment,
    ) -> Result<Rc<NodeEnum>, String> {
        match (multiply.left.as_ref(), multiply.right.as_ref()) {
            (NodeEnum::Constant(c2), _) => {
                let new_const = Constant::new(constant.get_value() * c2.get_value()).into();
                Ok(Rc::new(
                    Multiply::new(Rc::new(new_const), multiply.right.clone()).into(),
                ))
            }

            (_, NodeEnum::Constant(c2)) => {
                let new_const = Constant::new(constant.get_value() * c2.get_value()).into();
                Ok(Rc::new(
                    Multiply::new(Rc::new(new_const), multiply.right.clone()).into(),
                ))
            }

            (_, NodeEnum::Multiply(inner_mul)) => {
                let expr = Self::collapse_nested_multiply(constant, inner_mul, env)?;
                Ok(Rc::new(Multiply::new(expr, multiply.left.clone()).into()))
            }

            (NodeEnum::Multiply(inner_mul), _) => {
                let expr = Self::collapse_nested_multiply(constant, inner_mul, env)?;
                Ok(Rc::new(Multiply::new(expr, multiply.right.clone()).into()))
            }

            _ => Ok(Rc::new(
                Multiply::new(
                    Rc::new(constant.clone().into()),
                    Rc::new(multiply.clone().into()),
                )
                .into(),
            )),
        }
    }

    fn move_constants_to_left(
        left: Rc<NodeEnum>,
        right: Rc<NodeEnum>,
    ) -> Result<Rc<NodeEnum>, String> {
        match (left.as_ref(), right.as_ref()) {
            // Default cases that returns
            (NodeEnum::Constant(_), NodeEnum::Multiply(_))
            | (NodeEnum::Symbol(_), NodeEnum::Symbol(_)) => Ok(Self::new_rc(left, right)),

            (NodeEnum::Multiply(m), NodeEnum::Constant(c)) => Ok(Self::new_rc(
                right.clone(),
                Self::move_constants_to_left(m.left.clone(), m.right.clone())?,
            )),

            (NodeEnum::Multiply(m1), NodeEnum::Multiply(m2)) => Ok(Self::new_rc(
                Self::move_constants_to_left(m1.left.clone(), m1.right.clone())?,
                Self::move_constants_to_left(m2.left.clone(), m2.right.clone())?,
            )),

            _ => Ok(Self::new_rc(left, right)),
        }
    }
}

impl Multiply {
    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::new(left, right).into())
    }

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

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