use std::rc::Rc;

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

#[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>, 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), _) if zero.get_value() == 0. => Ok(evaluated_right),
            (_, NodeEnum::Constant(zero)) if zero.get_value() == 0. => Ok(evaluated_left),

            // 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(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()))
            }
            _ => {
                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 get_left(&self) -> Rc<NodeEnum> {
        self.left.clone()
    }

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