use std::rc::Rc;

use crate::{
    environment::Environment,
    node::{closure::Closure, function::FunctionType},
};

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

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

impl Node for Call {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        if env.closures_enabled() {
            return Ok(Rc::new(self.clone().into()));
        }

        // Evaluate callee and error if its not a function
        let evaluated = self.function.evaluate(env)?;
        // let func = if let NodeEnum::Function(func) = evaluated.as_ref() {
        //     func
        // } else {
        //     return Ok(Rc::new(self.clone().into()));
        // };

        env.push_stack()?;

        let mut existing_captures = vec![];

        let func = match evaluated.as_ref() {
            NodeEnum::Function(func) => func,
            NodeEnum::Closure(closure) => {
                for (k, v) in closure.get_captured_variables() {
                    env.insert(*k, v.clone(), false);
                    existing_captures.push((*k,v.clone()));
                }
                closure.get_function()
            }
            _ => {
                return Ok(Rc::new(self.clone().into()));
            }
        };

        let arguments = {
            let mut arguments = vec![];
            for value in self.arguments.iter() {
                arguments.push(value.evaluate(env)?);
            }
            arguments
        };

        // Call function body with arguments
        let ret = match func.get_body() {
            // Pass arguments to native function
            FunctionType::Native(_name, native_function) => native_function(&arguments, env),
            FunctionType::UserFunction(body, fargs) => {
                if fargs.len() != arguments.len() {
                    return Err(Error::ArgumentCount(fargs.len(), arguments.len()));
                }
                // Define variables
                fargs.iter().zip(&arguments).for_each(|(symbol, value)| {
                    env.insert(symbol.get_value(), value.clone(), false);
                });
                let ev = body.evaluate(env);

                if ev.is_err() {
                    env.pop_stack()?;
                    return ev;
                }

                let ev = ev?;

                // Return evaluated return value for function
                Ok(match ev.as_ref() {
                    NodeEnum::Function(hof) => {
                        let mut captures = fargs.iter().map(|a| a.get_value()).zip(arguments).collect();
                        existing_captures.append(&mut captures);
                        Closure::new(hof.clone(), existing_captures)
                    }
                    _ => ev,
                })
            }
        };

        env.pop_stack()?;
        ret
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        let arguments = format!(
            "{}",
            self.arguments
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + ", " + &b)
                .unwrap_or("".to_owned())
        );
        format!("{}({})", self.function.as_string(env), arguments)
    }

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

impl Call {
    pub fn new(function: Rc<NodeEnum>, arguments: Vec<Rc<NodeEnum>>) -> Rc<NodeEnum> {
        Rc::new(
            Self {
                function,
                arguments,
            }
            .into(),
        )
    }
}
