use crate::{
    collections::arena::ArenaIndex,
    core::{ReadAs, UntypedVal, WriteAs},
    store::Stored,
    AsContextMut,
    Func,
    StoreContext,
};
use alloc::boxed::Box;
use core::{any::Any, mem, num::NonZeroU32};

/// A nullable reference type.
#[derive(Debug, Default, Copy, Clone)]
pub enum Ref<T> {
    /// The [`Ref`] is a non-`null` value.
    Val(T),
    /// The [`Ref`] is `null`.
    #[default]
    Null,
}

impl<T> Ref<T> {
    /// Returns `true` is `self` is null.
    pub fn is_null(&self) -> bool {
        matches!(self, Self::Null)
    }

    /// Returns `Some` if `self` is a non-`null` value.
    ///
    /// Otherwise returns `None`.
    pub fn val(&self) -> Option<&T> {
        match self {
            Ref::Val(val) => Some(val),
            Ref::Null => None,
        }
    }

    /// Converts from `&Ref<T>` to `Ref<&T>`.
    pub fn as_ref(&self) -> Ref<&T> {
        match self {
            Ref::Val(val) => Ref::Val(val),
            Ref::Null => Ref::Null,
        }
    }
}

impl<T> From<T> for Ref<T> {
    fn from(value: T) -> Self {
        Self::Val(value)
    }
}

/// A raw index to an external entity.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExternRefIdx(NonZeroU32);

impl ArenaIndex for ExternRefIdx {
    fn into_usize(self) -> usize {
        self.0.get().wrapping_sub(1) as usize
    }

    fn from_usize(index: usize) -> Self {
        index
            .try_into()
            .ok()
            .map(|index: u32| index.wrapping_add(1))
            .and_then(NonZeroU32::new)
            .map(Self)
            .unwrap_or_else(|| panic!("out of bounds extern object index {index}"))
    }
}

/// An externally defined object.
#[derive(Debug)]
pub struct ExternRefEntity {
    inner: Box<dyn 'static + Any + Send + Sync>,
}

impl ExternRefEntity {
    /// Creates a new instance of `ExternRef` wrapping the given value.
    pub fn new<T>(object: T) -> Self
    where
        T: 'static + Any + Send + Sync,
    {
        Self {
            inner: Box::new(object),
        }
    }

    /// Returns a shared reference to the external object.
    pub fn data(&self) -> &dyn Any {
        &*self.inner
    }
}

/// Represents an opaque reference to any data within WebAssembly.
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct ExternRef(Stored<ExternRefIdx>);

impl ExternRef {
    /// Creates a new [`ExternRef`] reference from its raw representation.
    pub(crate) fn from_inner(stored: Stored<ExternRefIdx>) -> Self {
        Self(stored)
    }

    /// Returns the raw representation of the [`ExternRef`].
    pub(crate) fn as_inner(&self) -> &Stored<ExternRefIdx> {
        &self.0
    }

    /// Creates a new instance of `ExternRef` wrapping the given value.
    pub fn new<T>(mut ctx: impl AsContextMut, object: T) -> Self
    where
        T: 'static + Any + Send + Sync,
    {
        ctx.as_context_mut()
            .store
            .inner
            .alloc_extern_object(ExternRefEntity::new(object))
    }

    /// Returns a shared reference to the underlying data for this [`ExternRef`].
    ///
    /// # Panics
    ///
    /// Panics if `ctx` does not own this [`ExternRef`].
    pub fn data<'a, T: 'a>(&self, ctx: impl Into<StoreContext<'a, T>>) -> &'a dyn Any {
        ctx.into().store.inner.resolve_externref(self).data()
    }
}

#[test]
fn externref_sizeof() {
    // These assertions are important in order to convert `FuncRef`
    // from and to 64-bit `UntypedValue` instances.
    //
    // The following equation must be true:
    //     size_of(ExternRef) == size_of(ExternObject) == size_of(UntypedValue)
    use core::mem::size_of;
    assert_eq!(size_of::<ExternRef>(), size_of::<u64>());
    assert_eq!(size_of::<ExternRef>(), size_of::<ExternRef>());
}

#[test]
fn externref_null_to_zero() {
    assert_eq!(
        UntypedVal::from(<Ref<ExternRef>>::Null),
        UntypedVal::from(0)
    );
    assert!(<Ref<ExternRef>>::from(UntypedVal::from(0)).is_null());
}

#[test]
fn funcref_sizeof() {
    // These assertions are important in order to convert `FuncRef`
    // from and to 64-bit `UntypedValue` instances.
    //
    // The following equation must be true:
    //     size_of(Func) == size_of(UntypedValue) == size_of(FuncRef)
    use crate::Func;
    use core::mem::size_of;
    assert_eq!(size_of::<Func>(), size_of::<u64>());
    assert_eq!(size_of::<Func>(), size_of::<Ref<Func>>());
}

#[test]
fn funcref_null_to_zero() {
    use crate::Func;
    assert_eq!(UntypedVal::from(<Ref<Func>>::Null), UntypedVal::from(0));
    assert!(<Ref<Func>>::from(UntypedVal::from(0)).is_null());
}

macro_rules! impl_conversions {
    ( $( $reftype:ty ),* $(,)? ) => {
        $(
            impl ReadAs<$reftype> for UntypedVal {
                fn read_as(&self) -> $reftype {
                    let bits = u64::from(*self);
                    unsafe { mem::transmute::<u64, $reftype>(bits) }
                }
            }

            impl ReadAs<Ref<$reftype>> for UntypedVal {
                fn read_as(&self) -> Ref<$reftype> {
                    let bits = u64::from(*self);
                    if bits == 0 {
                        return <Ref<$reftype>>::Null;
                    }
                    <Ref<$reftype>>::Val(<Self as ReadAs<$reftype>>::read_as(self))
                }
            }

            impl WriteAs<$reftype> for UntypedVal {
                fn write_as(&mut self, value: $reftype) {
                    let bits = unsafe { mem::transmute::<$reftype, u64>(value) };
                    self.write_as(bits)
                }
            }

            impl WriteAs<Ref<$reftype>> for UntypedVal {
                fn write_as(&mut self, value: Ref<$reftype>) {
                    match value {
                        Ref::Null => self.write_as(0_u64),
                        Ref::Val(value) => self.write_as(value),
                    }
                }
            }

            impl From<UntypedVal> for Ref<$reftype> {
                fn from(untyped: UntypedVal) -> Self {
                    if u64::from(untyped) == 0 {
                        return <Ref<$reftype>>::Null;
                    }
                    // Safety: This operation is safe since there are no invalid
                    //         bit patterns for [`ExternRef`] instances. Therefore
                    //         this operation cannot produce invalid [`ExternRef`]
                    //         instances even though the input [`UntypedVal`]
                    //         was modified arbitrarily.
                    unsafe { mem::transmute::<u64, Self>(untyped.into()) }
                }
            }

            impl From<$reftype> for UntypedVal {
                fn from(reftype: $reftype) -> Self {
                    // Safety: This operation is safe since there are no invalid
                    //         bit patterns for [`UntypedVal`] instances. Therefore
                    //         this operation cannot produce invalid [`UntypedVal`]
                    //         instances even if it was possible to arbitrarily modify
                    //         the input `$reftype` instance.
                    let bits = unsafe { mem::transmute::<$reftype, u64>(reftype) };
                    UntypedVal::from(bits)
                }
            }

            impl From<Ref<$reftype>> for UntypedVal {
                fn from(reftype: Ref<$reftype>) -> Self {
                    match reftype {
                        Ref::Val(reftype) => UntypedVal::from(reftype),
                        Ref::Null => UntypedVal::from(0_u64),
                    }
                }
            }
        )*
    };
}
impl_conversions! {
    ExternRef,
    Func,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Engine, Store};

    #[test]
    fn it_works() {
        let engine = Engine::default();
        let mut store = <Store<()>>::new(&engine, ());
        let value = 42_i32;
        let obj = ExternRef::new::<i32>(&mut store, value);
        assert_eq!(obj.data(&store).downcast_ref::<i32>(), Some(&value),);
    }
}
