use std::rc::Rc;

use rug::{Float, ops::Pow};

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

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

impl Node for Exponent {
    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()) {
            (NodeEnum::Constant(a), NodeEnum::Constant(b)) => Ok(Rc::new(
                Constant::new(Float::with_val_64(
                    env.get_float_precision(),
                    a.get_value().pow(b.get_value()),
                ))
                .into(),
            )),

            _ => Ok(Self::new(evaluated_left, evaluated_right)),
        }
    }

    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::Exponent
    }
}

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

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

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