use num_bigint::BigInt;
use num_integer::Integer;
use smallvec::smallvec;
use crate::{
args::ArgValues,
bytecode::VM,
defer_drop,
exception_private::{ExcType, ExcTypeExt, RunResult, SimpleException},
heap::HeapData,
resource_checks::check_div_size,
types::{LongInt, allocate_tuple},
value::{Value, floor_divmod},
};
pub fn builtin_divmod(vm: &mut VM<'_>, args: ArgValues) -> RunResult<Value> {
let (a, b) = args.get_two_args("divmod", vm.heap)?;
let a = super::round::normalize_bool_to_int(a);
let b = super::round::normalize_bool_to_int(b);
defer_drop!(a, vm);
defer_drop!(b, vm);
match (a, b) {
(Value::Int(x), Value::Int(y)) => {
if *y == 0 {
Err(ExcType::divmod_by_zero())
} else if let Some((quot, rem)) = floor_divmod(*x, *y) {
Ok(allocate_tuple(smallvec![Value::Int(quot), Value::Int(rem)], vm.heap))
} else {
check_div_size(64, vm.heap.tracker())?;
let (quot, rem) = bigint_floor_divmod(&BigInt::from(*x), &BigInt::from(*y));
let quot_val = LongInt::new(quot).into_value(vm.heap);
let rem_val = LongInt::new(rem).into_value(vm.heap);
Ok(allocate_tuple(smallvec![quot_val, rem_val], vm.heap))
}
}
(Value::Int(x), Value::Ref(id)) if let HeapData::LongInt(li) = vm.heap.get(*id) => {
if li.is_zero() {
Err(ExcType::divmod_by_zero())
} else {
let x_bi = BigInt::from(*x);
let (quot, rem) = bigint_floor_divmod(&x_bi, li.inner());
let quot_val = LongInt::new(quot).into_value(vm.heap);
let rem_val = LongInt::new(rem).into_value(vm.heap);
Ok(allocate_tuple(smallvec![quot_val, rem_val], vm.heap))
}
}
(Value::Ref(id), Value::Int(y)) if let HeapData::LongInt(li) = vm.heap.get(*id) => {
if *y == 0 {
Err(ExcType::divmod_by_zero())
} else {
let y_bi = BigInt::from(*y);
let (quot, rem) = bigint_floor_divmod(li.inner(), &y_bi);
let quot_val = LongInt::new(quot).into_value(vm.heap);
let rem_val = LongInt::new(rem).into_value(vm.heap);
Ok(allocate_tuple(smallvec![quot_val, rem_val], vm.heap))
}
}
(Value::Ref(id1), Value::Ref(id2))
if let HeapData::LongInt(x_li) = vm.heap.get(*id1)
&& let HeapData::LongInt(y_li) = vm.heap.get(*id2) =>
{
if y_li.is_zero() {
Err(ExcType::divmod_by_zero())
} else {
let (quot, rem) = bigint_floor_divmod(x_li.inner(), y_li.inner());
let quot_val = LongInt::new(quot).into_value(vm.heap);
let rem_val = LongInt::new(rem).into_value(vm.heap);
Ok(allocate_tuple(smallvec![quot_val, rem_val], vm.heap))
}
}
(Value::Float(x), Value::Float(y)) => {
if *y == 0.0 {
Err(ExcType::divmod_by_zero())
} else {
let quot = (x / y).floor();
let rem = x - quot * y;
Ok(allocate_tuple(
smallvec![Value::Float(quot), Value::Float(rem)],
vm.heap,
))
}
}
(Value::Int(x), Value::Float(y)) => {
if *y == 0.0 {
Err(ExcType::divmod_by_zero())
} else {
let xf = *x as f64;
let quot = (xf / y).floor();
let rem = xf - quot * y;
Ok(allocate_tuple(
smallvec![Value::Float(quot), Value::Float(rem)],
vm.heap,
))
}
}
(Value::Float(x), Value::Int(y)) => {
if *y == 0 {
Err(ExcType::divmod_by_zero())
} else {
let yf = *y as f64;
let quot = (x / yf).floor();
let rem = x - quot * yf;
Ok(allocate_tuple(
smallvec![Value::Float(quot), Value::Float(rem)],
vm.heap,
))
}
}
_ => {
let a_type = a.py_type_name(vm);
let b_type = b.py_type_name(vm);
Err(SimpleException::new_msg(
ExcType::TypeError,
format!("unsupported operand type(s) for divmod(): '{a_type}' and '{b_type}'"),
)
.into())
}
}
}
fn bigint_floor_divmod(a: &BigInt, b: &BigInt) -> (BigInt, BigInt) {
a.div_mod_floor(b)
}