use crate::matrix::SparseMatrixGF2;
use std::collections::VecDeque;

// Graphe de Tanner
#[derive(Debug, Clone)]
pub struct TannerGraph {
    pub n_var: usize,
    pub n_chk: usize,
    var_to_chk: Vec<Vec<usize>>,
    chk_to_var: Vec<Vec<usize>>,
}

impl TannerGraph {
    pub fn from_matrix(h: &SparseMatrixGF2) -> Self {
        let n_var = h.cols;
        let n_chk = h.rows;
        let chk_to_var: Vec<Vec<usize>> = (0..n_chk).map(|c| h.row_neighbors(c).to_vec()).collect();
        let mut var_to_chk = vec![vec![]; n_var];
        for c in 0..n_chk {
            for &v in &chk_to_var[c] {
                var_to_chk[v].push(c);
            }
        }
        Self {
            n_var,
            n_chk,
            var_to_chk,
            chk_to_var,
        }
    }

    pub fn var_neighbors(&self, v: usize) -> &[usize] {
        &self.var_to_chk[v]
    }
    pub fn chk_neighbors(&self, c: usize) -> &[usize] {
        &self.chk_to_var[c]
    }
    pub fn var_degree(&self, v: usize) -> usize {
        self.var_to_chk[v].len()
    }
    pub fn chk_degree(&self, c: usize) -> usize {
        self.chk_to_var[c].len()
    }

    // Calcule le girth par BFS depuis chaque noeud de variable
    pub fn girth(&self) -> usize {
        let mut min_girth = usize::MAX;
        for start in 0..self.n_var {
            if let Some(g) = self.bfs_girth_from_var(start) {
                min_girth = min_girth.min(g);
                if min_girth == 4 {
                    return 4;
                } // minimum
            }
        }
        min_girth
    }

    // Détection cycles-4, 2 varnodes partagent >= check-nodes
    pub fn has_4_cycle(&self) -> bool {
        for v1 in 0..self.n_var {
            for v2 in (v1 + 1)..self.n_var {
                let common = self.var_to_chk[v1]
                    .iter()
                    .filter(|c| self.var_to_chk[v2].contains(c))
                    .count();
                if common >= 2 {
                    return true;
                }
            }
        }
        false
    }

    pub fn local_girth_from_var(&self, v: usize) -> usize {
        self.bfs_girth_from_var(v).unwrap_or(usize::MAX)
    }

    // retourne la longueur du court cycle passant par ce noeud (None si pas cycle)
    fn bfs_girth_from_var(&self, start: usize) -> Option<usize> {
        let mut dist_var = vec![usize::MAX; self.n_var];
        let mut dist_chk = vec![usize::MAX; self.n_chk];
        dist_var[start] = 0;

        // File (is_var, index, distance, parent_index)
        let mut queue: VecDeque<(bool, usize, usize, usize)> = VecDeque::new();
        queue.push_back((true, start, 0, usize::MAX));
        let mut shortest = None;

        while let Some((is_var, node, dist, parent)) = queue.pop_front() {
            if is_var {
                for &c in self.var_neighbors(node) {
                    if c == parent {
                        continue;
                    } // aller retour immédiat impossible
                    if dist_chk[c] == usize::MAX {
                        dist_chk[c] = dist + 1;
                        queue.push_back((false, c, dist + 1, node));
                    } else {
                        let cycle_len = dist + 1 + dist_chk[c];
                        shortest = Some(shortest.map_or(cycle_len, |s: usize| s.min(cycle_len)));
                    }
                }
            } else {
                for &v in self.chk_neighbors(node) {
                    if v == parent {
                        continue;
                    } // aller retour immédiat impossible
                    if v == start {
                        let cycle_len = dist + 1;
                        shortest = Some(shortest.map_or(cycle_len, |s: usize| s.min(cycle_len)));
                        continue;
                    }
                    if dist_var[v] == usize::MAX {
                        dist_var[v] = dist + 1;
                        queue.push_back((true, v, dist + 1, node));
                    }
                }
            }
        }
        shortest
    }

    pub fn var_degree_distribution(&self) -> Vec<f64> {
        let max_deg = self.var_to_chk.iter().map(|v| v.len()).max().unwrap_or(0);
        let mut counts = vec![0usize; max_deg + 1];
        for v in 0..self.n_var {
            counts[self.var_degree(v)] += 1;
        }
        counts
            .iter()
            .map(|&c| c as f64 / self.n_var as f64)
            .collect()
    }

    pub fn is_regular(&self) -> bool {
        let d0 = self.var_degree(0);
        let c0 = self.chk_degree(0);
        self.var_to_chk.iter().all(|v| v.len() == d0)
            && self.chk_to_var.iter().all(|c| c.len() == c0)
    }
}
