use core::{marker::PhantomData, num::NonZeroU32};

use crate::generation::Generation;

/// A key type for an [`Arena`](crate::Arena) that has a generation attached to
/// it.
///
/// The generic can be used to prevent keys from being used in an `Arena` they
/// don't belong to. For example, a custom key type can be declared like this:
/// ```
/// # use thunderation::Key;
/// pub struct MyKey;
///
/// let my_key = Key::<MyKey>::DANGLING;
/// ```
pub struct Key<T = ()> {
    pub(crate) slot: u32,
    pub(crate) generation: Generation,
    pub(crate) _key_type: PhantomData<T>,
}

impl<T> Key<T> {
    /// Represents a [`Key`] that is unlikely to be in use. This is useful for
    /// programs that want to do two-phase initialization in safe Rust. Avoid
    /// using this value to represent the absence of a `Key`: prefer
    /// `Option<Key>`.
    pub const DANGLING: Self = Self {
        slot: u32::MAX,
        generation: Generation::DANGLING,
        _key_type: PhantomData,
    };

    pub(crate) const fn new(slot: u32, generation: Generation) -> Self {
        Self {
            slot,
            generation,
            _key_type: PhantomData,
        }
    }

    /// Manually create a new `Key` with the given slot and generation.
    pub const fn from_slot_and_generation(slot: u32, generation: NonZeroU32) -> Self {
        Self {
            slot,
            generation: Generation::from_non_zero_u32(generation),
            _key_type: PhantomData,
        }
    }

    /// Convert this `Key` to an equivalent `u64` representation. Mostly
    /// useful for passing to code outside of Rust.
    ///
    /// ## Stability
    /// Bits from `Key` values are guaranteed to be compatible within all
    /// semver-compatible versions of Thunderation.
    #[allow(clippy::arithmetic_side_effects)]
    pub const fn to_bits(self) -> u64 {
        // This is safe because a `u32` bit-shifted by 32 will still fit in a `u64`.
        ((self.generation.to_non_zero_u32().get() as u64) << 32) | (self.slot as u64)
    }

    /// Create a `Key` from bits created with `Key::to_bits`.
    ///
    /// If this function is called with bits that are not valid for a `Key`,
    /// returns `None`. This can happen if the encoded generation value is 0,
    /// for example.
    ///
    /// ## Stability
    /// Bits from `Key` values are guaranteed to be compatible within all
    /// semver-compatible versions of Thunderation.
    #[allow(clippy::arithmetic_side_effects)]
    pub const fn from_bits(bits: u64) -> Option<Self> {
        // By bit-shifting right by 32, we're undoing the left-shift in `to_bits`
        // thus this is okay by the same rationale.
        let generation = (bits >> 32) as u32;
        let generation = match Generation::from_u32(generation) {
            Some(v) => v,
            None => return None,
        };

        let slot = bits as u32;

        Some(Self {
            generation,
            slot,
            _key_type: PhantomData,
        })
    }

    /// Get the generation of this key.
    pub const fn generation(&self) -> NonZeroU32 {
        self.generation.to_non_zero_u32()
    }

    /// Get the slot index of this key. Slots describe a location in an
    /// [`Arena`](crate::Arena) and are reused when entries are removed.
    pub const fn slot(&self) -> u32 {
        self.slot
    }
}

impl<T> Clone for Key<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for Key<T> {}

impl<T> PartialEq for Key<T> {
    fn eq(&self, other: &Self) -> bool {
        self.slot == other.slot && self.generation == other.generation
    }
}

impl<T> Eq for Key<T> {}

impl<T> core::hash::Hash for Key<T> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.slot.hash(state);
        self.generation.hash(state);
    }
}

impl<T> core::fmt::Debug for Key<T> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "Key(s {}, g {})",
            &self.slot,
            &self.generation.to_non_zero_u32().get()
        )
    }
}

#[cfg(test)]
mod test {
    use core::mem::size_of;

    use crate::Key;

    #[test]
    fn size_of_key() {
        #[derive(Copy, Clone)]
        struct CustomKeyType;

        assert_eq!(size_of::<Key<()>>(), 8);
        assert_eq!(size_of::<Option<Key<()>>>(), 8);
        assert_eq!(size_of::<Key<CustomKeyType>>(), 8);
        assert_eq!(size_of::<Option<Key<CustomKeyType>>>(), 8);
    }

    #[test]
    fn key_bits_roundtrip() {
        let key = Key::<()>::from_bits(0x1BAD_CAFE_DEAD_BEEF).unwrap();
        assert_eq!(key.to_bits(), 0x1BAD_CAFE_DEAD_BEEF);
    }

    #[test]
    fn key_bits_none_on_zero_generation() {
        let key = Key::<()>::from_bits(0x0000_0000_DEAD_BEEF);
        assert_eq!(key, None);
    }
}
