use crate::{
    core::{
        actually_used_field::ActuallyUsedField,
        bounds::{FieldBounds, IsBounds},
        circuits::traits::arithmetic_circuit::ArithmeticCircuit,
        expressions::expr::EvalFailure,
        global_value::value::FieldValue,
    },
    utils::used_field::UsedField,
};
use num_traits::Signed;

#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct SignedDivide;

impl SignedDivide {
    #[allow(dead_code)]
    pub fn checked_run<F: ActuallyUsedField>(
        a: FieldValue<F>,
        b: FieldValue<F>,
    ) -> Result<[FieldValue<F>; 2], &'static str> {
        let b_cst = b.as_constant();
        if b_cst == Some(F::ZERO) {
            Err("Division by zero.")
        } else {
            let res = SignedDivide.run(vec![a, b]);
            Ok([res[0], res[1]])
        }
    }
}

#[allow(dead_code)]
fn div_bounds<F: UsedField>(b1: FieldBounds<F>, b2: FieldBounds<F>) -> FieldBounds<F> {
    let (min1, max1) = b1.to_signed_number_pair();
    let (min2, max2) = b2.to_signed_number_pair();
    if min2 == 0 && max2 == 0 {
        b2
    } else {
        let (min, max) = if min2 >= 0 {
            (
                // the lhs accounts for the case min1 > 0 and the rhs for the case min1 < 0
                (min1.clone() / max2.clone()).min(min1 / min2.clone().max(1.into())),
                // the lhs accounts for the case max1 > 0 and the rhs for the case max1 < 0
                (max1.clone() / min2.clone().max(1.into())).max(max1 / max2.clone()),
            )
        } else if max2 <= 0 {
            (
                // the lhs accounts for the case max1 > 0 and the rhs for the case max1 < 0
                (max1.clone() / max2.clone().min((-1).into())).min(max1 / min2.clone()),
                // the lhs accounts for the case min1 > 0 and the rhs for the case min1 < 0
                (min1.clone() / min2.clone()).max(min1 / max2.clone().min((-1).into())),
            )
        } else {
            (
                // [min2, max2] contains both -1 and 1
                -(max1.abs().max(min1.abs())),
                max1.abs().max(min1.abs()),
            )
        };
        FieldBounds::new(min.into(), max.into())
    }
}

#[allow(dead_code)]
fn rem_bounds<F: UsedField>(b1: FieldBounds<F>, b2: FieldBounds<F>) -> FieldBounds<F> {
    if let (Some(c1), Some(c2)) = (b1.as_constant(), b2.as_constant()) {
        if c2 == F::ZERO {
            return FieldBounds::from(F::ZERO);
        }
        let res: F = (c1.to_signed_number() % c2.to_signed_number()).into();
        FieldBounds::from(res)
    } else {
        let max_r = b2.max_abs();
        if max_r == F::ZERO {
            FieldBounds::from(F::ZERO)
        } else {
            FieldBounds::new(-max_r + F::ONE, max_r - F::ONE)
        }
    }
}

impl<F: UsedField> ArithmeticCircuit<F> for SignedDivide {
    fn eval(&self, x: Vec<F>) -> Result<Vec<F>, EvalFailure> {
        assert_eq!(x.len(), 2);
        let a = x[0];
        let b = x[1];
        if b == F::ZERO {
            EvalFailure::err_ub("division by zero")?;
        }
        let q = a.signed_euclidean_division(b);
        let r = a - b * q;
        Ok(vec![q, r])
    }

    fn bounds(&self, bounds: Vec<FieldBounds<F>>) -> Vec<FieldBounds<F>> {
        let a_bounds = bounds[0];
        let b_bounds = bounds[1];
        vec![
            div_bounds(a_bounds, b_bounds),
            rem_bounds(a_bounds, b_bounds),
        ]
    }

    fn run(&self, vals: Vec<FieldValue<F>>) -> Vec<FieldValue<F>>
    where
        F: ActuallyUsedField,
    {
        let a = vals[0];
        let b = vals[1];
        let abs_div = a.abs() / b.abs();
        let a_sign = a.sign();
        let b_sign = b.sign();
        let abs_rem = a.abs() % b.abs();
        vec![abs_div * a_sign * b_sign, abs_rem * a_sign]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        core::circuits::traits::arithmetic_circuit::tests::TestedArithmeticCircuit,
        utils::field::ScalarField,
    };
    use rand::Rng;
    use std::marker::PhantomData;
    impl<F: ActuallyUsedField> TestedArithmeticCircuit<F> for SignedDivide {
        fn gen_desc<R: Rng + ?Sized>(_rng: &mut R) -> Self {
            SignedDivide
        }

        fn gen_n_inputs<R: Rng + ?Sized>(&self, _rng: &mut R) -> usize {
            2
        }
    }
    #[test]
    fn tested() {
        SignedDivide::test_with_marker(64, 16, PhantomData::<ScalarField>)
    }
}
