use crate::channel::Channel;
use crate::code::LdpcCode;
use crate::construction::random::generate_random_h_for_k;
use crate::decoder::bit_flip::BitFlipDecoder;
use crate::encoder::dense::DenseEncoder;
use rand::{Rng, RngExt};
use rayon::prelude::*;
use std::fs::File;
use std::io::Write;

#[derive(Debug, Clone, Copy)]
pub enum DecoderAlgorithm {
    BitFlipping,
}

#[derive(Clone)]
pub struct AnalysisConfig {
    pub k_values: Vec<usize>,
    pub wc_values: Vec<usize>,
    pub wr_values: Vec<usize>,
    pub min_error_prob: f64,
    pub max_error_prob: f64,
    pub steps: usize,
    pub frames_per_step: usize,
    pub max_iter: usize,
    pub algorithm: DecoderAlgorithm,
}

#[derive(Debug, Clone)]
pub struct SimulationPoint {
    pub k_config: usize,
    pub n_real: usize,
    pub wc: usize,
    pub wr: usize,
    pub rate: f64,
    pub error_rate: f64,
    pub ber: f64,
    pub fer: f64,
    pub avg_iter: f64,
}

pub struct Analyzer;

impl Analyzer {
    pub fn run_batch(config: AnalysisConfig) -> Vec<SimulationPoint> {
        let mut tasks = Vec::new();

        for &k in &config.k_values {
            for &wc in &config.wc_values {
                for &wr in &config.wr_values {
                    if wr <= wc {
                        continue;
                    }
                    if (k * wr) % (wr - wc) != 0 {
                        continue;
                    }
                    tasks.push((k, wc, wr));
                }
            }
        }

        println!("Configurations valides : {}", tasks.len());
        println!("Execution sur {} coeurs...", rayon::current_num_threads());

        let results: Vec<SimulationPoint> = tasks
            .par_iter()
            .flat_map(|&(k_target, wc, wr)| {
                let h_matrix = generate_random_h_for_k(k_target, wc, wr);
                let ldpc = LdpcCode::new(h_matrix);
                let encoder = DenseEncoder::new(&ldpc);

                let k_real = encoder.k;
                let n_real = encoder.n;
                let rate_theoretical = k_real as f64 / n_real as f64;

                let step_size = if config.steps > 1 {
                    (config.max_error_prob - config.min_error_prob) / (config.steps as f64 - 1.0)
                } else {
                    0.0
                };

                (0..config.steps)
                    .into_par_iter()
                    .map(|i| {
                        let p = config.min_error_prob + (i as f64 * step_size);

                        let point = Self::simulate_point(
                            &encoder,
                            &ldpc,
                            p,
                            &config,
                            k_real,
                            k_target,
                            n_real,
                            wc,
                            wr,
                            rate_theoretical,
                        );

                        println!(
                            "Termine : k={} wc={} wr={} p={:.3} -> BER={:.5}",
                            point.k_config, point.wc, point.wr, point.error_rate, point.ber
                        );

                        point
                    })
                    .collect::<Vec<SimulationPoint>>()
            })
            .collect();

        println!("Simulation terminee. {} points generes.", results.len());
        results
    }

    fn simulate_point(
        encoder: &DenseEncoder,
        ldpc: &LdpcCode,
        p: f64,
        config: &AnalysisConfig,
        k_real: usize,
        k_orig: usize,
        n: usize,
        wc: usize,
        wr: usize,
        rate: f64,
    ) -> SimulationPoint {
        let channel = Channel::new(p);
        let mut rng = rand::rng();

        let mut total_bit_errors = 0;
        let mut total_frame_errors = 0;
        let mut total_iterations = 0;
        let mut converged_count = 0;

        for _ in 0..config.frames_per_step {
            let message: Vec<u8> = (0..k_real).map(|_| rng.random_range(0..2)).collect();
            let codeword = encoder.encode(&message);
            let received = channel.add_noise(&codeword);

            let (decoded, iters) = match config.algorithm {
                DecoderAlgorithm::BitFlipping => {
                    let decoder = BitFlipDecoder::new(ldpc);
                    let res = decoder.decode(&received, config.max_iter);
                    (res, 0)
                }
            };

            match decoded {
                Some(res_code) => {
                    if res_code != codeword {
                        total_frame_errors += 1;
                        total_bit_errors += Channel::count_errors(&codeword, &res_code);
                    } else {
                        converged_count += 1;
                        total_iterations += iters;
                    }
                }
                None => {
                    total_frame_errors += 1;
                    total_bit_errors += Channel::count_errors(&codeword, &received);
                }
            }
        }

        let total_bits = (config.frames_per_step * n) as f64;
        let avg_iter = if converged_count > 0 {
            total_iterations as f64 / converged_count as f64
        } else {
            config.max_iter as f64
        };

        SimulationPoint {
            k_config: k_orig,
            n_real: n,
            wc,
            wr,
            rate,
            error_rate: p,
            ber: total_bit_errors as f64 / total_bits,
            fer: total_frame_errors as f64 / config.frames_per_step as f64,
            avg_iter,
        }
    }

    pub fn save_csv(results: &[SimulationPoint], filename: &str) {
        let mut sorted_results = results.to_vec();
        sorted_results.sort_by(|a, b| {
            a.k_config
                .cmp(&b.k_config)
                .then(a.wc.cmp(&b.wc))
                .then(a.wr.cmp(&b.wr))
                .then(a.error_rate.partial_cmp(&b.error_rate).unwrap())
        });

        let mut file = File::create(filename).expect("Erreur fichier");
        writeln!(file, "k,n,wc,wr,rate,p,ber,fer,avg_iter").unwrap();

        for r in sorted_results {
            writeln!(
                file,
                "{},{},{},{},{:.4},{:.6},{:.8},{:.8},{:.2}",
                r.k_config, r.n_real, r.wc, r.wr, r.rate, r.error_rate, r.ber, r.fer, r.avg_iter
            )
            .unwrap();
        }
        println!("Resultats sauvegardes dans '{}'", filename);
    }
}
