use std::collections::HashMap;
use serde::{Serialize, Deserialize};

use crate::lexer::*; // Identifier, Token, TokenString, etc.
use crate::builtin::{lookup_builtin, load_builtins};

pub type BuiltinFn = fn(&mut InterpreterState) -> bool;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StackValue {
    Identifier(Identifier),

    I64(i64),
    I32(i32),
    I16(i16),
    I8(i8),

    U64(u64),
    U32(u32),
    U16(u16),
    U8(u8),

    F32(f32),
    F64(f64),

    Character(u8),
    Boolean(bool),

    TokenString(TokenString),

    Callable(TokenString),
}

#[derive(Debug, Clone)]
pub struct BuiltinFunction {
    pub name: String,
    pub function: BuiltinFn,
}

impl BuiltinFunction {
    fn call(&self, interpreter: &mut InterpreterState) -> bool {
        (self.function)(interpreter)
    }
}

impl Serialize for BuiltinFunction {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.name)
    }
}

impl<'de> Deserialize<'de> for BuiltinFunction {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let name = String::deserialize(deserializer)?;

        let function = lookup_builtin(&name)
            .ok_or_else(|| serde::de::Error::custom(format!("Unknown builtin: {}", name)))?;

        Ok(BuiltinFunction { name, function })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FunctionItem {
    TokenString(TokenString),
    Builtin(BuiltinFunction),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct InterpreterState {
    pub stack: Vec<StackValue>,
    pub functions: HashMap<String, FunctionItem>,
}

impl InterpreterState {
    pub fn new() -> Self {
        Self {
            stack: Vec::new(),
            functions: HashMap::new(),
        }
    }

    pub fn init(&mut self) -> bool {
        load_builtins(self)
    }

    pub fn push_token(&mut self, token: &Token) -> bool {
        let value = match token {
            Token::Eof => return true,

            Token::Identifier(id) => {
                StackValue::Identifier(id.clone())
            }

            Token::I64(v) => StackValue::I64(*v),
            Token::I32(v) => StackValue::I32(*v),
            Token::I16(v) => StackValue::I16(*v),
            Token::I8(v)  => StackValue::I8(*v),

            Token::U64(v) => StackValue::U64(*v),
            Token::U32(v) => StackValue::U32(*v),
            Token::U16(v) => StackValue::U16(*v),
            Token::U8(v)  => StackValue::U8(*v),

            Token::Float(v)  => StackValue::F32(*v),
            Token::Double(v) => StackValue::F64(*v),

            Token::Character(c) => StackValue::Character(*c),
            Token::Boolean(b)   => StackValue::Boolean(*b),

            Token::TokenString(ts) => StackValue::TokenString(ts.clone()),

            Token::StringLiteral(_) |
            Token::Array(_) |
            Token::TypeTuple(_) => return false,
        };

        self.stack.push(value);
        true
    }

    pub fn execute_func(&mut self, key: &str) -> bool {
        let item = match self.functions.get(key) {
            Some(v) => v.clone(),
            None => return false,
        };

        match item {
            FunctionItem::Builtin(f) => f.call(self),
            FunctionItem::TokenString(ts) => self.execute_token_string(&ts),
        }
    }

    pub fn execute_token_string(&mut self, ts: &TokenString) -> bool {
        for token in &ts.tokens {
            if let Token::Identifier(id) = &token {
                if !id.is_literal {
                    if !self.execute_func(&id.name) {
                        return false;
                    }
                    continue;
                }
            }

            if !self.push_token(&token) {
                return false;
            }
        }
        true
    }

    pub fn execute(&mut self, token: &Token) -> bool {
        match token {
            Token::Identifier(id) if !id.is_literal => {
                self.execute_func(&id.name)
            }
            _ => self.push_token(token),
        }
    }

    pub fn stack_top(&self) -> Option<&StackValue> {
        self.stack.last()
    }
}
