use calyx_utils::{CalyxResult, Error, Id};
use std::str::FromStr;
use strum::EnumCount;
use strum_macros::{AsRefStr, EnumCount, EnumString, FromRepr};
pub const DEPRECATED_ATTRIBUTES: &[&str] = &[];
#[derive(
    EnumCount,
    FromRepr,
    AsRefStr,
    EnumString,
    Clone,
    Copy,
    Hash,
    PartialEq,
    Eq,
    Debug,
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[repr(u8)]
pub enum BoolAttr {
    #[strum(serialize = "toplevel")]
    TopLevel,
    #[strum(serialize = "external")]
    External,
    #[strum(serialize = "nointerface")]
    NoInterface,
    #[strum(serialize = "reset")]
    Reset,
    #[strum(serialize = "clk")]
    Clk,
    #[strum(serialize = "stable")]
    Stable,
    #[strum(serialize = "data")]
    Data,
    #[strum(serialize = "control")]
    Control,
    #[strum(serialize = "share")]
    Share,
    #[strum(serialize = "state_share")]
    StateShare,
    #[strum(serialize = "generated")]
    Generated,
    #[strum(serialize = "new_fsm")]
    NewFSM,
    #[strum(serialize = "inline")]
    Inline,
}
impl From<BoolAttr> for Attribute {
    fn from(attr: BoolAttr) -> Self {
        Attribute::Bool(attr)
    }
}
impl std::fmt::Display for BoolAttr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}
#[derive(AsRefStr, EnumString, Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub enum NumAttr {
    #[strum(serialize = "go")]
    Go,
    #[strum(serialize = "done")]
    Done,
    #[strum(serialize = "read_together")]
    ReadTogether,
    #[strum(serialize = "write_together")]
    WriteTogether,
    #[strum(serialize = "sync")]
    Sync,
    #[strum(serialize = "bound")]
    Bound,
    #[strum(serialize = "static")]
    Static,
    #[strum(serialize = "pos")]
    Pos,
    #[strum(serialize = "promote_static")]
    PromoteStatic,
    #[strum(serialize = "compactable")]
    Compactable,
}
impl From<NumAttr> for Attribute {
    fn from(attr: NumAttr) -> Self {
        Attribute::Num(attr)
    }
}
impl std::fmt::Display for NumAttr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}
#[derive(AsRefStr, Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[allow(non_camel_case_types)]
pub enum InternalAttr {
    DEAD,
    NODE_ID,
    BEGIN_ID,
    END_ID,
    ST_ID,
    LOOP,
    START,
    END,
}
impl From<InternalAttr> for Attribute {
    fn from(attr: InternalAttr) -> Self {
        Attribute::Internal(attr)
    }
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub enum Attribute {
    Bool(BoolAttr),
    Num(NumAttr),
    Internal(InternalAttr),
    Unknown(Id),
}
impl std::fmt::Display for Attribute {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Attribute::Bool(b) => write!(f, "{}", b.as_ref()),
            Attribute::Num(n) => write!(f, "{}", n.as_ref()),
            Attribute::Internal(i) => write!(f, "{}", i.as_ref()),
            Attribute::Unknown(s) => write!(f, "{}", s),
        }
    }
}
impl FromStr for Attribute {
    type Err = Error;
    fn from_str(s: &str) -> CalyxResult<Self> {
        if let Ok(b) = BoolAttr::from_str(s) {
            Ok(Attribute::Bool(b))
        } else if let Ok(n) = NumAttr::from_str(s) {
            Ok(Attribute::Num(n))
        } else {
            if s.to_uppercase() == s {
                return Err(Error::misc(format!("Invalid attribute: {}. All caps attributes are reserved for internal use.", s)));
            }
            Ok(Attribute::Unknown(s.into()))
        }
    }
}
#[derive(Default, Debug, Clone)]
pub(super) struct InlineAttributes {
    attrs: u16,
}
impl InlineAttributes {
    pub const fn is_empty(&self) -> bool {
        self.attrs == 0
    }
    pub fn insert(&mut self, attr: BoolAttr) {
        self.attrs |= 1 << attr as u8;
    }
    pub fn has(&self, attr: BoolAttr) -> bool {
        self.attrs & (1 << (attr as u8)) != 0
    }
    pub fn remove(&mut self, attr: BoolAttr) {
        self.attrs &= !(1 << attr as u8);
    }
    pub(super) fn iter(&self) -> impl Iterator<Item = BoolAttr> + '_ {
        (0..(BoolAttr::COUNT as u8)).filter_map(|idx| {
            if self.attrs & (1 << idx) != 0 {
                Some(BoolAttr::from_repr(idx).unwrap())
            } else {
                None
            }
        })
    }
}
#[cfg(feature = "serialize")]
impl serde::Serialize for InlineAttributes {
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.to_owned().attrs.serialize(ser)
    }
}