use std::rc::Rc;
use num_bigint::{BigInt, Sign};
use num_traits::ToPrimitive;
use rand::distributions::Uniform;
use rand::Rng;
use crate::interpreter::Interpreter;
use crate::primitives::PrimitiveFn;
use crate::universe::Universe;
use crate::value::Value;
use crate::{expect_args, reverse};
pub static INSTANCE_PRIMITIVES: &[(&str, PrimitiveFn, bool)] = &[
    ("<", self::lt, true),
    ("=", self::eq, true),
    ("+", self::plus, true),
    ("-", self::minus, true),
    ("*", self::times, true),
    ("/", self::divide, true),
    ("//", self::divide_float, true),
    ("%", self::modulo, true),
    ("rem:", self::remainder, true),
    ("&", self::bitand, true),
    ("<<", self::shift_left, true),
    (">>>", self::shift_right, true),
    ("bitXor:", self::bitxor, true),
    ("sqrt", self::sqrt, true),
    ("asString", self::as_string, true),
    ("asDouble", self::as_double, true),
    ("atRandom", self::at_random, true),
    ("as32BitSignedValue", self::as_32bit_signed_value, true),
    ("as32BitUnsignedValue", self::as_32bit_unsigned_value, true),
];
pub static CLASS_PRIMITIVES: &[(&str, PrimitiveFn, bool)] =
    &[("fromString:", self::from_string, true)];
macro_rules! demote {
    ($interpreter:expr, $expr:expr) => {{
        let value = $expr;
        match value.to_i64() {
            Some(value) => Value::Integer(value),
            None => Value::BigInteger(value),
        }
    }};
}
fn from_string(interpreter: &mut Interpreter, universe: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#fromString:";
    expect_args!(SIGNATURE, interpreter, [
        _,
        value => value,
    ]);
    let value = match value {
        Value::String(ref value) => value.as_str(),
        Value::Symbol(sym) => universe.lookup_symbol(sym),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    let parsed =
        (value.parse().map(Value::Integer)).or_else(|_| value.parse().map(Value::BigInteger));
    match parsed {
        Ok(parsed) => {
            interpreter.stack.push(parsed);
            return;
        }
        Err(err) => panic!("{}", err),
    }
}
fn as_string(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#asString";
    expect_args!(SIGNATURE, interpreter, [
        value => value,
    ]);
    let value = match value {
        Value::Integer(value) => value.to_string(),
        Value::BigInteger(value) => value.to_string(),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    {
        interpreter.stack.push(Value::String(Rc::new(value)));
        return;
    }
}
fn as_double(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#asDouble";
    expect_args!(SIGNATURE, interpreter, [
        value => value,
    ]);
    let value = match value {
        Value::Integer(value) => Value::Double(value as f64),
        Value::BigInteger(value) => match value.to_i64() {
            Some(value) => Value::Double(value as f64),
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn at_random(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#atRandom";
    expect_args!(SIGNATURE, interpreter, [
        value => value,
    ]);
    let chosen = match value {
        Value::Integer(value) => {
            let distribution = Uniform::new(0, value);
            let mut rng = rand::thread_rng();
            rng.sample(distribution)
        }
        Value::BigInteger(_) => panic!(
            "'{}': the range is too big to pick a random value from",
            SIGNATURE,
        ),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    {
        interpreter.stack.push(Value::Integer(chosen));
        return;
    }
}
fn as_32bit_signed_value(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#as32BitSignedValue";
    expect_args!(SIGNATURE, interpreter, [
        value => value,
    ]);
    let value = match value {
        Value::Integer(value) => value as i32 as i64,
        Value::BigInteger(value) => match value.to_u32_digits() {
            (Sign::Minus, values) => -(values[0] as i64),
            (Sign::Plus, values) | (Sign::NoSign, values) => values[0] as i64,
        },
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    {
        interpreter.stack.push(Value::Integer(value));
        return;
    }
}
fn as_32bit_unsigned_value(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#as32BitUnsignedValue";
    expect_args!(SIGNATURE, interpreter, [
        value => value,
    ]);
    let value = match value {
        Value::Integer(value) => value as u32 as i64,
        Value::BigInteger(value) => {
            let (_, values) = value.to_u32_digits();
            values[0] as i64
        }
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    {
        interpreter.stack.push(Value::Integer(value));
        return;
    }
}
fn plus(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#+";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => match a.checked_add(b) {
            Some(value) => Value::Integer(value),
            None => demote!(interpreter, BigInt::from(a) + BigInt::from(b)),
        },
        (Value::BigInteger(a), Value::BigInteger(b)) => demote!(interpreter, a + b),
        (Value::BigInteger(a), Value::Integer(b)) | (Value::Integer(b), Value::BigInteger(a)) => {
            demote!(interpreter, a + BigInt::from(b))
        }
        (Value::Double(a), Value::Double(b)) => Value::Double(a + b),
        (Value::Integer(a), Value::Double(b)) | (Value::Double(b), Value::Integer(a)) => {
            Value::Double((a as f64) + b)
        }
        (Value::BigInteger(a), Value::Double(b)) | (Value::Double(b), Value::BigInteger(a)) => {
            match a.to_f64() {
                Some(a) => Value::Double(a + b),
                None => panic!(
                    "'{}': `Integer` too big to be converted to `Double`",
                    SIGNATURE
                ),
            }
        }
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn minus(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#-";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => match a.checked_sub(b) {
            Some(value) => Value::Integer(value),
            None => demote!(interpreter, BigInt::from(a) - BigInt::from(b)),
        },
        (Value::BigInteger(a), Value::BigInteger(b)) => demote!(interpreter, a - b),
        (Value::BigInteger(a), Value::Integer(b)) => {
            demote!(interpreter, a - BigInt::from(b))
        }
        (Value::Integer(a), Value::BigInteger(b)) => {
            demote!(interpreter, BigInt::from(a) - b)
        }
        (Value::Double(a), Value::Double(b)) => Value::Double(a - b),
        (Value::Integer(a), Value::Double(b)) | (Value::Double(b), Value::Integer(a)) => {
            Value::Double((a as f64) - b)
        }
        (Value::BigInteger(a), Value::Double(b)) => match a.to_f64() {
            Some(a) => Value::Double(a - b),
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        (Value::Double(a), Value::BigInteger(b)) => match b.to_f64() {
            Some(b) => Value::Double(a - b),
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn times(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#*";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => match a.checked_mul(b) {
            Some(value) => Value::Integer(value),
            None => demote!(interpreter, BigInt::from(a) * BigInt::from(b)),
        },
        (Value::BigInteger(a), Value::BigInteger(b)) => demote!(interpreter, a * b),
        (Value::BigInteger(a), Value::Integer(b)) | (Value::Integer(b), Value::BigInteger(a)) => {
            demote!(interpreter, a * BigInt::from(b))
        }
        (Value::Double(a), Value::Double(b)) => Value::Double(a * b),
        (Value::Integer(a), Value::Double(b)) | (Value::Double(b), Value::Integer(a)) => {
            Value::Double((a as f64) * b)
        }
        (Value::BigInteger(a), Value::Double(b)) | (Value::Double(b), Value::BigInteger(a)) => {
            match a.to_f64() {
                Some(a) => Value::Double(a * b),
                None => panic!(
                    "'{}': `Integer` too big to be converted to `Double`",
                    SIGNATURE
                ),
            }
        }
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn divide(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#/";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => match a.checked_div(b) {
            Some(value) => Value::Integer(value),
            None => demote!(interpreter, BigInt::from(a) / BigInt::from(b)),
        },
        (Value::BigInteger(a), Value::BigInteger(b)) => demote!(interpreter, a / b),
        (Value::BigInteger(a), Value::Integer(b)) => {
            demote!(interpreter, a / BigInt::from(b))
        }
        (Value::Integer(a), Value::BigInteger(b)) => {
            demote!(interpreter, BigInt::from(a) / b)
        }
        (Value::Double(a), Value::Double(b)) => Value::Double(a / b),
        (Value::Integer(a), Value::Double(b)) | (Value::Double(b), Value::Integer(a)) => {
            Value::Double((a as f64) / b)
        }
        (Value::BigInteger(a), Value::Double(b)) => match a.to_f64() {
            Some(a) => Value::Double(a / b),
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        (Value::Double(a), Value::BigInteger(b)) => match b.to_f64() {
            Some(b) => Value::Double(a / b),
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn divide_float(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#//";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let a = match a {
        Value::Integer(a) => a as f64,
        Value::BigInteger(a) => match a.to_f64() {
            Some(a) => a,
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        Value::Double(a) => a,
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    let b = match b {
        Value::Integer(b) => b as f64,
        Value::BigInteger(b) => match b.to_f64() {
            Some(b) => b,
            None => panic!(
                "'{}': `Integer` too big to be converted to `Double`",
                SIGNATURE
            ),
        },
        Value::Double(b) => b,
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(Value::Double(a / b));
}
fn modulo(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#%";
    expect_args!(SIGNATURE, interpreter, [
        Value::Integer(a) => a,
        Value::Integer(b) => b,
    ]);
    let result = a % b;
    if result.signum() != b.signum() {
        interpreter.stack.push(Value::Integer((result + b) % b));
    } else {
        interpreter.stack.push(Value::Integer(result));
    }
}
fn remainder(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#rem:";
    expect_args!(SIGNATURE, interpreter, [
        Value::Integer(a) => a,
        Value::Integer(b) => b,
    ]);
    let result = a % b;
    if result.signum() != a.signum() {
        interpreter.stack.push(Value::Integer((result + a) % a));
    } else {
        interpreter.stack.push(Value::Integer(result));
    }
}
fn sqrt(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#sqrt";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
    ]);
    let value = match a {
        Value::Integer(a) => {
            let sqrt = (a as f64).sqrt();
            let trucated = sqrt.trunc();
            if sqrt == trucated {
                Value::Integer(trucated as i64)
            } else {
                Value::Double(sqrt)
            }
        }
        Value::BigInteger(a) => demote!(interpreter, a.sqrt()),
        Value::Double(a) => Value::Double(a.sqrt()),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn bitand(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#&";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => Value::Integer(a & b),
        (Value::BigInteger(a), Value::BigInteger(b)) => demote!(interpreter, a & b),
        (Value::BigInteger(a), Value::Integer(b)) | (Value::Integer(b), Value::BigInteger(a)) => {
            demote!(interpreter, a & BigInt::from(b))
        }
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn bitxor(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#bitXor:";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => Value::Integer(a ^ b),
        (Value::BigInteger(a), Value::BigInteger(b)) => demote!(interpreter, a ^ b),
        (Value::BigInteger(a), Value::Integer(b)) | (Value::Integer(b), Value::BigInteger(a)) => {
            demote!(interpreter, a ^ BigInt::from(b))
        }
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn lt(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#<";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => Value::Boolean(a < b),
        (Value::BigInteger(a), Value::BigInteger(b)) => Value::Boolean(a < b),
        (Value::Double(a), Value::Double(b)) => Value::Boolean(a < b),
        (Value::Integer(a), Value::Double(b)) => Value::Boolean((a as f64) < b),
        (Value::Double(a), Value::Integer(b)) => Value::Boolean(a < (b as f64)),
        (Value::BigInteger(a), Value::Integer(b)) => Value::Boolean(a < BigInt::from(b)),
        (Value::Integer(a), Value::BigInteger(b)) => Value::Boolean(BigInt::from(a) < b),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn eq(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#=";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        b => b,
    ]);
    let value = match (a, b) {
        (Value::Integer(a), Value::Integer(b)) => Value::Boolean(a == b),
        (Value::BigInteger(a), Value::BigInteger(b)) => Value::Boolean(a == b),
        (Value::Double(a), Value::Double(b)) => Value::Boolean(a == b),
        (Value::Integer(a), Value::Double(b)) => Value::Boolean((a as f64) == b),
        (Value::Double(a), Value::Integer(b)) => Value::Boolean(a == (b as f64)),
        _ => Value::Boolean(false),
    };
    interpreter.stack.push(value);
}
fn shift_left(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#<<";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        Value::Integer(b) => b,
    ]);
    let value = match a {
        Value::Integer(a) => match a.checked_shl(b as u32) {
            Some(value) => Value::Integer(value),
            None => demote!(interpreter, BigInt::from(a) << (b as usize)),
        },
        Value::BigInteger(a) => demote!(interpreter, a << (b as usize)),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
fn shift_right(interpreter: &mut Interpreter, _: &mut Universe) {
    const SIGNATURE: &str = "Integer>>#>>";
    expect_args!(SIGNATURE, interpreter, [
        a => a,
        Value::Integer(b) => b,
    ]);
    let value = match a {
        Value::Integer(a) => match a.checked_shr(b as u32) {
            Some(value) => Value::Integer(value),
            None => demote!(interpreter, BigInt::from(a) >> (b as usize)),
        },
        Value::BigInteger(a) => demote!(interpreter, a >> (b as usize)),
        _ => panic!("'{}': wrong types", SIGNATURE),
    };
    interpreter.stack.push(value);
}
pub fn get_instance_primitive(signature: &str) -> Option<PrimitiveFn> {
    INSTANCE_PRIMITIVES
        .iter()
        .find(|it| it.0 == signature)
        .map(|it| it.1)
}
pub fn get_class_primitive(signature: &str) -> Option<PrimitiveFn> {
    CLASS_PRIMITIVES
        .iter()
        .find(|it| it.0 == signature)
        .map(|it| it.1)
}