use crate::{
    core::{
        circuits::traits::curve_circuit::CurveCircuit,
        expressions::expr::EvalFailure,
        global_value::{curve_value::CurveValue, value::FieldValue},
    },
    utils::{curve_point::CurvePoint, field::ScalarField},
};
use curve25519_dalek_arcium_fork::traits::MultiscalarMul as DalekMultiscalarMul;
use primitives::algebra::elliptic_curve::{curve25519::Curve25519Ristretto, Curve};

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

impl CurveCircuit for MultiscalarMul {
    fn eval(
        &self,
        curve_points: Vec<CurvePoint>,
        scalars: Vec<ScalarField>,
    ) -> Result<Vec<CurvePoint>, EvalFailure> {
        if scalars.len() != curve_points.len() {
            return EvalFailure::err_ub("lengths don't match");
        }
        Ok(vec![CurvePoint::new(
            <Curve25519Ristretto as Curve>::Point::multiscalar_mul(
                scalars
                    .into_iter()
                    .map(|scalar| {
                        <Curve25519Ristretto as Curve>::Scalar::from_canonical_bytes(
                            scalar.to_le_bytes(),
                        )
                        .unwrap()
                    })
                    .collect::<Vec<<Curve25519Ristretto as Curve>::Scalar>>(),
                curve_points
                    .into_iter()
                    .map(|point| point.inner())
                    .collect::<Vec<<Curve25519Ristretto as Curve>::Point>>(),
            ),
        )])
    }

    fn run(
        &self,
        curve_vals: Vec<CurveValue>,
        scalar_vals: Vec<FieldValue<ScalarField>>,
    ) -> Vec<CurveValue> {
        vec![CurveValue::multiscalar_mul(scalar_vals, curve_vals)]
    }
}

#[cfg(test)]
mod tests {
    use crate::core::circuits::{
        curve::multiscalar_mul::MultiscalarMul,
        traits::curve_circuit::tests::TestedCurveCircuit,
    };

    impl TestedCurveCircuit for MultiscalarMul {
        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 {
            let mut n_points = 1;
            while rng.gen_bool(0.875) {
                n_points += 1;
            }
            n_points
        }

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

    #[test]
    fn test_multiscalar_mul() {
        MultiscalarMul::test(4, 4)
    }
}
