use std::collections::HashMap;

use crate::{
    parser::{Command, CommandType},
    scope::Scope,
};

#[derive(Clone, Debug)]
pub struct Function {
    pub args: Vec<String>,
    pub command: Command,
}

pub type Call = fn(&mut Context, &mut Scope) -> Result<(), String>;

#[derive(Clone)]
pub struct Builtin {
    pub call: Call,
    pub args: Vec<String>,
    pub description: String,
}

#[derive(Clone)]
pub enum VariableValue {
    List(Vec<String>),
    Callable(Command),
}

pub struct Context {
    pub(crate) builtins: HashMap<String, Builtin>,
    pub(crate) last_command: Option<Command>,
    pub(crate) last_call: Option<String>,
    pub(crate) exit: bool,
    variables: HashMap<*const (), HashMap<String, VariableValue>>,
}

impl Context {
    pub fn new() -> Self {
        Context {
            builtins: HashMap::new(),
            last_command: None,
            last_call: None,
            variables: HashMap::new(),
            exit: false,
        }
    }

    pub(crate) fn register_builtin(&mut self, name: &'static str, command: Builtin) {
        self.builtins.insert(name.into(), command);
    }

    pub fn builtin(&mut self, name: &'static str, call: Call) {
        self.register_builtin(
            name,
            Builtin {
                call,
                args: Vec::new(),
                description: "".into(),
            },
        )
    }

    pub fn info(
        &mut self,
        name: &'static str,
        args: Vec<&'static str>,
        desc: &'static str,
    ) -> Result<(), ()> {
        if let Some(builtin) = self.builtins.get_mut(name.into()) {
            builtin.args = args.into_iter().map(|e| e.to_string()).collect();
            builtin.description = desc.to_string();
            Ok(())
        } else {
            Err(())
        }
    }

    pub(crate) fn get_builtin(&self, name: &str) -> Option<Call> {
        self.builtins.get(name).map(|c| c.call)
    }

    pub(crate) fn evaluate(&mut self, scope: &mut Scope) -> Result<Vec<String>, String> {
        while let Some(command) = scope.commands.pop_front() {
            self.last_command = Some(command.clone());
            match command.kind {
                CommandType::String(s) => scope.list.push(s),
                CommandType::Number(n) => scope.list.push(format!("{}", n)),
                CommandType::Chain(commands) => {
                    scope
                        .list
                        .append(&mut self.evaluate(&mut scope.child(commands.to_owned()))?);
                }
                CommandType::FunctionDefinition(name, args, command) => {
                    scope.functions.insert(
                        name.clone(),
                        Function {
                            args: args.clone(),
                            command: *command.clone(),
                        },
                    );
                }
                CommandType::Assign(name) => {
                    let val = scope.list.clone();
                    scope.list.clear();
                    let mut lookup_scope: Option<&Scope> = Some(scope);
                    while let Some(child) = lookup_scope {
                        let scope = child as *const Scope as *const ();
                        if self
                            .variables
                            .get(&scope)
                            .map(|e| e.contains_key(&name))
                            .unwrap_or_default()
                        {
                            break;
                        }
                        lookup_scope = child.parent
                    }
                    self.set_variable(
                        lookup_scope.unwrap_or(scope),
                        name,
                        VariableValue::List(val),
                    );
                }
                CommandType::Identifier(i) => {
                    self.handle_identifier(i, scope)?;
                }
                CommandType::Comment(_) => {}
            };
            if self.exit {
                break;
            }
        }
        Ok(scope.list.clone())
    }

    fn call_function(
        &mut self,
        function: &Function,
        name: &String,
        scope: &mut Scope,
    ) -> Result<(), String> {
        let mut args = HashMap::new();
        for (i, arg) in function.args.iter().enumerate() {
            let Some(cmd) = scope.get_arg() else {
                return Err(format!(
                    "Expected parameter {}, {} for function {}",
                    i + 1,
                    arg,
                    name
                ))
            };
            if arg.ends_with('!') {
                args.insert(arg.clone(), VariableValue::Callable(cmd));
            } else {
                let mut arg_scope = scope.child(vec![cmd]);
                let evaluated = self.evaluate(&mut arg_scope)?;
                args.insert(arg.clone(), VariableValue::List(evaluated));
            }
        }
        let mut fun_scope = scope.child(vec![function.command.clone()]);
        for (k, v) in args.into_iter() {
            self.set_variable(&fun_scope, k, v);
        }
        fun_scope.list = scope.list.clone();
        scope.list = self.evaluate(&mut fun_scope)?;
        Ok(())
    }

    fn set_variable(&mut self, scope: &Scope, name: String, value: VariableValue) {
        let scope = scope as *const Scope as *const ();
        match self.variables.get_mut(&scope) {
            Some(map) => {
                map.insert(name, value);
            }
            None => {
                let mut map = HashMap::new();
                map.insert(name, value);
                self.variables.insert(scope, map);
            }
        };
    }

    fn collect_args(&self, scope: &Scope, vars: &mut HashMap<String, VariableValue>) {
        if let Some(parent) = scope.parent {
            self.collect_args(parent, vars)
        };
        let scope = scope as *const Scope as *const ();
        if let Some(map) = self.variables.get(&scope) {
            vars.extend(map.clone().into_iter())
        };
    }

    pub(crate) fn get_variables(&self, scope: &Scope) -> HashMap<String, VariableValue> {
        let mut vars = HashMap::new();
        self.collect_args(scope, &mut vars);
        vars
    }

    fn handle_identifier(&mut self, i: String, scope: &mut Scope) -> Result<(), String> {
        let mut lookup_scope: Option<&Scope> = Some(scope);
        let vars = self.get_variables(scope);
        if let Some(var) = vars.get(&i) {
            match var {
                VariableValue::List(list) => scope.list.append(&mut list.clone()),
                VariableValue::Callable(body) => scope
                    .list
                    .append(&mut self.evaluate(&mut scope.child(vec![body.clone()]))?),
            };
            return Ok(());
        }
        while let Some(child) = lookup_scope {
            if let Some(function) = child.get_func(&i) {
                self.call_function(&function.to_owned(), &i, scope)?;
                return Ok(());
            }
            lookup_scope = child.parent
        }
        if let Some(builtin) = self.get_builtin(&i) {
            self.last_call = Some(i.clone());
            builtin(self, scope)?;
            return Ok(());
        }
        Err(format!("No variable or function named \"{}\" declared", i))
    }
}