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

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

impl Min {
    pub const fn new(signed: bool) -> Self {
        Self { signed }
    }
    fn empty_value<F: UsedField>(&self) -> F {
        if self.signed {
            F::TWO_INV - F::ONE
        } else {
            F::ZERO - F::ONE
        }
    }
    fn min<F: UsedField>(&self, x: Vec<F>) -> F {
        x.into_iter()
            .fold(self.empty_value(), |x1, x2| x1.min(x2, self.signed))
    }
}

impl<F: UsedField> ArithmeticCircuit<F> for Min {
    fn eval(&self, x: Vec<F>) -> Result<Vec<F>, EvalFailure> {
        let res = self.min(x);
        Ok(vec![res])
    }

    fn bounds(&self, bounds: Vec<FieldBounds<F>>) -> Vec<FieldBounds<F>> {
        let (min, max) = bounds
            .iter()
            .map(|bounds| bounds.min_and_max(self.signed))
            .unzip();
        let min = self.min(min);
        let max = self.min(max);
        vec![FieldBounds::new(min, max)]
    }

    fn run(&self, vals: Vec<FieldValue<F>>) -> Vec<FieldValue<F>>
    where
        F: ActuallyUsedField,
    {
        let res = if self.signed {
            vals.into_iter()
                .reduce(|a, b| a.signed_gt(b).select(b, a))
                .unwrap()
        } else {
            vals.into_iter()
                .reduce(|a, b| a.le(b).select(a, b))
                .unwrap()
        };
        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 Min {
        fn gen_desc<R: Rng + ?Sized>(rng: &mut R) -> Self {
            Min::new(rng.r#gen())
        }

        fn gen_n_inputs<R: Rng + ?Sized>(&self, rng: &mut R) -> usize {
            let mut n_inputs = 1;
            while rng.gen_bool(0.875) {
                n_inputs += 1
            }
            n_inputs
        }
    }
    #[test]
    fn tested() {
        Min::test(64, 4)
    }
}
