use std::cmp::Ordering;

use num_traits::{
    AsPrimitive,
    ToPrimitive,
};
use ordered_float::OrderedFloat;

use crate::types::EuType;

impl EuType<'_> {
    #[must_use]
    pub fn eqv_ord(&self, other: &Self) -> Ordering {
        self.enum_index().cmp(&other.enum_index())
    }

    fn enum_index(&self) -> u8 {
        match self {
            Self::Bool(_) => 0,
            Self::Char(_) => 1,
            Self::I32(_) => 2,
            Self::I64(_) => 3,
            Self::IBig(_) => 4,
            Self::F64(_) => 5,
            Self::Word(_) => 6,
            Self::Str(_) => 7,
            Self::Opt(_) => 8,
            Self::Res(_) => 9,
            Self::Expr(_) => 10,
            Self::Vec(_) => 11,
            Self::Map(_) => 12,
            Self::Set(_) => 13,
            Self::Seq(_) => 14,
        }
    }
}

#[crabtime::function]
fn gen_partial_eq() {
    let types = [
        "Bool", "I32", "I64", "IBig", "F64", "Char", "Str", "Word", "Opt", "Res", "Vec", "Map",
        "Set", "Expr",
    ];
    let arms = types
        .map(|t| {
            crabtime::quote! {
                (Self::{{t}}(l0), Self::{{t}}(r0)) => l0 == r0,
            }
        })
        .join("");

    crabtime::output! {
        impl PartialEq for EuType<'_> {
            fn eq(&self, other: &Self) -> bool {
                match (self, other) {
                    {{arms}}
                    (Self::Seq(l0), Self::Seq(r0)) => l0.clone().eq(r0.clone()),
                    (a, b) if a.is_num() && b.is_num() => {
                        let (a, b) = a.clone().num_tower(b.clone()).unwrap();
                        a == b
                    }
                    _ => false,
                }
            }
        }
    }
}

gen_partial_eq!();

impl Eq for EuType<'_> {}

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

#[crabtime::function]
fn gen_ord() {
    use itertools::Itertools;

    let types = [
        "Bool", "I32", "I64", "IBig", "F64", "Char", "Str", "Word", "Opt", "Res", "Vec", "Map",
        "Set", "Expr",
    ];
    let arms = types
        .map(|t| {
            crabtime::quote! {
                (Self::{{t}}(l0), Self::{{t}}(r0)) => l0.cmp(r0),
            }
        })
        .join("");

    let nums = ["I32", "I64", "F64"];
    let arms_num = nums
        .iter()
        .permutations(2)
        .map(|ts| {
            let t0 = *ts[0];
            let t1 = *ts[1];
            let m = *nums.iter().find(|&&t| t == t0 || t == t1).unwrap();
            let n = if t0 == m { t1 } else { t0 };
            if t0.chars().next() == t1.chars().next() {
                crabtime::quote! {
                    (Self::{{t0}}(l0), Self::{{t1}}(r0)) => Self::{{n}}((*l0).as_()).cmp(&Self::{{n}}((*r0).as_())),
                }
            } else if t0 == n {
                let m = m.to_lowercase();
                let n = n.to_lowercase();
                crabtime::quote! {
                    (Self::{{t0}}(l0), Self::{{t1}}(r0)) => {
                        if l0.to_{{m}}().is_none() {
                            l0.cmp(&0.0.into())
                        } else {
                            let r0: OrderedFloat<{{n}}> = r0.to_{{n}}().unwrap().into();
                            l0.cmp(&r0)
                        }
                    }
                }
            } else {
                crabtime::quote! {
                    (l0 @ Self::{{t0}}(_), r0 @ Self::{{t1}}(_)) => r0.cmp(l0).reverse(),
                }
            }
        })
        .join("");

    let arms_ibig = nums
        .map(|t| {
            if t.chars().next() == Some('I') {
                crabtime::quote! {
                    (Self::IBig(l0), Self::{{t}}(r0)) => l0.cmp(&(*r0).into()),
                    (l0 @ Self::{{t}}(_), r0 @ Self::IBig(_)) => r0.cmp(l0).reverse(),
                }
            } else {
                let n = t.to_lowercase();
                crabtime::quote! {
                    (Self::{{t}}(l0), Self::IBig(r0)) => l0.cmp(&r0.to_{{n}}().value().into()),
                    (l0 @ Self::IBig(_), r0 @ Self::{{t}}(_)) => r0.cmp(l0).reverse(),
                }
            }
        })
        .join("");

    crabtime::output! {
        impl Ord for EuType<'_> {
            fn cmp(&self, other: &Self) -> Ordering {
                match (self, other) {
                    {{arms}}
                    (Self::Seq(l0), Self::Seq(r0)) => l0.clone().cmp(r0.clone()),
                    (Self::Bool(l0), _) => l0.cmp(&!l0),
                    (Self::Word(_), _) => Ordering::Greater,
                    (l0, r0 @ (Self::Bool(_) | Self::Word(_))) => r0.cmp(l0).reverse(),
                    {{arms_num}}
                    {{arms_ibig}}
                    (a, b) if a.is_num_like() && b.is_num_like() => {
                        let (a1, b1) = a.clone().num_tower(b.clone()).unwrap();
                        a1.cmp(&b1).then_with(|| a.eqv_ord(b))
                    }
                    (a, b) if a.is_vecz() || a.is_str() || a.is_expr() || b.is_vecz() || b.is_str() || b.is_expr() => {
                        a.clone().to_seq().cmp(b.clone().to_seq()).then_with(|| a.eqv_ord(b))
                    }
                    _ => unreachable!(),
                }
            }
        }
    }
}

gen_ord!();
