use std::hash::Hash;

use crate::stats::metric_tags;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct Tag(u32);

impl Tag {
    #[inline]
    pub fn empty() -> Self {
        Tag(0)
    }

    #[inline]
    pub fn is_empty(self) -> bool {
        self.0 == 0
    }

    #[inline]
    pub fn has(self, kind: TagType) -> bool {
        self.0 & kind.bit() != 0
    }

    #[inline]
    pub fn insert(&mut self, kind: TagType) {
        self.0 |= kind.bit();
    }

    #[inline]
    pub fn union(self, other: Tag) -> Tag {
        Tag(self.0 | other.0)
    }

    #[inline]
    pub fn iter(self) -> TagMaskIter {
        TagMaskIter { bits: self.0 }
    }
}

impl From<TagType> for Tag {
    #[inline]
    fn from(kind: TagType) -> Self {
        Tag(kind.bit())
    }
}

impl From<&TagType> for Tag {
    #[inline]
    fn from(kind: &TagType) -> Self {
        Tag(kind.bit())
    }
}

impl From<&[TagType]> for Tag {
    #[inline]
    fn from(kinds: &[TagType]) -> Self {
        let mut tag = Tag::empty();
        for kind in kinds {
            tag.insert(*kind);
        }
        tag
    }
}

impl From<u8> for Tag {
    #[inline]
    fn from(bits: u8) -> Self {
        Tag(bits as u32)
    }
}

impl From<u16> for Tag {
    #[inline]
    fn from(bits: u16) -> Self {
        Tag(bits as u32)
    }
}

impl From<Tag> for u32 {
    #[inline]
    fn from(mask: Tag) -> Self {
        mask.0
    }
}

impl Hash for Tag {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TagType {
    Selector,
    Alterer,
    Mutator,
    Crossover,
    Species,
    Failure,
    Age,
    Front,
    Derived,
    Other,
    Statistic,
    Time,
    Distribution,
    Score,
    Rate,
    Step,
    Lineage,
    Expr,
}

impl TagType {
    pub const COUNT: usize = 18;
    #[inline]
    pub fn from_index(idx: u32) -> Option<Self> {
        use TagType::*;
        Some(match idx {
            0 => Selector,
            1 => Alterer,
            2 => Mutator,
            3 => Crossover,
            4 => Species,
            5 => Failure,
            6 => Age,
            7 => Front,
            8 => Derived,
            9 => Other,
            10 => Statistic,
            11 => Time,
            12 => Distribution,
            13 => Score,
            14 => Rate,
            15 => Step,
            16 => Lineage,
            17 => Expr,
            _ => return None,
        })
    }

    #[inline]
    pub fn bit(self) -> u32 {
        1u32 << (self as u32)
    }

    #[inline]
    pub fn as_str(&self) -> &'static str {
        use TagType::*;
        match self {
            Selector => "Selector",
            Alterer => "Alterer",
            Mutator => "Mutator",
            Crossover => "Crossover",
            Species => "Species",
            Failure => "Failure",
            Age => "Age",
            Front => "Front",
            Derived => "Derived",
            Other => "Other",
            Statistic => "Statistic",
            Time => "Time",
            Distribution => "Distribution",
            Score => "Score",
            Rate => "Rate",
            Step => "Step",
            Lineage => "Lineage",
            Expr => "Expr",
        }
    }
}

impl PartialOrd for TagType {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some((*self as u16).cmp(&(*other as u16)))
    }
}

impl Ord for TagType {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        (*self as u16).cmp(&(*other as u16))
    }
}

impl From<String> for TagType {
    fn from(s: String) -> Self {
        use TagType::*;
        match s.as_str().to_lowercase().as_str() {
            metric_tags::SELECTOR => Selector,
            metric_tags::ALTERER => Alterer,
            metric_tags::MUTATOR => Mutator,
            metric_tags::CROSSOVER => Crossover,
            metric_tags::SPECIES => Species,
            metric_tags::FAILURE => Failure,
            metric_tags::AGE => Age,
            metric_tags::FRONT => Front,
            metric_tags::DERIVED => Derived,
            metric_tags::OTHER => Other,
            metric_tags::STATISTIC => Statistic,
            metric_tags::TIME => Time,
            metric_tags::DISTRIBUTION => Distribution,
            metric_tags::SCORE => Score,
            metric_tags::RATE => Rate,
            metric_tags::STEP => Step,
            metric_tags::LINEAGE => Lineage,
            metric_tags::EXPR => Expr,
            _ => Other,
        }
    }
}

pub struct TagMaskIter {
    bits: u32,
}

impl Iterator for TagMaskIter {
    type Item = TagType;

    fn next(&mut self) -> Option<Self::Item> {
        if self.bits == 0 {
            return None;
        }

        let tz = self.bits.trailing_zeros() as u32;
        self.bits &= self.bits - 1;
        TagType::from_index(tz)
    }
}

impl IntoIterator for Tag {
    type Item = TagType;
    type IntoIter = TagMaskIter;

    fn into_iter(self) -> Self::IntoIter {
        TagMaskIter { bits: self.0 }
    }
}
