use crate::channel::{AwgnChannel, Channel};
use crate::code::{CodeTopology, GenerationMethod, LdpcCode, LdpcParams};
use crate::decoder::{build_decoder, DecoderConfig, DecoderMethod};
use crate::encoder::{build_encoder, EncodingMethod};
use crate::Result as LdpcResult;
use rand::Rng;
use rayon::prelude::*;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use std::time::Instant;

pub fn run_simulation(mut code: LdpcCode) -> LdpcResult<()> {
    println!("[*] Étape 1 : Construction Mathématique et Graphe de Tanner");

    println!(
        "    - Dimensions : n={}, k={}, m={} (Taux R={:.3})",
        code.n(),
        code.k(),
        code.m(),
        code.rate()
    );

    let methode_nom = match code.params.generation {
        GenerationMethod::MacKayNeal { .. } => "MacKay-Neal",
        GenerationMethod::Gallager => "Gallager",
    };

    println!("    - Topologie  : Régulier via {}", methode_nom);
    println!("    - Densité H  : {:.2}%", code.h.density() * 100.0);
    println!("    - Girth      : {}", code.girth());
    println!(
        "    - Cycles-4   : {}",
        if code.graph.has_4_cycle() {
            "Présents (Problématique)"
        } else {
            "Aucun (Optimal)"
        }
    );

    println!("\n[*] Étape 2 : Instanciation de l'Encodeur (SIMD Bit-Packing)");
    let start_enc = Instant::now();
    let encoder = build_encoder(&mut code, EncodingMethod::Systematic)?;
    println!("    - Encodeur prêt en {:.2?}", start_enc.elapsed());

    println!("\n[*] Étape 3 : Instanciation des Décodeurs sur Graphe");
    let config = DecoderConfig {
        max_iterations: 50,
        early_stopping: true,
    };

    let dec_sp = build_decoder(&code, DecoderMethod::SumProduct, config.clone());
    let dec_ms = build_decoder(
        &code,
        DecoderMethod::MinSum {
            scaling_factor: 0.8,
        },
        config.clone(),
    );
    let dec_bf = build_decoder(&code, DecoderMethod::BitFlipping, config.clone());
    println!("    - Moteurs prêts : Sum-Product, Min-Sum (α=0.8), Bit-Flipping");

    let snr_range = [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 3.0, 3.5, 4.0];
    let n_trials = 1000;

    println!(
        "\n[*] Étape 4 : Simulation sur Canal AWGN ({} trames par SNR, Multi-threadé)",
        n_trials
    );
    println!("{:-<115}", "");
    println!(
        "{:>8} | {:>9} || {:>10} | {:>10} || {:>10} | {:>10} || {:>10} | {:>10} |",
        "SNR (dB)",
        "Capacité",
        "FER (SP)",
        "BER (SP)",
        "FER (MS)",
        "BER (MS)",
        "FER (BF)",
        "BER (BF)"
    );
    println!("{:-<115}", "");

    for &snr in &snr_range {
        let channel = AwgnChannel::new(snr, code.rate())?;

        let results: Vec<_> = (0..n_trials)
            .into_par_iter()
            .map(|_| {
                let mut rng = rand::thread_rng();

                let message: Vec<u8> = (0..code.k()).map(|_| rng.gen::<u8>() & 1).collect();
                let codeword = encoder.encode(&message).unwrap();
                let received_llr = channel.transmit(&codeword, &mut rng);

                let mut t_sp_f = 0;
                let mut t_sp_b = 0;
                let mut t_ms_f = 0;
                let mut t_ms_b = 0;
                let mut t_bf_f = 0;
                let mut t_bf_b = 0;

                // SP
                let res_sp = dec_sp.decode(&received_llr);
                if let Some(decoded) = res_sp.codeword() {
                    let errs = count_bit_errors(&codeword, decoded);
                    if errs > 0 {
                        t_sp_f = 1;
                        t_sp_b = errs;
                    }
                } else {
                    t_sp_f = 1;
                    t_sp_b = code.n();
                }

                // MS
                let res_ms = dec_ms.decode(&received_llr);
                if let Some(decoded) = res_ms.codeword() {
                    let errs = count_bit_errors(&codeword, decoded);
                    if errs > 0 {
                        t_ms_f = 1;
                        t_ms_b = errs;
                    }
                } else {
                    t_ms_f = 1;
                    t_ms_b = code.n();
                }

                // BF
                let res_bf = dec_bf.decode(&received_llr);
                if let Some(decoded) = res_bf.codeword() {
                    let errs = count_bit_errors(&codeword, decoded);
                    if errs > 0 {
                        t_bf_f = 1;
                        t_bf_b = errs;
                    }
                } else {
                    t_bf_f = 1;
                    t_bf_b = code.n();
                }

                (t_sp_f, t_sp_b, t_ms_f, t_ms_b, t_bf_f, t_bf_b)
            })
            .collect();

        // Agrégation
        let mut err_sp_frames = 0;
        let mut err_sp_bits = 0;
        let mut err_ms_frames = 0;
        let mut err_ms_bits = 0;
        let mut err_bf_frames = 0;
        let mut err_bf_bits = 0;

        for res in results {
            err_sp_frames += res.0;
            err_sp_bits += res.1;
            err_ms_frames += res.2;
            err_ms_bits += res.3;
            err_bf_frames += res.4;
            err_bf_bits += res.5;
        }

        let total_bits = (n_trials * code.n()) as f64;
        let total_frames = n_trials as f64;

        println!(
            "{:>8.2} | {:>9.4} || {:>9.2}% | {:>9.2}% || {:>9.2}% | {:>9.2}% || {:>9.2}% | {:>9.2}% |",
            snr, channel.capacity(),
            (err_sp_frames as f64 / total_frames) * 100.0, (err_sp_bits as f64 / total_bits) * 100.0,
            (err_ms_frames as f64 / total_frames) * 100.0, (err_ms_bits as f64 / total_bits) * 100.0,
            (err_bf_frames as f64 / total_frames) * 100.0, (err_bf_bits as f64 / total_bits) * 100.0
        );
    }
    println!("{:-<115}", "");
    Ok(())
}

#[inline]
fn count_bit_errors(transmitted: &[u8], decoded: &[u8]) -> usize {
    transmitted
        .iter()
        .zip(decoded.iter())
        .filter(|(a, b)| a != b)
        .count()
}

// pub fn generate_valid_code(
//     n: usize,
//     k: usize,
//     wc: usize,
//     wr: usize,
//     generation_method: GenerationMethod,
// ) -> LdpcResult<LdpcCode> {
//     let mut attempt = 0;
//     let start_gen = Instant::now();
//     loop {
//         attempt += 1;
//         let params = LdpcParams {
//             n,
//             k,
//             topology: CodeTopology::Regular { wc, wr },
//             generation: generation_method.clone(),
//             seed: Some(rand::random()),
//         };
//
//         if let Ok(mut code) = LdpcCode::new(params) {
//             if code.compute_systematic_form().is_ok() {
//                 if attempt > 1 {
//                     println!(
//                         "    -> Matrice inversible obtenue après {} tentatives.",
//                         attempt
//                     );
//                 }
//                 println!("    - Génération : Terminée en {:.2?}", start_gen.elapsed());
//                 return Ok(code);
//             }
//         }
//     }
// }

pub fn get_or_generate_cached_code(
    n: usize,
    k: usize,
    wc: usize,
    wr: usize,
    generation_method: GenerationMethod,
) -> LdpcResult<LdpcCode> {
    let method_str = match generation_method {
        GenerationMethod::MacKayNeal { .. } => "MN",
        GenerationMethod::Gallager => "GAL",
    };
    let cache_filename = format!("cache_ldpc_{}_n{}_k{}.bin", method_str, n, k);
    let path = Path::new(&cache_filename);

    // Chargement
    if path.exists() {
        let start_load = Instant::now();
        let mut file = File::open(&path).expect("Impossible d'ouvrir le fichier de cache");
        let mut buffer = Vec::new();
        file.read_to_end(&mut buffer).unwrap();

        let mut code: LdpcCode = bincode::deserialize(&buffer).expect(
            "Erreur de désérialisation du cache LDPC. Supprimez le fichier .bin et réessayez.",
        );

        // Reconstruction du Graphe de Tanner
        code.graph = crate::graph::TannerGraph::from_matrix(&code.h);

        println!(
            "    -> Matrice chargée depuis le cache en {:.2?}",
            start_load.elapsed()
        );
        return Ok(code);
    }

    // Génération
    println!("    -> Aucun cache trouvé. Génération en cours ...");
    let mut attempt = 0;
    let start_gen = Instant::now();

    loop {
        attempt += 1;
        let params = LdpcParams {
            n,
            k,
            topology: CodeTopology::Regular { wc, wr },
            generation: generation_method.clone(),
            seed: Some(rand::random()),
        };

        if let Ok(mut code) = LdpcCode::new(params) {
            if code.compute_systematic_form().is_ok() {
                println!(
                    "    - Génération et Pivot de Gauss terminés en {:.2?}",
                    start_gen.elapsed()
                );

                // sauvegarde cache
                let encoded = bincode::serialize(&code).expect("Échec de la sérialisation");
                let mut file =
                    File::create(&path).expect("Impossible de créer le fichier de cache");
                file.write_all(&encoded)
                    .expect("Impossible d'écrire sur le disque");
                println!("    -> Matrice sauvegardée ({})", cache_filename);

                return Ok(code);
            }
        }
    }
}
