#![allow(non_snake_case)]

use crate::plugin::*;
use crate::stdlib::{format, string::String};
use crate::{def_package, EvalAltResult, Position, INT};

#[cfg(not(feature = "no_float"))]
use crate::FLOAT;

#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
use num_traits::float::Float;

#[inline(always)]
pub fn make_err(msg: impl Into<String>) -> Box<EvalAltResult> {
    EvalAltResult::ErrorArithmetic(msg.into(), Position::NONE).into()
}

macro_rules! gen_arithmetic_functions {
    ($root:ident => $($arg_type:ident),+) => {
        pub mod $root { $(pub mod $arg_type {
            use super::super::*;

            #[export_module]
            pub mod functions {
                #[rhai_fn(name = "+", return_raw)]
                pub fn add(x: $arg_type, y: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))).map(Dynamic::from)
                    } else {
                        Ok(Dynamic::from(x + y))
                    }
                }
                #[rhai_fn(name = "-", return_raw)]
                pub fn subtract(x: $arg_type, y: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))).map(Dynamic::from)
                    } else {
                        Ok(Dynamic::from(x - y))
                    }
                }
                #[rhai_fn(name = "*", return_raw)]
                pub fn multiply(x: $arg_type, y: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))).map(Dynamic::from)
                    } else {
                        Ok(Dynamic::from(x * y))
                    }
                }
                #[rhai_fn(name = "/", return_raw)]
                pub fn divide(x: $arg_type, y: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        // Detect division by zero
                        if y == 0 {
                            Err(make_err(format!("Division by zero: {} / {}", x, y)))
                        } else {
                            x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))).map(Dynamic::from)
                        }
                    } else {
                        Ok(Dynamic::from(x / y))
                    }
                }
                #[rhai_fn(name = "%", return_raw)]
                pub fn modulo(x: $arg_type, y: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))).map(Dynamic::from)
                    } else {
                        Ok(Dynamic::from(x % y))
                    }
                }
                #[rhai_fn(name = "~", return_raw)]
                pub fn power(x: INT, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
                            Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y)))
                        } else if y < 0 {
                            Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y)))
                        } else {
                            x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Power overflow: {} ~ {}", x, y))).map(Dynamic::from)
                        }
                    } else {
                        Ok(Dynamic::from(x.pow(y as u32)))
                    }
                }

                #[rhai_fn(name = "<<", return_raw)]
                pub fn shift_left(x: $arg_type, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
                            Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y)))
                        } else if y < 0 {
                            Err(make_err(format!("Left-shift by a negative number: {} << {}", x, y)))
                        } else {
                            x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {} << {}", x, y))).map(Dynamic::from)
                        }
                    } else {
                        Ok(Dynamic::from(x << y))
                    }
                }
                #[rhai_fn(name = ">>", return_raw)]
                pub fn shift_right(x: $arg_type, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
                            Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y)))
                        } else if y < 0 {
                            Err(make_err(format!("Right-shift by a negative number: {} >> {}", x, y)))
                        } else {
                            x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {} >> {}", x, y))).map(Dynamic::from)
                        }
                    } else {
                        Ok(Dynamic::from(x >> y))
                    }
                }
                #[rhai_fn(name = "&")]
                pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type {
                    x & y
                }
                #[rhai_fn(name = "|")]
                pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type {
                    x | y
                }
                #[rhai_fn(name = "^")]
                pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type {
                    x ^ y
                }
            }
        })* }
    }
}

macro_rules! gen_signed_functions {
    ($root:ident => $($arg_type:ident),+) => {
        pub mod $root { $(pub mod $arg_type {
            use super::super::*;

            #[export_module]
            pub mod functions {
                #[rhai_fn(name = "-", return_raw)]
                pub fn neg(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from)
                    } else {
                        Ok(Dynamic::from(-x))
                    }
                }
                #[rhai_fn(name = "+")]
                pub fn plus(x: $arg_type) -> $arg_type {
                    x
                }
                #[rhai_fn(return_raw)]
                pub fn abs(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
                    if cfg!(not(feature = "unchecked")) {
                        x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from)
                    } else {
                        Ok(Dynamic::from(x.abs()))
                    }
                }
                pub fn sign(x: $arg_type) -> INT {
                    if x == 0 {
                        0
                    } else if x < 0 {
                        -1
                    } else {
                        1
                    }
                }
            }
        })* }
    }
}

macro_rules! reg_functions {
    ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $(
        combine_with_exported_module!($mod_name, "arithmetic", $root::$arg_type::functions);
    )* }
}

def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
    reg_functions!(lib += signed_basic; INT);

    #[cfg(not(feature = "only_i32"))]
    #[cfg(not(feature = "only_i64"))]
    {
        reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64);
        reg_functions!(lib += signed_numbers; i8, i16, i32);

        #[cfg(not(target_arch = "wasm32"))]
        {
            reg_functions!(lib += arith_num_128; i128, u128);
            reg_functions!(lib += signed_num_128; i128);
        }
    }

    // Basic arithmetic for floating-point
    #[cfg(not(feature = "no_float"))]
    {
        combine_with_exported_module!(lib, "f32", f32_functions);
        combine_with_exported_module!(lib, "f64", f64_functions);
    }
});

gen_arithmetic_functions!(arith_basic => INT);

#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64);

#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))]
gen_arithmetic_functions!(arith_num_128 => i128, u128);

gen_signed_functions!(signed_basic => INT);

#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
gen_signed_functions!(signed_numbers => i8, i16, i32);

#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))]
gen_signed_functions!(signed_num_128 => i128);

#[cfg(not(feature = "no_float"))]
#[export_module]
mod f32_functions {
    #[cfg(not(feature = "f32_float"))]
    pub mod basic_arithmetic {
        #[rhai_fn(name = "+")]
        pub fn add(x: f32, y: f32) -> f32 {
            x + y
        }
        #[rhai_fn(name = "-")]
        pub fn subtract(x: f32, y: f32) -> f32 {
            x - y
        }
        #[rhai_fn(name = "*")]
        pub fn multiply(x: f32, y: f32) -> f32 {
            x * y
        }
        #[rhai_fn(name = "/")]
        pub fn divide(x: f32, y: f32) -> f32 {
            x / y
        }
        #[rhai_fn(name = "%")]
        pub fn modulo(x: f32, y: f32) -> f32 {
            x % y
        }
        #[rhai_fn(name = "~", return_raw)]
        pub fn pow_f_f(x: f32, y: f32) -> Result<Dynamic, Box<EvalAltResult>> {
            Ok(Dynamic::from(x.powf(y)))
        }
    }
    #[rhai_fn(name = "-")]
    pub fn neg(x: f32) -> f32 {
        -x
    }
    #[rhai_fn(name = "+")]
    pub fn plus(x: f32) -> f32 {
        -x
    }
    pub fn abs(x: f32) -> f32 {
        x.abs()
    }
    pub fn sign(x: f32) -> INT {
        if x == 0.0 {
            0
        } else if x < 0.0 {
            -1
        } else {
            1
        }
    }
    #[rhai_fn(name = "~", return_raw)]
    pub fn pow_f_i(x: f32, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
        if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
            Err(make_err(format!(
                "Number raised to too large an index: {} ~ {}",
                x, y
            )))
        } else {
            Ok(Dynamic::from(x.powi(y as i32)))
        }
    }
}

#[cfg(not(feature = "no_float"))]
#[export_module]
mod f64_functions {
    #[cfg(feature = "f32_float")]
    pub mod basic_arithmetic {
        #[rhai_fn(name = "+")]
        pub fn add(x: f64, y: f64) -> f64 {
            x + y
        }
        #[rhai_fn(name = "-")]
        pub fn subtract(x: f64, y: f64) -> f64 {
            x - y
        }
        #[rhai_fn(name = "*")]
        pub fn multiply(x: f64, y: f64) -> f64 {
            x * y
        }
        #[rhai_fn(name = "/")]
        pub fn divide(x: f64, y: f64) -> f64 {
            x / y
        }
        #[rhai_fn(name = "%")]
        pub fn modulo(x: f64, y: f64) -> f64 {
            x % y
        }
        #[rhai_fn(name = "~", return_raw)]
        pub fn pow_f_f(x: f64, y: f64) -> Result<Dynamic, Box<EvalAltResult>> {
            Ok(Dynamic::from(x.powf(y)))
        }
    }
    #[rhai_fn(name = "-")]
    pub fn neg(x: f64) -> f64 {
        -x
    }
    #[rhai_fn(name = "+")]
    pub fn plus(x: f64) -> f64 {
        -x
    }
    pub fn abs(x: f64) -> f64 {
        x.abs()
    }
    pub fn sign(x: f64) -> INT {
        if x == 0.0 {
            0
        } else if x < 0.0 {
            -1
        } else {
            1
        }
    }
    #[rhai_fn(name = "~", return_raw)]
    pub fn pow_f_i(x: FLOAT, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
        if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
            Err(make_err(format!(
                "Number raised to too large an index: {} ~ {}",
                x, y
            )))
        } else {
            Ok(x.powi(y as i32).into())
        }
    }
}
