use crate::driver::BackendToken;

#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TokenSlot(u16);

impl TokenSlot {
    #[inline(always)]
    pub fn from_index(index: usize) -> Self {
        Self(u16::try_from(index).expect("dope: token slot index exceeds u16 range"))
    }

    #[inline(always)]
    pub const fn from_raw(raw: u16) -> Self {
        Self(raw)
    }

    #[inline(always)]
    pub const fn as_usize(self) -> usize {
        self.0 as usize
    }

    #[inline(always)]
    pub const fn raw(self) -> u16 {
        self.0
    }
}

#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TokenEpoch(u32);

impl TokenEpoch {
    pub const MAX: u32 = (1 << 24) - 1;

    #[inline(always)]
    pub const fn from_raw(raw: u32) -> Self {
        assert!(
            raw <= Self::MAX,
            "dope: token epoch exceeds 24-bit user_data budget",
        );
        Self(raw)
    }

    #[inline(always)]
    pub const fn raw(self) -> u32 {
        self.0
    }

    #[inline(always)]
    pub fn next(seq: &mut u32) -> Self {
        *seq = seq.wrapping_add(1) & Self::MAX;
        Self::from_raw(*seq)
    }
}

pub trait WirePad: Copy + 'static {
    const ZERO: Self;
}

impl WirePad for () {
    const ZERO: () = ();
}
impl WirePad for u8 {
    const ZERO: u8 = 0;
}

#[repr(C, packed)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Token<P: WirePad> {
    slot: TokenSlot,
    epoch: TokenEpoch,
    _pad: P,
}

impl<P: WirePad> Token<P> {
    #[inline(always)]
    pub const fn new(slot: TokenSlot, epoch: TokenEpoch) -> Self {
        Self {
            slot,
            epoch,
            _pad: P::ZERO,
        }
    }

    #[inline(always)]
    pub fn from_index(index: usize, epoch: TokenEpoch) -> Self {
        Self::new(TokenSlot::from_index(index), epoch)
    }

    #[inline(always)]
    pub fn from_index_raw(index: usize, epoch: u32) -> Self {
        Self::from_index(index, TokenEpoch::from_raw(epoch))
    }

    #[inline(always)]
    pub fn next_for_slot(index: usize, seq: &mut u32) -> Self {
        Self::from_index(index, TokenEpoch::next(seq))
    }

    #[inline(always)]
    pub const fn parts(self) -> (usize, u32) {
        (self.slot.as_usize(), self.epoch.raw())
    }

    #[inline(always)]
    pub fn slot_raw(self) -> u16 {
        self.slot.raw()
    }

    #[inline(always)]
    pub fn epoch_raw(self) -> u32 {
        self.epoch.raw()
    }
}

impl<P: WirePad> BackendToken for Token<P> {
    #[inline(always)]
    fn parts(self) -> (usize, u32) {
        Self::parts(self)
    }

    #[inline(always)]
    fn next_for_slot(index: usize, seq: &mut u32) -> Self {
        Self::next_for_slot(index, seq)
    }

    #[inline(always)]
    fn from_index_raw(index: usize, epoch: u32) -> Self {
        Self::from_index_raw(index, epoch)
    }
}
