use std::ops::{
    Add,
    Div,
    Mul,
    Rem,
    Sub,
};

use num_traits::{
    FloatConst,
    Pow,
};
use ordered_float::{
    FloatCore,
    OrderedFloat,
};

use crate::{
    fns::{
        EuDef,
        macros::f_2_to_try_1,
    },
    types::EuType,
};

#[crabtime::function]
fn f64_f_1_to_1(name: String) {
    let f = name.to_lowercase();
    crabtime::output! {
        pub const {{name}}: EuDef = |env| {
            let a0 = env.arg("a0")?.try_f64()?;
            env.push(EuType::f64(a0.{{f}}()));
            Ok(())
        };
    }
}

#[crabtime::function]
fn f64_f_2_to_1(name: String) {
    let f = name.to_lowercase();
    crabtime::output! {
        pub const {{name}}: EuDef = |env| {
            let a1 = env.arg("a1")?.try_f64()?;
            let a0 = env.arg("a0")?.try_f64()?;
            env.push(EuType::f64(a0.{{f}}(a1)));
            Ok(())
        };
    }
}

#[crabtime::function]
fn f64_f_1_to_2(name: String) {
    let f = name.to_lowercase();
    crabtime::output! {
        pub const {{name}}: EuDef = |env| {
            let a0 = env.arg("a0")?.try_f64()?;
            let (b0, b1) = a0.{{f}}();
            env.push(EuType::f64(b0));
            env.push(EuType::f64(b1));
            Ok(())
        };
    }
}

pub const MIN_I32: EuDef = |env| {
    env.push(EuType::I32(i32::MIN));
    Ok(())
};

pub const MAX_I32: EuDef = |env| {
    env.push(EuType::I32(i32::MAX));
    Ok(())
};

pub const MIN_I64: EuDef = |env| {
    env.push(EuType::I64(i64::MIN));
    Ok(())
};

pub const MAX_I64: EuDef = |env| {
    env.push(EuType::I64(i64::MAX));
    Ok(())
};

pub const MIN_F64: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::min_value()));
    Ok(())
};

pub const MAX_F64: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::max_value()));
    Ok(())
};

pub const INF: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::infinity()));
    Ok(())
};

pub const NAN: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::nan()));
    Ok(())
};

pub const PI: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::PI()));
    Ok(())
};

pub const E: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::E()));
    Ok(())
};

pub const EPSILON: EuDef = |env| {
    env.push(EuType::F64(OrderedFloat::epsilon()));
    Ok(())
};

pub const TO_I32: EuDef = |env| {
    let a0 = env.arg("a0")?;
    env.push(EuType::opt(a0.to_i32().map(EuType::i32)));
    Ok(())
};

pub const TO_I64: EuDef = |env| {
    let a0 = env.arg("a0")?;
    env.push(EuType::opt(a0.to_i64().map(EuType::i64)));
    Ok(())
};

pub const TO_F64: EuDef = |env| {
    let a0 = env.arg("a0")?;
    env.push(EuType::opt(a0.to_f64().map(EuType::f64)));
    Ok(())
};

pub const TO_IBIG: EuDef = |env| {
    let a0 = env.arg("a0")?;
    env.push(EuType::opt(a0.to_ibig().map(EuType::ibig)));
    Ok(())
};

pub const NEG: EuDef = |env| {
    let a0 = env.arg("a0")?;
    env.push((-a0)?);
    Ok(())
};

f_2_to_try_1!(ADD);
f_2_to_try_1!(SUB);
f_2_to_try_1!(MUL);
f_2_to_try_1!(DIV);
f_2_to_try_1!(REM);
f_2_to_try_1!(POW);

pub const DIV_REM: EuDef = |env| {
    let a1 = env.arg("a1")?;
    let a0 = env.arg("a0")?;
    env.push((a0.clone() / a1.clone())?);
    env.push((a0 % a1)?);
    Ok(())
};

f64_f_1_to_1!(EXP);
f64_f_1_to_1!(EXP_M1);
f64_f_1_to_1!(SQRT);
f64_f_1_to_1!(CBRT);
f64_f_2_to_1!(HYPOT);
f64_f_2_to_1!(LOG);
f64_f_1_to_1!(LN);
f64_f_1_to_1!(LN_1P);
f64_f_1_to_2!(SIN_COS);
f64_f_1_to_1!(SIN);
f64_f_1_to_1!(COS);
f64_f_1_to_1!(TAN);
f64_f_1_to_1!(ASIN);
f64_f_1_to_1!(ACOS);
f64_f_1_to_1!(ATAN);
f64_f_2_to_1!(ATAN2);
f64_f_1_to_1!(SINH);
f64_f_1_to_1!(COSH);
f64_f_1_to_1!(TANH);
f64_f_1_to_1!(ASINH);
f64_f_1_to_1!(ACOSH);
f64_f_1_to_1!(ATANH);
