use std::rc::Rc;

use crate::environment::Environment;

use super::{Error, Node, NodeEnum, Precedence, empty::Empty};

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Break;

impl Node for Break {
    fn evaluate(&self,_: &mut Environment) -> Result<Rc<NodeEnum>,Error> {
        return Err(Error::Break);
    }

    fn as_string(&self,_:Option<&Environment>) -> String {
        "break".to_owned()
    }

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

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Loop {
    body: Vec<Rc<NodeEnum>>,
}

impl Node for Loop {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut break_loop = false;
        while !break_loop {
            env.push_stack()?;

            for statement in self.body.iter() {
                let evaluated = statement.evaluate(env);

                match evaluated {
                    Err(Error::Break) => {
                        break_loop = true;
                        break;
                    }
                    Err(e) => {
                        env.pop_stack()?;
                        return Err(e);
                    }
                    _ => {}
                }
            }

            env.pop_stack()?;
        }

        Ok(Empty::new(""))
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        format!(
            "loop {} end",
            self.body
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + "; " + &b)
                .unwrap()
                .as_str()
        )
    }

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

impl Loop {
    pub fn new(body: Vec<Rc<NodeEnum>>) -> Rc<NodeEnum> {
        Rc::new(Self { body }.into())
    }
}
