use core::fmt::Debug;
use core::hash::Hash;

use crate::{free_pointer::FreePointer, generation::Generation};

pub(crate) enum Entry<V> {
    Occupied(OccupiedEntry<V>),
    Empty(EmptyEntry),
}

impl<V> Entry<V> {
    /// Consume the entry, and if it's occupied, return the value.
    pub(crate) fn into_value(self) -> Option<V> {
        match self {
            Entry::Occupied(occupied) => Some(occupied.value),
            Entry::Empty(_) => None,
        }
    }

    pub(crate) fn get_value_mut(&mut self, generation: Generation) -> Option<&mut V> {
        match self {
            Entry::Occupied(occupied) if occupied.generation == generation => {
                Some(&mut occupied.value)
            }
            _ => None,
        }
    }

    /// If the entry is empty, a reference to it.
    pub(crate) fn as_empty(&self) -> Option<&EmptyEntry> {
        match self {
            Entry::Empty(empty) => Some(empty),
            Entry::Occupied(_) => None,
        }
    }

    /// If the entry is empty, return a mutable reference to it.
    pub(crate) fn as_empty_mut(&mut self) -> Option<&mut EmptyEntry> {
        match self {
            Entry::Empty(empty) => Some(empty),
            Entry::Occupied(_) => None,
        }
    }
}

pub(crate) struct OccupiedEntry<V> {
    pub(crate) generation: Generation,
    pub(crate) value: V,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct EmptyEntry {
    pub(crate) generation: Generation,
    pub(crate) next_free: Option<FreePointer>,
}

impl<V: Clone> Clone for OccupiedEntry<V> {
    fn clone(&self) -> Self {
        Self {
            generation: self.generation,
            value: self.value.clone(),
        }
    }
}

impl<V: Copy> Copy for OccupiedEntry<V> {}

impl<V: PartialEq> PartialEq for OccupiedEntry<V> {
    fn eq(&self, other: &Self) -> bool {
        self.generation == other.generation && self.value.eq(&other.value)
    }
}

impl<V: Eq> Eq for OccupiedEntry<V> {}

impl<V: Clone> Clone for Entry<V> {
    fn clone(&self) -> Self {
        match self {
            Entry::Occupied(entry) => Self::Occupied(entry.clone()),
            Entry::Empty(entry) => Self::Empty(*entry),
        }
    }
}

impl<V: Hash> Hash for OccupiedEntry<V> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.generation.hash(state);
        self.value.hash(state);
    }
}

impl<V: Copy> Copy for Entry<V> {}

impl<V: Debug> Debug for Entry<V> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if let Entry::Occupied(entry) = self {
            entry.value.fmt(f)
        } else {
            write!(f, "Empty")
        }
    }
}

impl<V: PartialEq> PartialEq for Entry<V> {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Entry::Occupied(e0), Entry::Occupied(e1)) => e0.eq(e1),
            (Entry::Empty(e0), Entry::Empty(e1)) => e0.eq(e1),
            _ => false,
        }
    }
}

impl<V: Eq> Eq for Entry<V> {}

impl<V: Hash> Hash for Entry<V> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        match self {
            Entry::Occupied(entry) => entry.hash(state),
            Entry::Empty(entry) => entry.hash(state),
        }
    }
}
