use std::collections::hash_map::DefaultHasher;
use std::convert::TryFrom;
use std::hash::{Hash, Hasher};
use crate::interpreter::Interpreter;
use crate::primitives::PrimitiveFn;
use crate::universe::Universe;
use crate::value::Value;
use crate::{expect_args, reverse};
pub static INSTANCE_PRIMITIVES: &[(&str, PrimitiveFn, bool)] = &[
    ("class", self::class, true),
    ("objectSize", self::object_size, true),
    ("hashcode", self::hashcode, true),
    ("perform:", self::perform, true),
    ("perform:withArguments:", self::perform_with_arguments, true),
    ("perform:inSuperclass:", self::perform_in_super_class, true),
    (
        "perform:withArguments:inSuperclass:",
        self::perform_with_arguments_in_super_class,
        true,
    ),
    ("instVarAt:", self::inst_var_at, true),
    ("instVarAt:put:", self::inst_var_at_put, true),
    ("==", self::eq, true),
];
pub static CLASS_PRIMITIVES: &[(&str, PrimitiveFn, bool)] = &[];
fn class(interpreter: &mut Interpreter, universe: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#class";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
    ]);
    interpreter.stack.push(Value::Class(object.class(universe)));
}
fn object_size(interpreter: &mut Interpreter, _: &mut Universe) {
    const _: &'static str = "Object>>#objectSize";
    interpreter
        .stack
        .push(Value::Integer(std::mem::size_of::<Value>() as i64));
}
fn hashcode(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#hashcode";
    expect_args!(SIGNATURE, interpreter, [
        value => value,
    ]);
    let mut hasher = DefaultHasher::new();
    value.hash(&mut hasher);
    let hash = (hasher.finish() as i64).abs();
    interpreter.stack.push(Value::Integer(hash));
}
fn eq(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#==";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    interpreter.stack.push(Value::Boolean(a == b));
}
fn perform(interpreter: &mut Interpreter, universe: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#perform:";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
        Value::Symbol(sym) => sym,
    ]);
    let object: Value = object;
    let signature = universe.lookup_symbol(sym);
    let method = object.lookup_method(universe, sym);
    match method {
        Some(invokable) => invokable.invoke(interpreter, universe, object, vec![]),
        None => {
            let signature = signature.to_string();
            universe
                .does_not_understand(interpreter, object.clone(), sym, vec![object.clone()])
                .unwrap_or_else(|| {
                    panic!(
                        "'{}': method '{}' not found for '{}'",
                        SIGNATURE,
                        signature,
                        object.to_string(universe),
                    )
                    })
        }
    }
}
fn perform_with_arguments(interpreter: &mut Interpreter, universe: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#perform:withArguments:";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
        Value::Symbol(sym) => sym,
        Value::Array(arr) => arr,
    ]);
    let signature = universe.lookup_symbol(sym);
    let method = object.lookup_method(universe, sym);
    match method {
        Some(invokable) => {
            let args = arr.borrow().iter().cloned().collect();
            invokable.invoke(interpreter, universe, object, args)
        }
        None => {
            let signature = signature.to_string();
            let args = std::iter::once(object.clone())
                .chain(arr.borrow().iter().cloned())
                .collect();
            universe
                .does_not_understand(interpreter, object.clone(), sym, args)
                .unwrap_or_else(|| {
                    panic!(
                        "'{}': method '{}' not found for '{}'",
                        SIGNATURE,
                        signature,
                        object.to_string(universe)
                    )
                    })
        }
    }
}
fn perform_in_super_class(interpreter: &mut Interpreter, universe: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#perform:inSuperclass:";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
        Value::Symbol(sym) => sym,
        Value::Class(class) => class,
    ]);
    let signature = universe.lookup_symbol(sym);
    let method = class.borrow().lookup_method(sym);
    match method {
        Some(invokable) => invokable.invoke(interpreter, universe, object, vec![]),
        None => {
            let signature = signature.to_string();
            let args = vec![object.clone()];
            universe
                .does_not_understand(interpreter, Value::Class(class), sym, args)
                .unwrap_or_else(|| {
                    panic!(
                        "'{}': method '{}' not found for '{}'",
                        SIGNATURE,
                        signature,
                        object.to_string(universe)
                    )
                    })
        }
    }
}
fn perform_with_arguments_in_super_class(interpreter: &mut Interpreter, universe: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#perform:withArguments:inSuperclass:";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
        Value::Symbol(sym) => sym,
        Value::Array(arr) => arr,
        Value::Class(class) => class,
    ]);
    let signature = universe.lookup_symbol(sym);
    let method = class.borrow().lookup_method(sym);
    match method {
        Some(invokable) => {
            let args = arr.borrow().iter().cloned().collect();
            invokable.invoke(interpreter, universe, object, args)
        }
        None => {
            let args = std::iter::once(object.clone())
                .chain(arr.replace(Vec::default()).into_iter())
                .collect();
            let signature = signature.to_string();
            universe
                .does_not_understand(interpreter, Value::Class(class), sym, args)
                .unwrap_or_else(|| {
                    panic!(
                        "'{}': method '{}' not found for '{}'",
                        SIGNATURE,
                        signature,
                        object.to_string(universe)
                    )
                    })
        }
    }
}
fn inst_var_at(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#instVarAt:";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
        Value::Integer(index) => index,
    ]);
    let index = match usize::try_from(index - 1) {
        Ok(index) => index,
        Err(err) => panic!("'{}': {}", SIGNATURE, err),
    };
    let local = object.lookup_local(index).unwrap_or(Value::Nil);
    interpreter.stack.push(local);
}
fn inst_var_at_put(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &'static str = "Object>>#instVarAt:put:";
    expect_args!(SIGNATURE, interpreter, [
        object => object,
        Value::Integer(index) => index,
        value => value,
    ]);
    let index = match usize::try_from(index - 1) {
        Ok(index) => index,
        Err(err) => panic!("'{}': {}", SIGNATURE, err),
    };
    let local = object
        .assign_local(index, value.clone())
        .map(|_| value)
        .unwrap_or(Value::Nil);
    interpreter.stack.push(local);
}
pub fn get_instance_primitive(signature: &str) -> Option<PrimitiveFn> {
    INSTANCE_PRIMITIVES
        .iter()
        .find(|it| it.0 == signature)
        .map(|it| it.1)
}
pub fn get_class_primitive(signature: &str) -> Option<PrimitiveFn> {
    CLASS_PRIMITIVES
        .iter()
        .find(|it| it.0 == signature)
        .map(|it| it.1)
}