use radiate_core::{
    AlterResult, BoundedGene, Chromosome, Crossover, FloatGene, Gene, Rate, Valid, random_provider,
};
use radiate_utils::Float;

/// Intermediate Crossover. This crossover method takes two chromosomes and crosses them
/// by taking a weighted average of the two alleles. The weight is determined by the `alpha`
/// parameter. The new allele is calculated as:
/// ```text
/// a = a1 * alpha + a2 * (1 - alpha)
/// ```
/// where `a` is the new allele, `a1` is the allele from the first chromosome, `a2` is the allele
/// from the second chromosome, and `alpha` is a value between 0 and 1.
#[derive(Clone, Debug)]
pub struct IntermediateCrossover {
    rate: Rate,
    alpha: f32,
}

impl IntermediateCrossover {
    /// Create a new instance of the `IntermediateCrossover` with the given rate and alpha.
    /// The rate must be between 0.0 and 1.0, and the alpha must be between 0.0 and 1.0.
    pub fn new(rate: impl Into<Rate>, alpha: f32) -> Self {
        let rate = rate.into();

        if !rate.is_valid() {
            panic!("Rate {rate:?} is not valid. Must be between 0.0 and 1.0",);
        }

        if !(0.0..=1.0).contains(&alpha) {
            panic!("Alpha must be between 0 and 1");
        }

        IntermediateCrossover { rate, alpha }
    }
}

impl<F, C> Crossover<C> for IntermediateCrossover
where
    F: Float,
    C: Chromosome<Gene = FloatGene<F>>,
{
    fn rate(&self) -> Rate {
        self.rate.clone()
    }

    #[inline]
    fn cross_chromosomes(&self, chrom_one: &mut C, chrom_two: &mut C, rate: f32) -> AlterResult {
        let mut cross_count = 0;
        let alpha = F::from(self.alpha).unwrap();

        random_provider::with_rng(|rand| {
            for i in 0..std::cmp::min(chrom_one.len(), chrom_two.len()) {
                if rand.bool(rate) {
                    let gene_one = chrom_one.get_mut(i);
                    let gene_two = chrom_two.get_mut(i);

                    let allele_one = gene_one.allele().clone();
                    let allele_two = gene_two.allele().clone();

                    let alpha = rand.range(F::ZERO..alpha);
                    let new_allele_one = allele_one * alpha + allele_two * (F::ONE - alpha);
                    let new_allele_two = allele_two * alpha + allele_one * (F::ONE - alpha);

                    let (one_min, one_max) = gene_one.bounds();
                    let (two_min, two_max) = gene_two.bounds();

                    *gene_one.allele_mut() = new_allele_one.clamp(*one_min, *one_max);
                    *gene_two.allele_mut() = new_allele_two.clamp(*two_min, *two_max);

                    cross_count += 1;
                }
            }
        });

        cross_count.into()
    }
}
