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

const NAME: &str = "sbx_crossover";

pub struct SimulatedBinaryCrossover {
    crossover_rate: Rate,
    contiguty: f32,
}

impl SimulatedBinaryCrossover {
    pub fn new(crossover_rate: impl Into<Rate>, contiguty: f32) -> Self {
        let crossover_rate = crossover_rate.into();
        if !crossover_rate.is_valid() {
            panic!("Rate {crossover_rate:?} is not valid. Must be between 0.0 and 1.0",);
        }
        Self {
            contiguty,
            crossover_rate,
        }
    }
}

impl<A, G, C> Crossover<C> for SimulatedBinaryCrossover
where
    A: Float,
    G: Gene<Allele = A> + BoundedGene,
    C: Chromosome<Gene = G>,
{
    fn name(&self) -> String {
        NAME.to_string()
    }

    fn rate(&self) -> Rate {
        self.crossover_rate.clone()
    }

    #[inline]
    fn cross_chromosomes(&self, chrom_one: &mut C, chrom_two: &mut C, _: f32) -> AlterResult {
        let length = std::cmp::min(chrom_one.len(), chrom_two.len());

        if length < 2 {
            return AlterResult::empty();
        }

        let mut count = 0;

        random_provider::with_rng(|rand| {
            let one_slice = chrom_one.as_mut_slice();
            let two_slice = chrom_two.as_slice();
            for i in 0..length {
                if rand.bool(0.5) {
                    let u = rand.random::<f32>();
                    let beta = A::from(if u <= 0.5 {
                        (2.0 * u).powf(1.0 / (self.contiguty + 1.0))
                    } else {
                        (0.5 / (1.0 - u)).powf(1.0 / (self.contiguty + 1.0))
                    })
                    .unwrap();

                    let v1 = one_slice[i].allele().clone();
                    let v2 = two_slice[i].allele().clone();

                    let v = if rand.bool(0.5) {
                        ((v1 - v2) * A::HALF) - (beta * A::HALF * (v1 - v2).abs())
                    } else {
                        ((v1 - v2) * A::HALF) + (beta * A::HALF * (v1 - v2).abs())
                    };

                    let (one_min, one_max) = one_slice[i].bounds();
                    let new_gene = v.clamp(*one_min, *one_max);

                    count += 1;

                    *one_slice[i].allele_mut() = new_gene;
                }
            }
        });

        AlterResult::from(count)
    }
}
