//! General enumeration for chemical tree nodes.

use core::fmt::Display;

use crate::{
    ChargeLike, ChemicalTree, CountLike, MolecularTree,
    prelude::{Element, RepeatNode},
};

#[derive(Debug, PartialEq, Clone, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "mem_size", derive(mem_dbg::MemSize))]
#[cfg_attr(feature = "mem_dbg", derive(mem_dbg::MemDbg))]
/// A tree node representing molecular formulas in InChI format.
pub struct InChITree<Count> {
    node: RepeatNode<Count, Element>,
}

impl<Count> From<InChITree<Count>> for RepeatNode<Count, Element> {
    fn from(tree: InChITree<Count>) -> Self {
        tree.node
    }
}

impl<Count: CountLike, Charge: ChargeLike, Extension> From<InChITree<Count>>
    for ChemicalTree<Count, Charge, Extension>
{
    fn from(tree: InChITree<Count>) -> Self {
        let count: RepeatNode<Count, Element> = tree.into();
        count.into()
    }
}

impl<Count: CountLike> MolecularTree<Count> for InChITree<Count> {
    type ElementIter<'a>
        = <RepeatNode<Count, Element> as MolecularTree<Count>>::ElementIter<'a>
    where
        Self: 'a;

    type NonHydrogenElementIter<'a>
        = <RepeatNode<Count, Element> as MolecularTree<Count>>::NonHydrogenElementIter<'a>
    where
        Self: 'a;

    #[inline]
    fn elements(&self) -> Self::ElementIter<'_> {
        self.node.elements()
    }

    #[inline]
    fn non_hydrogens(&self) -> Self::NonHydrogenElementIter<'_> {
        self.node.non_hydrogens()
    }

    #[inline]
    fn contains_elements(&self) -> bool {
        self.node.contains_elements()
    }

    #[inline]
    fn contains_non_hydrogens(&self) -> bool {
        self.node.contains_non_hydrogens()
    }

    #[inline]
    fn contains_isotopes(&self) -> bool {
        self.node.contains_isotopes()
    }

    #[inline]
    fn contains_element(&self, element: Element) -> bool {
        self.node.contains_element(element)
    }

    #[inline]
    fn contains_isotope(&self, isotope: elements_rs::Isotope) -> bool {
        self.node.contains_isotope(isotope)
    }

    #[inline]
    fn number_of_elements(&self) -> usize {
        self.node.number_of_elements()
    }

    #[inline]
    fn count_of_element<C>(&self, element: Element) -> Result<C, crate::errors::CountError>
    where
        C: From<Count>
            + num_traits::CheckedAdd
            + num_traits::CheckedMul
            + num_traits::ConstZero
            + num_traits::ConstOne,
    {
        self.node.count_of_element::<C>(element)
    }

    #[inline]
    fn count_of_isotope<C>(
        &self,
        isotope: elements_rs::Isotope,
    ) -> Result<C, crate::errors::CountError>
    where
        C: From<Count>
            + num_traits::CheckedAdd
            + num_traits::CheckedMul
            + num_traits::ConstZero
            + num_traits::ConstOne,
    {
        self.node.count_of_isotope::<C>(isotope)
    }

    fn isotopologue_mass(&self) -> f64 {
        self.node.isotopologue_mass()
    }

    fn is_noble_gas_compound(&self) -> bool {
        self.node.is_noble_gas_compound()
    }

    fn isotopic_normalization(&self) -> Self {
        Self { node: self.node.isotopic_normalization() }
    }

    fn check_hill_ordering(
        &self,
        predecessor: Option<Element>,
        has_carbon: bool,
    ) -> Result<Option<Element>, ()> {
        self.node.check_hill_ordering(predecessor, has_carbon)
    }
}

impl<Count: CountLike> From<Element> for InChITree<Count> {
    fn from(element: Element) -> Self {
        InChITree { node: RepeatNode::new(Count::ONE, element) }
    }
}

impl<Count> From<RepeatNode<Count, Element>> for InChITree<Count> {
    fn from(node: RepeatNode<Count, Element>) -> Self {
        InChITree { node }
    }
}

impl<Count: CountLike> Display for InChITree<Count> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if self.node.count().is_one() {
            write!(f, "{}", self.node.node())
        } else {
            write!(f, "{}{}", self.node.node(), self.node.count())
        }
    }
}
