use std::rc::Rc;

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

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Bool {
    True,
    False,
}

impl Node for Bool {
    fn evaluate(&self, _: &mut Environment) -> Result<Rc<NodeEnum>, String> {
        Ok(Rc::new(self.clone().into()))
    }

    fn as_string(&self, _: Option<&Environment>) -> String {
        match self {
            Bool::True => "true",
            Bool::False => "false",
        }
        .into()
    }

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

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum ElseBranchEnum {
    ElseIf(Rc<IfElse>),
    Block(Vec<Rc<NodeEnum>>),
}

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct IfElse {
    condition: Rc<NodeEnum>,
    true_branch: Vec<Rc<NodeEnum>>,
    else_branch: ElseBranchEnum,
}

impl Node for IfElse {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, String> {
        let condition_evaluated = self.condition.evaluate(env)?;

        let condition = if let NodeEnum::Bool(bool) = condition_evaluated.as_ref() {
            bool
        } else {
            return Err(format!(
                "Cannot evaluate {} to a bool",
                condition_evaluated.as_string(Some(env))
            ));
        };

        fn evaluate_block(
            block: &Vec<Rc<NodeEnum>>,
            env: &mut Environment,
        ) -> Result<Rc<NodeEnum>, String> {
            // TODO: Push new scope
            if let Some((last, to_evaluate)) = block.split_last() {
                for expr in to_evaluate {
                    expr.evaluate(env)?;
                }
                last.evaluate(env)
            } else {
                Err("Empty if statemenent true branch")?
            }
            // TODO: Pop scope
        }

        match condition {
            Bool::True => evaluate_block(&self.true_branch, env),
            Bool::False => match &self.else_branch {
                ElseBranchEnum::ElseIf(if_else) => if_else.evaluate(env),
                ElseBranchEnum::Block(node_enums) => evaluate_block(node_enums, env),
            },
        }
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        format!(
            "if {} then\n{}\nelse\n{}",
            self.condition.as_string(env),
            self.true_branch
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + "\n" + &b)
                .unwrap()
                .as_str(),
            match &self.else_branch {
                ElseBranchEnum::ElseIf(if_else) => if_else.as_string(env),
                ElseBranchEnum::Block(vec) =>
                    vec.iter()
                        .map(|x| x.as_string(env))
                        .reduce(|a, b| a + "\n" + &b)
                        .unwrap()
                        + "\nend",
            }
            .as_str()
        )
    }

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