use std::{
    borrow::Cow,
    cell::{Cell, RefCell},
    collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
    ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
    rc::Rc,
    sync::{Arc, Mutex, RwLock},
};

/// Trait to descript a data structure or type
pub trait Describer {
    /// describer function
    fn describe() -> String;
}

// ============================================================================================
// PRIMITIVES
// ============================================================================================
macro_rules! describer_primitive {
    ($($t:ty),+) => (
         $(impl Describer for $t {
            fn describe() -> String {
                stringify!($t).to_string()
            }
        })*
    )
}

describer_primitive!(
    i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize
);
describer_primitive!(f32, f64);
describer_primitive!(bool, char);

impl Describer for String {
    fn describe() -> String {
        "String".to_string()
    }
}

impl Describer for str {
    fn describe() -> String {
        "&str".to_string()
    }
}

// ============================================================================================
// Option and Result
// ============================================================================================
impl<T: Describer> Describer for Option<T> {
    fn describe() -> String {
        format!("{}?", T::describe())
    }
}

impl<T: Describer, E: Describer> Describer for Result<T, E> {
    fn describe() -> String {
        format!("!{{{}, {}}}", T::describe(), E::describe())
    }
}

// ============================================================================================
// STD Collections
// ============================================================================================
impl<T: Describer> Describer for Vec<T> {
    fn describe() -> String {
        format!("[{}]", T::describe())
    }
}

impl<T: Describer> Describer for [T] {
    fn describe() -> String {
        format!("[{}]", T::describe())
    }
}

impl<T: Describer> Describer for VecDeque<T> {
    fn describe() -> String {
        format!("[{}]", T::describe())
    }
}

impl<T: Describer> Describer for LinkedList<T> {
    fn describe() -> String {
        format!("[{}]", T::describe())
    }
}

impl<T: Describer> Describer for BinaryHeap<T> {
    fn describe() -> String {
        format!("^[{}]", T::describe())
    }
}

impl<T: Describer, S: ::std::hash::BuildHasher> Describer for HashSet<T, S> {
    fn describe() -> String {
        format!("#{{{}}}", T::describe())
    }
}

#[cfg(feature = "indexmap")]
impl<T: Describer, S: ::std::hash::BuildHasher> Describer for indexmap::IndexSet<T, S> {
    fn describe() -> String {
        format!("#{{{}}}", T::describe())
    }
}

#[cfg(feature = "dashmap")]
impl<T: Describer, S: ::std::hash::BuildHasher> Describer for dashmap::DashSet<T, S> {
    fn describe() -> String {
        format!("#{{{}}}", T::describe())
    }
}

impl<T: Describer> Describer for BTreeSet<T> {
    fn describe() -> String {
        format!("#{{{}}}", T::describe())
    }
}

impl<T: Describer, V: Describer, S: ::std::hash::BuildHasher> Describer for HashMap<T, V, S> {
    fn describe() -> String {
        format!("{{{}: {}}}", T::describe(), V::describe())
    }
}

#[cfg(feature = "indexmap")]
impl<T: Describer, V: Describer, S: ::std::hash::BuildHasher> Describer
    for indexmap::IndexMap<T, V, S>
{
    fn describe() -> String {
        format!("{{{}: {}}}", T::describe(), V::describe())
    }
}

#[cfg(feature = "dashmap")]
impl<T: Describer, V: Describer, S: ::std::hash::BuildHasher> Describer
    for dashmap::DashMap<T, V, S>
{
    fn describe() -> String {
        format!("{{{}: {}}}", T::describe(), V::describe())
    }
}

impl<T: Describer, V: Describer> Describer for BTreeMap<T, V> {
    fn describe() -> String {
        format!("{{{}: {}}}", T::describe(), V::describe())
    }
}

// ============================================================================================
// Pointers
// ============================================================================================
impl<T: Describer> Describer for RwLock<T> {
    fn describe() -> String {
        T::describe()
    }
}

impl<T: Describer> Describer for Mutex<T> {
    fn describe() -> String {
        T::describe()
    }
}

impl<T: Describer> Describer for Box<T> {
    fn describe() -> String {
        format!("*{}", T::describe())
    }
}

impl<T: Describer> Describer for Arc<T> {
    fn describe() -> String {
        format!("arc {}", T::describe())
    }
}

impl<T: Describer> Describer for Rc<T> {
    fn describe() -> String {
        format!("rc {}", T::describe())
    }
}

impl<T: Describer> Describer for Cell<T> {
    fn describe() -> String {
        format!("&{}", T::describe())
    }
}

impl<T: Describer> Describer for RefCell<T> {
    fn describe() -> String {
        format!("&{}", T::describe())
    }
}

impl<T: Describer + Clone> Describer for Cow<'_, T> {
    fn describe() -> String {
        format!("~{}", T::describe())
    }
}

// ============================================================================================
// Pointers
// ============================================================================================
macro_rules! describer_tuple {
    () => {
        impl Describer for () {
            fn describe() -> String {
                "()".to_string()
            }
        }
    };
    ($($T:ident),+) => {
        impl<$($T: Describer),+> Describer for ($($T,)+) {
            fn describe() -> String {
                format!(
                    "({})",
                    vec![$($T::describe()),+].join(", ")
                )
            }
        }
    };
}

describer_tuple!();
describer_tuple!(T0);
describer_tuple!(T0, T1);
describer_tuple!(T0, T1, T2);
describer_tuple!(T0, T1, T2, T3);
describer_tuple!(T0, T1, T2, T3, T4);
describer_tuple!(T0, T1, T2, T3, T4, T5);
describer_tuple!(T0, T1, T2, T3, T4, T5, T6);
describer_tuple!(T0, T1, T2, T3, T4, T5, T6, T7);
describer_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
describer_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
describer_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
describer_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);

// ============================================================================================
// Ranges
// ============================================================================================
impl<T: Describer> Describer for RangeToInclusive<T> {
    fn describe() -> String {
        format!("]-∞..{}]", T::describe())
    }
}

impl<T: Describer> Describer for RangeTo<T> {
    fn describe() -> String {
        format!("]-∞..{}[", T::describe())
    }
}

impl<T: Describer> Describer for RangeInclusive<T> {
    fn describe() -> String {
        format!("[{}]", T::describe())
    }
}

impl Describer for RangeFull {
    fn describe() -> String {
        "]∞[".to_string()
    }
}

impl<T: Describer> Describer for RangeFrom<T> {
    fn describe() -> String {
        format!("[{}..+∞[", T::describe())
    }
}

impl<T: Describer> Describer for Range<T> {
    fn describe() -> String {
        format!("[{}[", T::describe())
    }
}

// ============================================================================================
// Third Parties
// ============================================================================================
#[cfg(feature = "chrono")]
impl<T: chrono::TimeZone> Describer for chrono::DateTime<T> {
    fn describe() -> String {
        "#DateTime".to_string()
    }
}

#[cfg(feature = "chrono")]
impl Describer for chrono::NaiveDate {
    fn describe() -> String {
        "#NaiveDate".to_string()
    }
}

#[cfg(feature = "chrono")]
impl Describer for chrono::NaiveDateTime {
    fn describe() -> String {
        "#NaiveDateTime".to_string()
    }
}

#[cfg(feature = "chrono")]
impl Describer for chrono::NaiveTime {
    fn describe() -> String {
        "#NaiveTime".to_string()
    }
}

#[cfg(feature = "uuid")]
impl Describer for uuid::Uuid {
    fn describe() -> String {
        "#Uuid".to_string()
    }
}

#[cfg(feature = "regex")]
impl Describer for regex::Regex {
    fn describe() -> String {
        "#Regex".to_string()
    }
}

#[cfg(feature = "http")]
impl Describer for http::HeaderMap {
    fn describe() -> String {
        "#Http/HeaderMap".to_string()
    }
}

#[cfg(feature = "http")]
impl Describer for http::Extensions {
    fn describe() -> String {
        "#Http/Extensions".to_string()
    }
}

#[cfg(feature = "http")]
impl Describer for http::Method {
    fn describe() -> String {
        "#Http/Method".to_string()
    }
}

#[cfg(feature = "http")]
impl<T> Describer for http::Request<T> {
    fn describe() -> String {
        "#Http/Request".to_string()
    }
}

#[cfg(feature = "http")]
impl<T> Describer for http::Response<T> {
    fn describe() -> String {
        "#Http/Response".to_string()
    }
}

#[cfg(feature = "http")]
impl Describer for http::Uri {
    fn describe() -> String {
        "#Http/Uri".to_string()
    }
}

#[cfg(feature = "http")]
impl Describer for http::StatusCode {
    fn describe() -> String {
        "#Http/StatusCode".to_string()
    }
}

#[cfg(feature = "http")]
impl Describer for http::Version {
    fn describe() -> String {
        "#Http/Version".to_string()
    }
}

#[cfg(feature = "tokio")]
impl<T: Describer> Describer for tokio::sync::Mutex<T> {
    fn describe() -> String {
        format!("#tokio/{}", T::describe())
    }
}

#[cfg(feature = "tokio")]
impl<T: Describer> Describer for tokio::sync::RwLock<T> {
    fn describe() -> String {
        format!("#tokio/{}", T::describe())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn primitives() {
        assert_eq!(i8::describe(), "i8");
        assert_eq!(i16::describe(), "i16");
        assert_eq!(i32::describe(), "i32");
        assert_eq!(i64::describe(), "i64");
        assert_eq!(i128::describe(), "i128");
        assert_eq!(isize::describe(), "isize");

        assert_eq!(u8::describe(), "u8");
        assert_eq!(u16::describe(), "u16");
        assert_eq!(u32::describe(), "u32");
        assert_eq!(u64::describe(), "u64");
        assert_eq!(u128::describe(), "u128");
        assert_eq!(usize::describe(), "usize");

        assert_eq!(f32::describe(), "f32");
        assert_eq!(f64::describe(), "f64");

        assert_eq!(char::describe(), "char");
        assert_eq!(bool::describe(), "bool");
        assert_eq!(String::describe(), "String");
        assert_eq!(str::describe(), "&str");
    }

    #[test]
    fn optional() {
        assert_eq!(Option::<u8>::describe(), "u8?");
    }

    #[test]
    fn result() {
        assert_eq!(Result::<u8, bool>::describe(), "!{u8, bool}");
    }

    #[test]
    fn collections() {
        assert_eq!(HashMap::<u8, bool>::describe(), "{u8: bool}");
        assert_eq!(BTreeMap::<u8, bool>::describe(), "{u8: bool}");
        assert_eq!(Vec::<u8>::describe(), "[u8]");
        assert_eq!(VecDeque::<u8>::describe(), "[u8]");
        assert_eq!(LinkedList::<u8>::describe(), "[u8]");
        assert_eq!(BinaryHeap::<u8>::describe(), "^[u8]");
        assert_eq!(HashSet::<u8>::describe(), "#{u8}");
        assert_eq!(BTreeSet::<u8>::describe(), "#{u8}");
    }

    #[test]
    fn pointers() {
        assert_eq!(Box::<u8>::describe(), "*u8");
        assert_eq!(Arc::<u8>::describe(), "arc u8");
        assert_eq!(Rc::<u8>::describe(), "rc u8");
        assert_eq!(Cell::<u8>::describe(), "&u8");
        assert_eq!(RefCell::<u8>::describe(), "&u8");
        assert_eq!(Cow::<u8>::describe(), "~u8");
    }

    #[test]
    fn range() {
        assert_eq!(RangeToInclusive::<i32>::describe(), "]-∞..i32]");
        assert_eq!(RangeTo::<i32>::describe(), "]-∞..i32[");
        assert_eq!(RangeInclusive::<i32>::describe(), "[i32]");
        assert_eq!(RangeFull::describe(), "]∞[");
        assert_eq!(RangeFrom::<i32>::describe(), "[i32..+∞[");
        assert_eq!(Range::<i32>::describe(), "[i32[");
    }
}
