use crate::Gf2;
use serde::{Deserialize, Serialize};

// Matrice creuse format CSR + CSC
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SparseMatrixGF2 {
    pub rows: usize,
    pub cols: usize,
    // CSR accès ligne i : col_idx[row_ptr[i]..row_ptr[i+1]]
    row_ptr: Vec<usize>,
    col_idx: Vec<usize>,
    // CSC accès col j  : row_idx[col_ptr[j]..col_ptr[j+1]]
    col_ptr: Vec<usize>,
    row_idx: Vec<usize>,
}

impl SparseMatrixGF2 {
    pub fn zeros(rows: usize, cols: usize) -> Self {
        Self {
            rows,
            cols,
            row_ptr: vec![0; rows + 1],
            col_idx: vec![],
            col_ptr: vec![0; cols + 1],
            row_idx: vec![],
        }
    }

    // Depuis une liste de (row, col) indiquant les positions des 1s
    // Trie les entrées et construit CSR + CSC en un seul passage
    pub fn from_positions(rows: usize, cols: usize, mut ones: Vec<(usize, usize)>) -> Self {
        // Construction CSR
        ones.sort_unstable();
        let mut row_ptr = vec![0usize; rows + 1];
        let mut col_idx = Vec::with_capacity(ones.len());
        for &(r, c) in &ones {
            row_ptr[r + 1] += 1;
            col_idx.push(c);
        }
        for i in 0..rows {
            row_ptr[i + 1] += row_ptr[i];
        }
        // Construction CSC
        let mut col_sorted = ones.clone();
        col_sorted.sort_unstable_by_key(|&(r, c)| (c, r));
        let mut col_ptr = vec![0usize; cols + 1];
        let mut row_idx = Vec::with_capacity(ones.len());
        for &(r, c) in &col_sorted {
            col_ptr[c + 1] += 1;
            row_idx.push(r);
        }
        for j in 0..cols {
            col_ptr[j + 1] += col_ptr[j];
        }
        Self {
            rows,
            cols,
            row_ptr,
            col_idx,
            col_ptr,
            row_idx,
        }
    }

    pub fn from_dense(dense: &[Vec<Gf2>]) -> Self {
        let rows = dense.len();
        let cols = if rows > 0 { dense[0].len() } else { 0 };
        let ones: Vec<(usize, usize)> = dense
            .iter()
            .enumerate()
            .flat_map(|(r, row)| {
                row.iter()
                    .enumerate()
                    .filter(|(_, &v)| v == 1)
                    .map(move |(c, _)| (r, c))
            })
            .collect();
        Self::from_positions(rows, cols, ones)
    }

    pub fn get(&self, row: usize, col: usize) -> Gf2 {
        let slice = self.row_neighbors(row);
        if slice.binary_search(&col).is_ok() {
            1
        } else {
            0
        }
    }

    // Indices des colonnes où la ligne row vaut 1
    pub fn row_neighbors(&self, row: usize) -> &[usize] {
        &self.col_idx[self.row_ptr[row]..self.row_ptr[row + 1]]
    }

    // Indices des lignes où la colonne col vaut 1
    pub fn col_neighbors(&self, col: usize) -> &[usize] {
        &self.row_idx[self.col_ptr[col]..self.col_ptr[col + 1]]
    }

    pub fn row_weight(&self, row: usize) -> usize {
        self.row_ptr[row + 1] - self.row_ptr[row]
    }

    pub fn col_weight(&self, col: usize) -> usize {
        self.col_ptr[col + 1] - self.col_ptr[col]
    }

    pub fn nnz(&self) -> usize {
        self.col_idx.len()
    }

    pub fn density(&self) -> f64 {
        self.nnz() as f64 / (self.rows * self.cols) as f64
    }

    // Produit H * x mod 2 (syndrome : s = H * c^T)
    pub fn multiply_vec(&self, x: &[Gf2]) -> Vec<Gf2> {
        (0..self.rows)
            .map(|r| {
                self.row_neighbors(r)
                    .iter()
                    .map(|&c| x[c])
                    .fold(0u8, |acc, b| acc ^ b)
            })
            .collect()
    }

    pub fn transpose(&self) -> Self {
        Self {
            rows: self.cols,
            cols: self.rows,
            row_ptr: self.col_ptr.clone(),
            col_idx: self.row_idx.clone(),
            col_ptr: self.row_ptr.clone(),
            row_idx: self.col_idx.clone(),
        }
    }

    // Vérifie si deux colonnes partagent >= 2 positions de 1 -> cycle-4 détecté
    pub fn columns_share_two_ones(&self, c1: usize, c2: usize) -> bool {
        let n1 = self.col_neighbors(c1);
        let n2 = self.col_neighbors(c2);
        let mut common = 0usize;
        let (mut i, mut j) = (0, 0);
        while i < n1.len() && j < n2.len() {
            match n1[i].cmp(&n2[j]) {
                std::cmp::Ordering::Less => i += 1,
                std::cmp::Ordering::Greater => j += 1,
                std::cmp::Ordering::Equal => {
                    common += 1;
                    if common >= 2 {
                        return true;
                    }
                    i += 1;
                    j += 1;
                }
            }
        }
        false
    }

    pub fn to_dense(&self) -> Vec<Vec<Gf2>> {
        let mut out = vec![vec![0u8; self.cols]; self.rows];
        for r in 0..self.rows {
            for &c in self.row_neighbors(r) {
                out[r][c] = 1;
            }
        }
        out
    }
}

// Matrice dense
// Utilisée pour G et Gauss-Jordan
// G = [I | P], P dense

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DenseMatrixGF2 {
    pub rows: usize,
    pub cols: usize,
    data: Vec<Vec<Gf2>>,
}

impl DenseMatrixGF2 {
    pub fn zeros(rows: usize, cols: usize) -> Self {
        Self {
            rows,
            cols,
            data: vec![vec![0u8; cols]; rows],
        }
    }

    pub fn identity(n: usize) -> Self {
        let mut m = Self::zeros(n, n);
        for i in 0..n {
            m.data[i][i] = 1;
        }
        m
    }

    pub fn get(&self, row: usize, col: usize) -> Gf2 {
        self.data[row][col]
    }
    pub fn set(&mut self, row: usize, col: usize, val: Gf2) {
        self.data[row][col] = val;
    }

    pub fn row_add(&mut self, dst: usize, src: usize) {
        for j in 0..self.cols {
            self.data[dst][j] ^= self.data[src][j];
        }
    }

    pub fn row_swap(&mut self, r1: usize, r2: usize) {
        self.data.swap(r1, r2);
    }

    pub fn col_swap(&mut self, c1: usize, c2: usize) {
        for r in 0..self.rows {
            let tmp = self.data[r][c1];
            self.data[r][c1] = self.data[r][c2];
            self.data[r][c2] = tmp;
        }
    }

    pub fn multiply_vec(&self, x: &[Gf2]) -> Vec<Gf2> {
        self.data
            .iter()
            .map(|row| {
                row.iter()
                    .zip(x.iter())
                    .fold(0u8, |acc, (&a, &b)| acc ^ (a & b))
            })
            .collect()
    }

    pub fn into_sparse(self) -> SparseMatrixGF2 {
        SparseMatrixGF2::from_dense(&self.data)
    }

    pub fn systematize(&mut self, k: usize) -> (Vec<usize>, usize) {
        let m = self.rows;
        let n = self.cols;
        let mut col_perm: Vec<usize> = (0..n).collect();
        let mut rank = 0;

        for i in 0..m {
            // Placer le pivot ligne i à la colonne target_c (former I_m à droite)
            let target_c = k + i;
            let mut pivot_found = false;

            // pivot, chercher en premier dans les colonnes cibles restantes
            // et après dans les colonnes de données (0..k)
            for c_search in (target_c..n).chain(0..k) {
                for r_search in i..m {
                    if self.data[r_search][c_search] == 1 {
                        // pivot à la position (i, target_c)
                        self.row_swap(i, r_search);
                        if c_search != target_c {
                            self.col_swap(target_c, c_search);
                            col_perm.swap(target_c, c_search);
                        }
                        pivot_found = true;
                        break;
                    }
                }
                if pivot_found {
                    break;
                }
            }

            if pivot_found {
                rank += 1;
                // Elimination dans toutes les autres lignes => forcer la colonne à 0 (sauf le pivot à 1)
                for r in 0..m {
                    if r != i && self.data[r][target_c] == 1 {
                        self.row_add(r, i);
                    }
                }
            }
        }
        (col_perm, rank)
    }
}
