#![no_std]
//! Runtime reflection of Rust values.
//!
//! `serde_derive` is infamous for the sheer amount of code its liberal use can introduce
//! to a workspace. `refloctopus` tries to combat that: avoid deriving `Serialize` and
//! `Deserialize` for most of your types, instead deriveing `Reflect`. The reflection
//! metadata for each type is reasonably small. Using the reflection metadata, a single
//! `Serialize`/`Deserialize` implementation can marshall any Rust type.
use core::{any::TypeId, cell::Cell};

extern crate alloc;
use alloc::{borrow::Cow, collections::BTreeMap};

/// A (often owned) ReflectedType which was not generated
/// at compile-time.
pub type DynamicType<'r> = Cow<'r, ReflectedType<'r>>;
/// A (usually borrowed) ReflectedType
pub type StaticType = DynamicType<'static>;

#[cfg(feature = "derive")]
extern crate refloctopus_derive;
#[doc(hidden)]
pub use memoffset;
#[cfg(feature = "derive")]
#[doc(hidden)]
pub use refloctopus_derive::*;

mod db;
mod metadata;
pub use db::*;
pub use metadata::*;

#[cfg(feature = "use_serde")]
mod de;
#[cfg(feature = "use_serde")]
mod ser;
#[cfg(feature = "use_serde")]
pub(crate) use de::*;
#[cfg(feature = "use_serde")]
pub(crate) use ser::*;

#[cfg(feature = "use_serde")]
type DeserializeTrampoline =
    fn(&mut dyn erased_serde::Deserializer, &ShapedOutputLocation) -> erased_serde::Result<()>;

/// Trait for types which can ponder at runtime and produce a description of themselves.
///
/// There is a blanket impl for types which implement `StaticReflect`.
pub unsafe trait Reflect {
    /// `TypeId::of::<R::Key>()` is the database index for this type.
    type Key: 'static;
    fn rust_type() -> StaticType;
    /// Call this method to register all of the leaf Serialize/Deserialize and Reflect implementations
    /// that this type depends on existing.
    fn register(db: &mut Db<'_>);
}

/// A type with a `StaticType` available.
///
/// There is a `#[derive(Reflect)]` available that you probably want instead.
pub trait StaticReflect: Reflect {
    const RUST_TYPE: &'static StaticType;
    const FIELDS: &'static FieldsStorage<'static>;
}

/// A destination in memory with a known eventual type.
///
/// These are the "places" that `Db::deserialize_in_place` can deserialize into.
/// Nothing about the type is assumed to be initialized until deserialization
/// is complete. ⚠️ If deserialization fails, any intermediate owned values will
/// be **leaked**. ⚠️
///
/// **SAFETY**: the type must always match the actual content of the memory!
/// The easiest way to ensure this is to only use the `From` impls that work on
/// references to any type that implements `Reflect` to construct locations.
pub struct ShapedOutputLocation<'data, 'visitor, 'fields> {
    ptr: *mut u8,
    shape: &'visitor DataShape<'visitor>,
    fields: &'visitor FieldsCursor<'fields>,
    _data: core::marker::PhantomData<&'data mut ()>,
}

impl<'data, 'visitor, 'fields> ShapedOutputLocation<'data, 'visitor, 'fields> {
    /// Prepare to deserialize a value of `typ` into `ptr`.
    ///
    /// # Safety
    /// If `ptr` doesn't point to a large-enough region,
    /// then eventually deserialization is going to write a bunch of data
    /// into a suspect location.
    pub unsafe fn new(
        shape: &'visitor DataShape<'visitor>,
        fields: &'visitor FieldsCursor<'fields>,
        ptr: *mut u8,
    ) -> Self {
        let _data = Default::default();
        ShapedOutputLocation {
            shape,
            ptr,
            fields,
            _data,
        }
    }

    /// SAFETY: the location must be an enum
    unsafe fn write_discriminant(&self, disc: u16) {
        self.ptr.cast::<u16>().write(disc)
    }
}

/// A location in memory with a known reflected type.
///
/// These can be used with `Db::serialize_any`.
///
/// **SAFETY**: the type must always match the actual content of the memory!
/// The easiest way to ensure this is to only use the `From` impls that work on
/// references to any type that implements `Reflect` to construct locations.
pub struct ShapedLocation<'db, 'data, 'fields> {
    shape: &'db DataShape<'db>,
    ptr: *const u8,
    // all of the fields described by this data shape are relative to this handle
    fields: FieldsStorage<'fields>,
    _data: core::marker::PhantomData<&'data ()>,
}

impl<'db, 'data, 'fields> ShapedLocation<'db, 'data, 'fields> {
    /// SAFETY: the location must be an enum.
    unsafe fn read_discriminant(&self) -> u16 {
        debug_assert!(matches!(self.shape, DataShape::Enum { .. }));
        self.ptr.cast::<u16>().read()
    }

    pub(crate) unsafe fn reshape(&'fields self, shape: &'db DataShape<'db>) -> Self {
        ShapedLocation {
            shape,
            ptr: self.ptr,
            fields: self.fields.borrow(),
            _data: Default::default(),
        }
    }

    /// Prepare to deserialize a value of `typ` into `ptr`.
    ///
    /// # Safety
    ///
    /// If `typ` doesn't actually describe the region of memory, laundry will be eaten.
    pub unsafe fn new(
        ty: &'db DynamicType<'db>,
        fields: FieldsStorage<'fields>,
        ptr: *const u8,
    ) -> ShapedLocation<'db, 'data, 'fields> {
        let _data = Default::default();
        ShapedLocation {
            shape: &ty.shape,
            fields,
            ptr,
            _data,
        }
    }

    /// Prepare to deserialize a value of `typ` into `ptr`.
    ///
    /// # Safety
    ///
    /// If `typ` doesn't actually describe the region of memory, laundry will be eaten.
    pub unsafe fn from_shape<'z>(
        shape: &'db DataShape<'db>,
        fields: FieldsStorage<'z>,
        ptr: *const u8,
    ) -> ShapedLocation<'db, 'data, 'z> {
        let _data = Default::default();
        ShapedLocation {
            shape,
            fields,
            ptr,
            _data,
        }
    }
}

//#region Conversions

impl<'db, 'data, T: StaticReflect> From<&'data mut T> for ShapedLocation<'db, 'data, 'static> {
    fn from(v: &'data mut T) -> Self {
        Self {
            shape: &T::RUST_TYPE.shape,
            ptr: v as *mut _ as *const _,
            _data: Default::default(),
            fields: FieldsStorage::from_inner(Cow::Borrowed(&T::FIELDS.area)),
        }
    }
}

impl<'a, T: StaticReflect> From<&'a T> for ShapedLocation<'static, 'a, 'a> {
    fn from(v: &'a T) -> Self {
        Self {
            shape: &T::RUST_TYPE.shape,
            ptr: v as *const _ as *const _,
            _data: Default::default(),
            fields: FieldsStorage::from_inner(Cow::Borrowed(&T::FIELDS.area)),
        }
    }
}

/*
impl<'db, 'data, 'visitor> From<ShapedOutputLocation<'db, 'data, 'visitor>>
    for ShapedLocation<'db, 'data, 'visitor>
{
    fn from(v: ShapedOutputLocation<'db, 'data, 'visitor>) -> Self {
        Self {
            shape: v.shape,
            ptr: v.ptr as *const _,
            _data: Default::default(),
            fields: v.fields,
        }
    }
}
*/

//#endregion

// the type in std is unstable. the layout isn't.
// https://doc.rust-lang.org/std/raw/struct.TraitObject.html
#[repr(C)]
pub(crate) struct TraitObject {
    pub(crate) data: *mut (),
    pub(crate) vtable: *mut (),
}
