use super::Attribute;
use crate::InlineAttributes;
use calyx_utils::{CalyxResult, GPosIdx, WithPos};
use linked_hash_map::LinkedHashMap;
use std::convert::TryFrom;
#[derive(Debug, Clone, Default)]
struct HeapAttrInfo {
    attrs: LinkedHashMap<Attribute, u64>,
    span: GPosIdx,
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct Attributes {
    inl: InlineAttributes,
    hinfo: Box<HeapAttrInfo>,
}
impl TryFrom<Vec<(Attribute, u64)>> for Attributes {
    type Error = calyx_utils::Error;
    fn try_from(v: Vec<(Attribute, u64)>) -> CalyxResult<Self> {
        let mut attrs = Attributes::default();
        for (k, v) in v {
            if attrs.has(k) {
                return Err(Self::Error::malformed_structure(format!(
                    "Multiple entries for attribute: {}",
                    k
                )));
            }
            attrs.insert(k, v);
        }
        Ok(attrs)
    }
}
impl WithPos for Attributes {
    fn copy_span(&self) -> GPosIdx {
        self.hinfo.span
    }
}
pub trait GetAttributes {
    fn get_attributes(&self) -> &Attributes;
    fn get_mut_attributes(&mut self) -> &mut Attributes;
}
impl Attributes {
    pub fn insert<A>(&mut self, key: A, val: u64)
    where
        A: Into<Attribute>,
    {
        match key.into() {
            Attribute::Bool(b) => {
                assert!(
                    val == 1,
                    "{} is a boolean attribute and can only have a value of 1",
                    b.as_ref(),
                );
                self.inl.insert(b);
            }
            attr => {
                self.hinfo.attrs.insert(attr, val);
            }
        }
    }
    pub fn get<A>(&self, key: A) -> Option<u64>
    where
        A: Into<Attribute>,
    {
        match key.into() {
            Attribute::Bool(b) => {
                if self.inl.has(b) {
                    Some(1)
                } else {
                    None
                }
            }
            attr => self.hinfo.attrs.get(&attr).cloned(),
        }
    }
    pub fn has<A>(&self, key: A) -> bool
    where
        A: Into<Attribute>,
    {
        match key.into() {
            Attribute::Bool(b) => self.inl.has(b),
            attr => self.hinfo.attrs.contains_key(&attr),
        }
    }
    pub fn is_empty(&self) -> bool {
        self.inl.is_empty() && self.hinfo.attrs.is_empty()
    }
    pub fn remove<A>(&mut self, key: A)
    where
        A: Into<Attribute>,
    {
        match key.into() {
            Attribute::Bool(b) => {
                self.inl.remove(b);
            }
            attr => {
                self.hinfo.attrs.remove(&attr);
            }
        }
    }
    pub fn add_span(mut self, span: GPosIdx) -> Self {
        self.hinfo.span = span;
        self
    }
    pub fn to_string_with<F>(&self, sep: &'static str, fmt: F) -> String
    where
        F: Fn(String, u64) -> String,
    {
        if self.is_empty() {
            return String::default();
        }
        self.hinfo
            .attrs
            .iter()
            .map(|(k, v)| fmt(k.to_string(), *v))
            .chain(self.inl.iter().map(|k| fmt(k.as_ref().to_string(), 1)))
            .collect::<Vec<_>>()
            .join(sep)
    }
}
#[cfg(feature = "serialize")]
impl serde::Serialize for HeapAttrInfo {
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        ser.collect_map(self.to_owned().attrs.iter())
    }
}