use super::input::input_node::parse_u64_strict;
use crate::iter::IntoDynBoxed;
use bytemuck::{Pod, Zeroable};
use itertools::Itertools;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
use std::{
    borrow::Cow,
    fmt::{Debug, Display, Formatter},
    iter,
};

pub mod edges;
pub mod layers;
pub mod properties;

use crate::core::entities::properties::prop::PropType;
pub use layers::*;

// The only reason this is public is because the physical IDs of the nodes don’t move.
#[repr(transparent)]
#[derive(
    Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize, Pod, Zeroable,
)]
pub struct VID(pub usize);

impl Default for VID {
    fn default() -> Self {
        VID(usize::MAX)
    }
}

impl VID {
    pub fn index(&self) -> usize {
        self.0
    }

    pub fn as_u64(&self) -> u64 {
        self.0 as u64
    }

    /// check if the VID points to a node
    #[inline]
    pub fn is_initialised(&self) -> bool {
        self.0 != usize::MAX
    }

    #[inline]
    /// either return `self` if it is initialised, otherwise run the `init` closure
    pub fn or_init(self, init: impl FnOnce() -> VID) -> Self {
        if self.is_initialised() {
            self
        } else {
            init()
        }
    }

    #[inline]
    /// either return `Some(self)` if it is initialised, otherwise, run the `init` closure
    pub fn or_maybe_init(self, init: impl FnOnce() -> Option<VID>) -> Option<Self> {
        if self.is_initialised() {
            Some(self)
        } else {
            init()
        }
    }

    #[inline]
    /// return `Some(self)` if initialised, otherwise, `None`
    pub fn into_option(self) -> Option<Self> {
        self.is_initialised().then_some(self)
    }
}

impl From<usize> for VID {
    fn from(id: usize) -> Self {
        VID(id)
    }
}

impl From<VID> for usize {
    fn from(id: VID) -> Self {
        id.0
    }
}

#[repr(transparent)]
#[derive(
    Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize, Pod, Zeroable,
)]
pub struct EID(pub usize);

impl Default for EID {
    fn default() -> Self {
        EID(usize::MAX)
    }
}

impl EID {
    pub fn index(&self) -> usize {
        self.0
    }

    pub fn as_u64(self) -> u64 {
        self.0 as u64
    }

    pub fn with_layer(self, layer: LayerId) -> ELID {
        ELID::new(self, layer)
    }

    pub fn with_layer_deletion(self, layer: LayerId) -> ELID {
        ELID::new_deletion(self, layer)
    }
}

impl From<EID> for usize {
    fn from(id: EID) -> Self {
        id.0
    }
}

impl From<usize> for EID {
    fn from(id: usize) -> Self {
        EID(id)
    }
}

impl EID {
    pub fn from_u64(id: u64) -> Self {
        EID(id as usize)
    }
}

impl From<ELID> for EID {
    fn from(elid: ELID) -> Self {
        elid.eid()
    }
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
pub struct ELID {
    edge_and_deletion: EDID,
    layer: LayerId,
}

/// Edge id with deletion flag
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
pub struct EDID(pub usize);

impl Debug for EDID {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("EDID")
            .field("edge", &self.eid())
            .field("deletion", &self.is_deletion())
            .finish()
    }
}

impl EDID {
    pub fn new(edge: EID) -> Self {
        EDID(edge.0)
    }

    pub fn new_deletion(edge: EID) -> Self {
        EDID(edge.0 | DELETION_FLAG)
    }

    pub fn eid(&self) -> EID {
        EID(self.0 & !DELETION_FLAG)
    }

    pub fn is_deletion(&self) -> bool {
        self.0 & DELETION_FLAG != 0
    }

    pub fn into_deletion(mut self) -> Self {
        self.0 = self.0 | DELETION_FLAG;
        self
    }
}

impl Debug for ELID {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ELID")
            .field("edge", &self.edge_and_deletion)
            .field("layer", &self.layer())
            .field("deletion", &self.is_deletion())
            .finish()
    }
}

const DELETION_FLAG: usize = 1usize.reverse_bits();
pub const MAX_EID: usize = usize::MAX & !DELETION_FLAG;

impl ELID {
    pub fn new(edge: EID, layer: LayerId) -> Self {
        ELID {
            edge_and_deletion: EDID::new(edge),
            layer,
        }
    }

    pub fn new_flagged(edge_and_deletion: EDID, layer: LayerId) -> Self {
        ELID {
            edge_and_deletion,
            layer,
        }
    }

    pub fn new_deletion(edge: EID, layer: LayerId) -> Self {
        ELID {
            edge_and_deletion: EDID::new_deletion(edge),
            layer,
        }
    }

    pub fn layer(&self) -> LayerId {
        self.layer
    }

    pub fn eid(&self) -> EID {
        self.edge_and_deletion.eid()
    }

    pub fn eid_and_flag(&self) -> EDID {
        self.edge_and_deletion
    }

    pub fn is_deletion(&self) -> bool {
        self.edge_and_deletion.is_deletion()
    }

    pub fn into_deletion(mut self) -> Self {
        self.edge_and_deletion = self.edge_and_deletion.into_deletion();
        self
    }
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum GID {
    U64(u64),
    Str(String),
}

impl PartialEq<str> for GID {
    fn eq(&self, other: &str) -> bool {
        match self {
            GID::U64(_) => false,
            GID::Str(id) => id == other,
        }
    }
}

impl PartialEq<String> for GID {
    fn eq(&self, other: &String) -> bool {
        match self {
            GID::U64(_) => false,
            GID::Str(id) => id == other,
        }
    }
}

impl PartialEq<u64> for GID {
    fn eq(&self, other: &u64) -> bool {
        match self {
            GID::Str(_) => false,
            GID::U64(id) => id == other,
        }
    }
}

impl Default for GID {
    fn default() -> Self {
        GID::U64(u64::MAX)
    }
}

impl Display for GID {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            GID::U64(v) => write!(f, "{}", v),
            GID::Str(v) => write!(f, "{}", v),
        }
    }
}

impl GID {
    pub fn dtype(&self) -> GidType {
        match self {
            GID::U64(_) => GidType::U64,
            GID::Str(_) => GidType::Str,
        }
    }
    pub fn into_str(self) -> Option<String> {
        match self {
            GID::Str(v) => Some(v),
            _ => None,
        }
    }

    pub fn into_u64(self) -> Option<u64> {
        match self {
            GID::U64(v) => Some(v),
            _ => None,
        }
    }

    pub fn as_str(&self) -> Option<&str> {
        match self {
            GID::Str(v) => Some(v.as_str()),
            _ => None,
        }
    }

    pub fn as_u64(&self) -> Option<u64> {
        match self {
            GID::U64(v) => Some(*v),
            _ => None,
        }
    }

    pub fn to_str(&self) -> Cow<'_, str> {
        match self {
            GID::U64(v) => Cow::Owned(v.to_string()),
            GID::Str(v) => Cow::Borrowed(v),
        }
    }

    pub fn to_i64(&self) -> Option<i64> {
        match self {
            GID::U64(v) => v.to_i64(),
            GID::Str(v) => parse_u64_strict(v)?.to_i64(),
        }
    }

    pub fn to_u64(&self) -> Option<u64> {
        match self {
            GID::U64(v) => Some(*v),
            GID::Str(v) => parse_u64_strict(v),
        }
    }

    pub fn as_ref(&self) -> GidRef<'_> {
        match self {
            GID::U64(v) => GidRef::U64(*v),
            GID::Str(v) => GidRef::Str(v),
        }
    }
}

impl From<u64> for GID {
    fn from(id: u64) -> Self {
        Self::U64(id)
    }
}

impl From<&u64> for GID {
    fn from(value: &u64) -> Self {
        GID::U64(*value)
    }
}

impl From<String> for GID {
    fn from(id: String) -> Self {
        Self::Str(id)
    }
}

impl From<&str> for GID {
    fn from(id: &str) -> Self {
        Self::Str(id.to_string())
    }
}

impl<'a> From<GidRef<'a>> for GID {
    fn from(value: GidRef<'a>) -> Self {
        match value {
            GidRef::U64(v) => GID::U64(v),
            GidRef::Str(v) => GID::Str(v.to_owned()),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum GidRef<'a> {
    U64(u64),
    Str(&'a str),
}

#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum GidCow<'a> {
    U64(u64),
    Str(Cow<'a, str>),
}

impl<'a> From<GidRef<'a>> for GidCow<'a> {
    fn from(value: GidRef<'a>) -> Self {
        match value {
            GidRef::U64(v) => Self::U64(v),
            GidRef::Str(v) => Self::Str(Cow::Borrowed(v)),
        }
    }
}

impl<'a> GidCow<'a> {
    pub fn as_ref<'b>(&'b self) -> GidRef<'b>
    where
        'a: 'b,
    {
        match self {
            GidCow::U64(v) => GidRef::U64(*v),
            GidCow::Str(v) => GidRef::Str(v),
        }
    }

    pub fn into_owned(self) -> GID {
        match self {
            GidCow::U64(v) => GID::U64(v),
            GidCow::Str(v) => GID::Str(v.into_owned()),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum GidType {
    U64,
    Str,
}

impl Display for GidType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            GidType::U64 => {
                write!(f, "Numeric")
            }
            GidType::Str => {
                write!(f, "String")
            }
        }
    }
}

impl GidType {
    pub fn from_prop_type(prop_type: &PropType) -> Option<Self> {
        match prop_type {
            PropType::Str => Some(GidType::Str),
            PropType::U64 | PropType::U32 | PropType::I64 | PropType::I32 => Some(GidType::U64),
            _ => None,
        }
    }
}

impl Display for GidRef<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            GidRef::U64(v) => write!(f, "{v}"),
            GidRef::Str(v) => write!(f, "{v}"),
        }
    }
}

impl<'a> From<&'a GID> for GidRef<'a> {
    fn from(value: &'a GID) -> Self {
        match value {
            GID::U64(v) => GidRef::U64(*v),
            GID::Str(v) => GidRef::Str(v),
        }
    }
}

impl<'a> From<&'a str> for GidRef<'a> {
    fn from(value: &'a str) -> Self {
        GidRef::Str(value)
    }
}

impl From<u64> for GidRef<'_> {
    fn from(value: u64) -> Self {
        GidRef::U64(value)
    }
}

impl<'a> GidRef<'a> {
    pub fn dtype(self) -> GidType {
        match self {
            GidRef::U64(_) => GidType::U64,
            GidRef::Str(_) => GidType::Str,
        }
    }
    pub fn as_str(self) -> Option<&'a str> {
        match self {
            GidRef::Str(s) => Some(s),
            _ => None,
        }
    }

    pub fn as_u64(self) -> Option<u64> {
        match self {
            GidRef::U64(v) => Some(v),
            _ => None,
        }
    }

    pub fn to_owned(self) -> GID {
        match self {
            GidRef::U64(v) => GID::U64(v),
            GidRef::Str(v) => GID::Str(v.to_owned()),
        }
    }

    pub fn to_str(self) -> Cow<'a, str> {
        match self {
            GidRef::U64(v) => Cow::Owned(v.to_string()),
            GidRef::Str(v) => Cow::Borrowed(v),
        }
    }

    pub fn to_i64(self) -> Option<i64> {
        match self {
            GidRef::U64(v) => v.to_i64(),
            GidRef::Str(v) => parse_u64_strict(v)?.to_i64(),
        }
    }

    pub fn to_u64(self) -> Option<u64> {
        match self {
            GidRef::U64(v) => Some(v),
            GidRef::Str(v) => parse_u64_strict(v),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::core::entities::{LayerId, Multiple};

    #[test]
    fn empty_bit_multiple() {
        let bm = super::Multiple::default();
        let actual = bm.into_iter().collect::<Vec<_>>();
        let expected: Vec<LayerId> = vec![];
        assert_eq!(actual, expected);
    }

    #[test]
    fn set_one() {
        let bm: Multiple = [1].into_iter().collect();
        let actual = bm.into_iter().collect::<Vec<_>>();
        assert_eq!(actual, vec![1usize]);
    }

    #[test]
    fn set_two() {
        let bm: Multiple = [1, 67].into_iter().collect();

        let actual = bm.into_iter().collect::<Vec<_>>();
        assert_eq!(actual, vec![1usize, 67]);
    }
}

impl LayerIds {
    pub fn intersect(&self, other: &LayerIds) -> LayerIds {
        match (self, other) {
            (LayerIds::None, _) => LayerIds::None,
            (_, LayerIds::None) => LayerIds::None,
            (LayerIds::All, other) => other.clone(),
            (this, LayerIds::All) => this.clone(),
            (LayerIds::One(id), other) => {
                if other.contains(id) {
                    LayerIds::One(*id)
                } else {
                    LayerIds::None
                }
            }
            (LayerIds::Multiple(ids), other) => {
                let ids: Vec<_> = ids.iter().filter(|id| other.contains(id)).collect();
                match ids.len() {
                    0 => LayerIds::None,
                    1 => LayerIds::One(ids[0]),
                    _ => LayerIds::Multiple(ids.into()),
                }
            }
        }
    }

    pub fn union(&self, other: &LayerIds) -> LayerIds {
        match (self, other) {
            (LayerIds::All, _) | (_, LayerIds::All) => LayerIds::All,
            (LayerIds::None, o) | (o, LayerIds::None) => o.clone(),
            (LayerIds::One(id), LayerIds::One(other_id)) => {
                if id == other_id {
                    LayerIds::One(*id)
                } else {
                    LayerIds::Multiple([*id.min(other_id), *id.max(other_id)].into_iter().collect())
                }
            }
            (LayerIds::Multiple(ids), LayerIds::Multiple(other_ids)) => {
                LayerIds::Multiple(ids.iter().merge(other_ids.iter()).dedup().collect())
            }
            (LayerIds::One(id), LayerIds::Multiple(ids))
            | (LayerIds::Multiple(ids), LayerIds::One(id)) => {
                if ids.contains(*id) {
                    LayerIds::Multiple(ids.clone())
                } else {
                    LayerIds::Multiple(ids.iter().merge(iter::once(*id)).collect())
                }
            }
        }
    }

    pub fn contains(&self, layer_id: &LayerId) -> bool {
        match self {
            LayerIds::All => true,
            LayerIds::One(id) => id == layer_id,
            LayerIds::Multiple(ids) => ids.contains(*layer_id),
            LayerIds::None => false,
        }
    }

    pub fn is_none(&self) -> bool {
        matches!(self, LayerIds::None)
    }

    pub fn is_single(&self) -> bool {
        matches!(self, LayerIds::One(_))
    }

    pub fn is_all(&self) -> bool {
        matches!(self, LayerIds::All)
    }

    pub fn iter(&self, num_layers: usize) -> impl Iterator<Item = LayerId> + use<'_> {
        match self {
            LayerIds::None => iter::empty().into_dyn_boxed(),
            LayerIds::All => (0..num_layers).map(LayerId).into_dyn_boxed(),
            LayerIds::One(id) => iter::once(*id).into_dyn_boxed(),
            LayerIds::Multiple(ids) => ids.into_iter().into_dyn_boxed(),
        }
    }

    pub fn into_iter(self, num_layers: usize) -> impl Iterator<Item = LayerId> {
        match self {
            LayerIds::None => iter::empty().into_dyn_boxed(),
            LayerIds::All => (0..num_layers).map(LayerId).into_dyn_boxed(),
            LayerIds::One(id) => iter::once(id).into_dyn_boxed(),
            LayerIds::Multiple(ids) => ids.into_iter().into_dyn_boxed(),
        }
    }
}

impl From<Vec<usize>> for LayerIds {
    fn from(v: Vec<usize>) -> Self {
        match v.len() {
            0 => LayerIds::All,
            1 => LayerIds::One(LayerId(v[0])),
            _ => LayerIds::Multiple(v.into()),
        }
    }
}

impl From<Vec<LayerId>> for LayerIds {
    fn from(v: Vec<LayerId>) -> Self {
        match v.len() {
            0 => LayerIds::All,
            1 => LayerIds::One(v[0]),
            _ => LayerIds::Multiple(v.into()),
        }
    }
}

impl<const N: usize> From<[usize; N]> for LayerIds {
    fn from(v: [usize; N]) -> Self {
        match v.len() {
            0 => LayerIds::All,
            1 => LayerIds::One(LayerId(v[0])),
            _ => LayerIds::Multiple(v.into_iter().collect()),
        }
    }
}

impl<const N: usize> From<[LayerId; N]> for LayerIds {
    fn from(v: [LayerId; N]) -> Self {
        match v.len() {
            0 => LayerIds::All,
            1 => LayerIds::One(v[0]),
            _ => LayerIds::Multiple(v.into_iter().collect()),
        }
    }
}

impl From<usize> for LayerIds {
    fn from(id: usize) -> Self {
        LayerIds::One(LayerId(id))
    }
}

impl From<LayerId> for LayerIds {
    fn from(id: LayerId) -> Self {
        LayerIds::One(id)
    }
}

#[cfg(test)]
mod tests {
    use std::hash::{Hash, Hasher};

    use super::*;
    use crate::core::entities::{LayerId, EID, MAX_EID};
    use proptest::{prelude::*, prop_assert, prop_assert_eq, prop_oneof, proptest};

    #[test]
    fn test_elid_layer_proptest() {
        proptest!(|(eid in 0..=MAX_EID, layer in 0..=usize::MAX)| {
            let elid = EID(eid).with_layer(LayerId(layer));
            prop_assert_eq!(elid.layer(), LayerId(layer));
            prop_assert!(!elid.is_deletion());

            let elid_deleted = elid.into_deletion();
            prop_assert_eq!(elid_deleted.layer(), LayerId(layer));
            prop_assert_eq!(elid_deleted.eid(), EID(eid));
            prop_assert!(elid_deleted.is_deletion())
        })
    }

    #[test]
    fn test_elid_deletion_proptest() {
        proptest!(|(eid in 0..=MAX_EID, layer in 0..=usize::MAX)| {
            let elid = EID(eid).with_layer_deletion(LayerId(layer));
            prop_assert_eq!(elid.layer(), LayerId(layer));
            prop_assert!(elid.is_deletion());
            prop_assert_eq!(elid, elid.into_deletion());
            prop_assert_eq!(elid.eid().0, eid);
        })
    }

    #[test]
    fn gid_and_gid_ref_hash_to_the_same_thing() {
        proptest!(|(gid in prop_oneof![any::<u64>().prop_map(GID::U64), ".*".prop_map(GID::Str)])| {
            let gid_ref: GidRef<'_> = (&gid).into();
            let mut gid_hasher = std::collections::hash_map::DefaultHasher::new();
            let mut gid_ref_hasher = std::collections::hash_map::DefaultHasher::new();
            gid.hash(&mut gid_hasher);
            gid_ref.hash(&mut gid_ref_hasher);
            prop_assert_eq!(gid_hasher.finish(), gid_ref_hasher.finish());
        })
    }
}
