pub mod ref_id;

#[cfg(test)]
pub mod test;

use std::cmp::Ordering;
use std::hash::{Hash, Hasher};

use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::event::id::ref_id::{RefID, RefIDInner};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ID {
    pub inner: IDInner,
}
impl ID {
    /// Convenience method to get a [`RefID`] borrowing from this `ID`.
    pub fn as_ref(&self) -> ref_id::RefID<'_> {
        ref_id::RefID::from(self)
    }

    pub fn try_as_i64(&self) -> Option<i64> {
        match &self.inner {
            IDInner::Int(v) => Some(*v),
            IDInner::String(_) => None,
        }
    }

    pub fn try_as_i64_or_panic(&self) -> i64 {
        match &self.inner {
            IDInner::Int(v) => *v,
            IDInner::String(_) => panic!("ID is not an integer"),
        }
    }

    pub fn try_as_str(&self) -> Option<&str> {
        match &self.inner {
            IDInner::Int(_) => None,
            IDInner::String(s) => Some(s.as_str()),
        }
    }

    pub fn try_as_str_or_panic(&self) -> &str {
        match &self.inner {
            IDInner::Int(_) => panic!("ID is not a string"),
            IDInner::String(s) => s.as_str(),
        }
    }

    pub fn is_int(&self) -> bool {
        matches!(&self.inner, IDInner::Int(_))
    }

    pub fn is_string(&self) -> bool {
        matches!(&self.inner, IDInner::String(_))
    }

    pub fn try_into_string(self) -> Option<String> {
        match self.inner {
            IDInner::Int(_) => None,
            IDInner::String(s) => Some(s),
        }
    }

    pub fn try_into_string_or_panic(self) -> String {
        match self.inner {
            IDInner::Int(_) => panic!("ID is not a string"),
            IDInner::String(s) => s,
        }
    }
}

impl std::fmt::Display for ID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.inner {
            IDInner::Int(v) => write!(f, "{}", v),
            IDInner::String(v) => write!(f, "{}", v),
        }
    }
}
impl ID {
    pub fn new<T: ParseId>(inner: T) -> ID {
        let inner = inner.into_id_inner();
        ID { inner }
    }
}

impl PartialEq<ref_id::RefID<'_>> for ID {
    fn eq(&self, other: &ref_id::RefID<'_>) -> bool {
        self.inner == other.inner
    }
}

impl PartialOrd for ID {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for ID {
    fn cmp(&self, other: &Self) -> Ordering {
        self.inner.cmp(&other.inner)
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IDInner {
    Int(i64),
    String(String),
}

impl PartialEq<ref_id::RefIDInner<'_>> for IDInner {
    fn eq(&self, other: &ref_id::RefIDInner<'_>) -> bool {
        match (self, other) {
            (IDInner::Int(a), ref_id::RefIDInner::Int(b)) => a == *b,
            (IDInner::String(a), ref_id::RefIDInner::String(b)) => a == b,

            _ => false,
        }
    }
}

impl PartialOrd for IDInner {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for IDInner {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (IDInner::Int(a), IDInner::Int(b)) => a.cmp(b),
            (IDInner::String(a), IDInner::String(b)) => a.cmp(b),
            (IDInner::Int(_), IDInner::String(_)) => Ordering::Less,
            (IDInner::String(_), IDInner::Int(_)) => Ordering::Greater,
        }
    }
}

impl Hash for IDInner {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            IDInner::Int(v) => {
                0u8.hash(state);
                v.hash(state);
            }
            IDInner::String(v) => {
                1u8.hash(state);
                v.hash(state);
            }
        }
    }
}

pub trait ParseId: Sized {
    fn into_id_inner(self) -> IDInner;
}

impl ParseId for i64 {
    fn into_id_inner(self) -> IDInner {
        IDInner::Int(self)
    }
}
impl ParseId for String {
    fn into_id_inner(self) -> IDInner {
        IDInner::String(self)
    }
}
impl ParseId for &str {
    fn into_id_inner(self) -> IDInner {
        IDInner::String(self.to_string())
    }
}
impl ParseId for RefIDInner<'_> {
    fn into_id_inner(self) -> IDInner {
        match self {
            ref_id::RefIDInner::Int(v) => IDInner::Int(v.to_owned()),
            ref_id::RefIDInner::String(v) => IDInner::String(v.to_owned()),
        }
    }
}
impl ParseId for RefID<'_> {
    fn into_id_inner(self) -> IDInner {
        self.inner.into_id_inner()
    }
}
impl ParseId for IDInner {
    fn into_id_inner(self) -> IDInner {
        self
    }
}

// Serde implementations:
impl Serialize for IDInner {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match self {
            IDInner::Int(v) => serializer.serialize_i64(*v),
            IDInner::String(s) => serializer.serialize_str(s),
        }
    }
}

struct IDInnerVisitor;

impl<'de> Visitor<'de> for IDInnerVisitor {
    type Value = IDInner;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(formatter, "an integer or a string for ID")
    }

    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(IDInner::Int(v))
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(IDInner::String(v.to_string()))
    }

    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(IDInner::String(v))
    }
}

impl<'de> Deserialize<'de> for IDInner {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_any(IDInnerVisitor)
    }
}

impl Serialize for ID {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.inner.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for ID {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let inner = IDInner::deserialize(deserializer)?;
        Ok(ID { inner })
    }
}

impl From<ID> for toml::Value {
    fn from(value: ID) -> Self {
        match value.inner {
            IDInner::Int(v) => v.into(),
            IDInner::String(v) => v.into(),
        }
    }
}
impl From<ID> for toml_edit::Value {
    fn from(value: ID) -> Self {
        match value.inner {
            IDInner::Int(v) => v.into(),
            IDInner::String(v) => v.into(),
        }
    }
}
impl From<ID> for serde_json::Value {
    fn from(value: ID) -> Self {
        match value.inner {
            IDInner::Int(v) => v.into(),
            IDInner::String(v) => v.into(),
        }
    }
}
