use std::{
    any::Any,
    fmt,
    hash::{Hash, Hasher},
    rc::Rc,
};

use crate::{
    error::MachineError,
    vm::{
        env::Environment,
        machine::Machine,
        value::{IdentifierValue, StringValue, Value},
    },
};

pub trait NativeObject: Any + fmt::Debug + fmt::Display {
    fn as_any(&self) -> &dyn Any;
}

#[derive(Clone, Debug)]
pub struct NativeValue(Rc<dyn NativeObject>);

pub type NativeFunctionImpl =
    Rc<dyn Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static>;

#[derive(Clone)]
pub struct NativeFunction {
    name: IdentifierValue,
    inner: NativeFunctionImpl,
    docstring: StringValue,
}

impl NativeFunction {
    pub fn new<S, D, F>(name: S, docstring: D, function: F) -> Self
    where
        S: Into<IdentifierValue>,
        D: Into<StringValue>,
        F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
    {
        Self {
            name: name.into(),
            docstring: docstring.into(),
            inner: Rc::new(function),
        }
    }

    pub fn name(&self) -> &IdentifierValue {
        &self.name
    }

    pub fn docstring(&self) -> &StringValue {
        &self.docstring
    }

    pub fn invoke(
        &self,
        machine: &mut Machine,
        environment: &Rc<Environment>,
        arguments: &[Value],
    ) -> Result<Value, MachineError> {
        (self.inner)(machine, environment, arguments)
    }
}

impl Hash for NativeFunction {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
        (&raw const *self.inner.as_ref()).addr().hash(state);
    }
}

impl PartialEq for NativeFunction {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && Rc::ptr_eq(&self.inner, &other.inner)
    }
}

impl Eq for NativeFunction {}

impl fmt::Debug for NativeFunction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("NativeFunction")
            .field("name", &self.name)
            .field_with("inner", |f| write!(f, "{:p}", self.inner))
            .finish()
    }
}

impl fmt::Display for NativeFunction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "<native {}@{:#x}>",
            self.name,
            (&raw const *self.inner.as_ref()).addr()
        )
    }
}

impl NativeValue {
    pub fn cast_rc<T: NativeObject>(&self) -> Option<Rc<T>> {
        Rc::downcast(self.0.clone()).ok()
    }

    pub fn cast_ref<T: NativeObject>(&self) -> Option<&T> {
        self.0.as_any().downcast_ref()
    }
}

impl From<Rc<dyn NativeObject + 'static>> for NativeValue {
    fn from(value: Rc<dyn NativeObject>) -> Self {
        Self(value)
    }
}

impl PartialEq for NativeValue {
    fn eq(&self, other: &Self) -> bool {
        Rc::ptr_eq(&self.0, &other.0)
    }
}

impl fmt::Display for NativeValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<native {}>", self.0)
    }
}
