use crate::{
    core::{
        actually_used_field::ActuallyUsedField,
        bounds::FieldBounds,
        circuits::{
            boolean::boolean_value::BooleanValue,
            traits::arithmetic_circuit::ArithmeticCircuit,
        },
        expressions::expr::EvalFailure,
        global_value::value::FieldValue,
    },
    traits::{FromLeBits, GetBit},
    utils::{number::Number, used_field::UsedField},
};
use num_bigint::BigInt;

#[derive(Debug, Clone)]
pub struct BitwiseAnd {
    signed: bool,
}

impl BitwiseAnd {
    pub const fn new(signed: bool) -> Self {
        Self { signed }
    }
}

impl BitwiseAnd {
    pub fn bitwise_and<F: ActuallyUsedField>(
        lhs: FieldValue<F>,
        rhs: FieldValue<F>,
        signed: bool,
    ) -> FieldValue<F> {
        let union_bounds = lhs.bounds().union(rhs.bounds());
        let circuit_size = union_bounds.bin_size(signed);
        let lhs_bits = (0..circuit_size)
            .map(|i| lhs.get_bit(i, signed))
            .collect::<Vec<BooleanValue>>();
        let rhs_bits = (0..circuit_size)
            .map(|i| rhs.get_bit(i, signed))
            .collect::<Vec<BooleanValue>>();
        let res_bits = lhs_bits
            .into_iter()
            .zip(rhs_bits)
            .map(|(lhs_bit, rhs_bit)| lhs_bit & rhs_bit)
            .collect::<Vec<BooleanValue>>();

        FieldValue::<F>::from_le_bits(res_bits, signed)
    }
}

impl<F: UsedField> ArithmeticCircuit<F> for BitwiseAnd {
    fn eval(&self, x: Vec<F>) -> Result<Vec<F>, EvalFailure> {
        assert_eq!(x.len(), 2);
        let x0 = x[0];
        let x1 = x[1];
        let v = if self.signed {
            let x0 = BigInt::from(x0.to_signed_number());
            let x1 = BigInt::from(x1.to_signed_number());
            Number::from(x0 & x1).into()
        } else {
            let x0 = BigInt::from(x0.to_unsigned_number());
            let x1 = BigInt::from(x1.to_unsigned_number());
            Number::from(x0 & x1).into()
        };
        Ok(vec![v])
    }

    fn bounds(&self, bounds: Vec<FieldBounds<F>>) -> Vec<FieldBounds<F>> {
        let max_size = if self.signed {
            bounds
                .into_iter()
                .map(|bound| bound.bin_size(self.signed))
                .max()
                .unwrap()
        } else {
            bounds
                .into_iter()
                .map(|bound| bound.bin_size(self.signed))
                .min()
                .unwrap()
        };
        let res = if max_size >= F::CAPACITY as usize {
            FieldBounds::All
        } else if self.signed && max_size > 0 {
            let pot = F::power_of_two(max_size - 1);
            FieldBounds::new(-pot, pot - F::ONE)
        } else {
            FieldBounds::new(F::ZERO, F::power_of_two(max_size) - F::ONE)
        };
        vec![res]
    }

    fn run(&self, vals: Vec<FieldValue<F>>) -> Vec<FieldValue<F>>
    where
        F: ActuallyUsedField,
    {
        let val0 = vals[0];
        let val1 = vals[1];
        let res = Self::bitwise_and(val0, val1, self.signed);
        vec![res]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        core::circuits::traits::arithmetic_circuit::tests::TestedArithmeticCircuit,
        ArcisField,
    };
    use rand::Rng;

    impl TestedArithmeticCircuit<ArcisField> for BitwiseAnd {
        fn gen_desc<R: Rng + ?Sized>(rng: &mut R) -> Self {
            Self::new(rng.gen())
        }

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