Documentation
use std::collections::{HashMap, HashSet};

pub use rts_derive::rts_to_string;
pub use rts_derive::RTS;

pub trait RTS<A1 = (), A2 = (), B = ()> {
    fn to_type_string(&self) -> String {
        "any".into()
    }
    fn to_type_name(&self) -> String {
        "any".into()
    }
}

macro_rules! impl_rts_number {
    ( $( $a:ty )   * ) => {
            $(
                impl RTS for $a {
                    fn to_type_string(&self)->String{
                        "number".into()
                    }
                    fn to_type_name(&self)->String{
                        "number".into()
                    }
                }

             )*

    };
}

macro_rules! impl_rts {
    ( $( ($a:ty, $b:expr) ) , * ) => {
            $(
                impl RTS for $a {
                    fn to_type_string(&self)->String{
                        $b.into()
                    }
                    fn to_type_name(&self)->String{
                        $b.into()
                    }
                }

             )*

    };
}

impl<K: RTS + Default, V: RTS + Default> RTS for HashMap<K, V> {
    fn to_type_string(&self) -> String {
        format!(
            "Map<{}, {}>",
            K::default().to_type_string(),
            V::default().to_type_string(),
        )
    }

    fn to_type_name(&self) -> String {
        format!(
            "Map<{}, {}>",
            K::default().to_type_name(),
            V::default().to_type_name(),
        )
    }
}

impl<V: RTS + Default> RTS for HashSet<V> {
    fn to_type_string(&self) -> String {
        format!("Set<{}>", V::default().to_type_string(),)
    }
    fn to_type_name(&self) -> String {
        format!("Set<{}>", V::default().to_type_name(),)
    }
}

impl<V: RTS + Default> RTS for Vec<V> {
    fn to_type_string(&self) -> String {
        format!("Array<{}>", V::default().to_type_string(),)
    }
    fn to_type_name(&self) -> String {
        format!("Array<{}>", V::default().to_type_name(),)
    }
}

fn get_name(s: &str) -> &str {
    s.split("::").last().expect("function name error")
}

impl_rts_number!(i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 );

impl_rts!((bool, "bool"), (char, "string"), (String, "string"));

// impl RTS for () {
//     fn to_type_string(&self) -> String {
//         "void".into()
//     }

//     fn to_type_name(&self) -> String {
//         "void".into()
//     }
// }

// A->R
impl<F, A, R> RTS<(A), (R)> for F
where
    F: Fn(A) -> R,
    A: RTS,
    R: RTS,
{
    fn to_type_string(&self) -> String {
        let fn_name = heck::AsLowerCamelCase(get_name(std::any::type_name::<F>()));
        let arg_name = get_name(std::any::type_name::<A>());
        let rt_name = get_name(std::any::type_name::<R>());
        format!(
            "declare function {}(arg0: {}): {};",
            fn_name, arg_name, rt_name
        )
    }

    fn to_type_name(&self) -> String {
        "any".into()
    }
}

// ()->()
impl<F> RTS for F
where
    F: Fn(),
{
    fn to_type_string(&self) -> String {
        let fn_name = heck::AsLowerCamelCase(get_name(std::any::type_name::<F>()));
        format!("declare function {}();", fn_name)
    }

    fn to_type_name(&self) -> String {
        "any".into()
    }
}

// ()->R
impl<F, R> RTS<R> for F
where
    F: Fn() -> R,
    R: RTS,
{
    fn to_type_string(&self) -> String {
        let fn_name = heck::AsLowerCamelCase(get_name(std::any::type_name::<F>()));
        let rt_name = get_name(std::any::type_name::<R>());
        format!("declare function {}(): {};", fn_name, rt_name)
    }

    fn to_type_name(&self) -> String {
        "any".into()
    }
}

// A A ->R
impl<F, A1, A2, R> RTS<A1, A2, R> for F
where
    F: Fn(A1, A2) -> R,
    A1: RTS,
    A2: RTS,
    R: RTS,
{
    fn to_type_string(&self) -> String {
        let fn_name = heck::AsLowerCamelCase(get_name(std::any::type_name::<F>()));
        let rt_name = get_name(std::any::type_name::<R>());
        let arg1_name = get_name(std::any::type_name::<A1>());
        let arg2_name = get_name(std::any::type_name::<A2>());
        format!(
            "declare function {}(arg0: {}, arg1: {}): {};",
            fn_name, arg1_name, arg2_name, rt_name
        )
    }

    fn to_type_name(&self) -> String {
        "any".into()
    }
}