#[cfg(test)]
mod tests {
    use crate::{
        core::{
            bounds::FieldBounds,
            circuits::{
                boolean::byte::Byte,
                traits::{
                    arithmetic_circuit::{tests::TestedArithmeticCircuit, ArithmeticCircuit},
                    curve_circuit::{tests::TestedCurveCircuit, CurveCircuit},
                },
            },
            expressions::expr::EvalFailure,
            global_value::{curve_value::CurveValue, value::FieldValue},
        },
        utils::{
            curve_point::CurvePoint,
            elliptic_curve::{AffineEdwardsPoint, ProjectiveEdwardsPoint, EIGHT_INV_MOD_ELL},
            field::{BaseField, ScalarField},
        },
    };
    use rand::{thread_rng, Rng};

    #[derive(Debug, Clone)]
    struct ConversionFromCurveValue;

    impl CurveCircuit for ConversionFromCurveValue {
        fn eval(
            &self,
            curve_points: Vec<CurvePoint>,
            _scalars: Vec<ScalarField>,
        ) -> Result<Vec<CurvePoint>, EvalFailure> {
            assert!(curve_points.len() == 1);
            Ok(curve_points)
        }

        fn run(
            &self,
            curve_vals: Vec<CurveValue>,
            _scalar_vals: Vec<FieldValue<ScalarField>>,
        ) -> Vec<CurveValue> {
            curve_vals
                .into_iter()
                .map(|point| CurveValue::from_projective(point.to_projective()))
                .collect::<Vec<CurveValue>>()
        }
    }

    /// Conversion ProjectiveEdwardsPoint -> CurveValue -> AffineEdwardsPoint.
    /// If self.test_infinity = true the double conversion returns the affine identity.
    /// Otherwise, if the affine input y-coordinate belongs to a point on the curve the 8-torsion
    /// component is cleared. If it does not belong to a point on the curve the double conversion
    /// returns the affine identity.
    #[derive(Debug, Clone)]
    struct ConversionFromProjectiveEdwardsPoint {
        test_infinity: bool,
    }

    impl ArithmeticCircuit<BaseField> for ConversionFromProjectiveEdwardsPoint {
        fn eval(&self, x: Vec<BaseField>) -> Result<Vec<BaseField>, EvalFailure> {
            if self.test_infinity {
                Ok(vec![
                    AffineEdwardsPoint::identity().x,
                    AffineEdwardsPoint::identity().y,
                ])
            } else {
                let y = x[1];
                let (is_on_curve, affine_point) = AffineEdwardsPoint::try_from_y(y);

                if is_on_curve {
                    // clear the 8-torsion component by applying the map [8^-1 mod ell] \circ [8]
                    let cleared = affine_point
                        .to_projective()
                        .mul_bits(vec![false, false, false, true])
                        .mul_bits(
                            EIGHT_INV_MOD_ELL
                                .into_iter()
                                .flat_map(|byte| Byte::<bool>::from(byte).to_vec())
                                .collect::<Vec<bool>>(),
                        )
                        .to_affine();
                    Ok(vec![cleared.x, cleared.y])
                } else {
                    Ok(vec![
                        AffineEdwardsPoint::identity().x,
                        AffineEdwardsPoint::identity().y,
                    ])
                }
            }
        }

        fn bounds(&self, _bounds: Vec<FieldBounds<BaseField>>) -> Vec<FieldBounds<BaseField>> {
            vec![FieldBounds::All; 2]
        }

        #[allow(non_snake_case)]
        fn run(&self, vals: Vec<FieldValue<BaseField>>) -> Vec<FieldValue<BaseField>> {
            let point = if self.test_infinity {
                let mut rng = thread_rng();
                let X = vals[0];
                let Y = vals[1];
                let point_at_infinity = if rng.gen_bool(0.5) {
                    // This is one of the two points at infinity on the Edwards curve in projective
                    // coordinates. Recall that it is a singular point.
                    ProjectiveEdwardsPoint::new(
                        (FieldValue::from(0), Y, FieldValue::from(0)),
                        false,
                        false,
                    )
                } else {
                    // Point at infinity (that is very likely not on the curve).
                    ProjectiveEdwardsPoint::new((X, Y, FieldValue::from(0)), false, false)
                };
                CurveValue::from_projective(point_at_infinity)
            } else {
                let y = vals[1];
                let (_, affine_point) = AffineEdwardsPoint::try_from_y(y);
                CurveValue::from_affine(affine_point)
            };
            let converted = point.to_affine();

            vec![converted.x, converted.y]
        }
    }

    impl TestedCurveCircuit for ConversionFromCurveValue {
        fn gen_desc<R: rand::Rng + ?Sized>(_rng: &mut R) -> Self {
            Self
        }

        fn gen_n_points<R: rand::Rng + ?Sized>(&self, _rng: &mut R) -> usize {
            1
        }

        fn gen_n_scalars<R: rand::Rng + ?Sized>(&self, _rng: &mut R, _n_points: usize) -> usize {
            0
        }
    }

    impl TestedArithmeticCircuit<BaseField> for ConversionFromProjectiveEdwardsPoint {
        fn gen_desc<R: rand::Rng + ?Sized>(rng: &mut R) -> Self {
            Self {
                test_infinity: rng.gen_bool(0.5),
            }
        }

        fn gen_n_inputs<R: rand::Rng + ?Sized>(&self, _rng: &mut R) -> usize {
            2
        }
    }

    #[test]
    fn test_conversion_from_curve_value() {
        ConversionFromCurveValue::test(1, 4)
    }

    #[test]
    fn test_conversion_from_projective_edwards_point() {
        ConversionFromProjectiveEdwardsPoint::test(1, 4)
    }
}
