use crate::structures::clifford_circuit::{CliffordCircuit, CliffordGate};
use crate::structures::graph_state::GraphState;
use crate::structures::pauli_like::PauliLike;
use petgraph::algo::maximum_matching;
use petgraph::prelude::*;

fn score_matrix(graph: &mut GraphState, qubits_used: &[bool]) -> Vec<Vec<i32>> {
    let base_value: i32 = graph.count_ones() as i32;
    let mut scores = vec![vec![-1; graph.n]; graph.n];
    for i in 0..graph.n {
        for j in i + 1..graph.n {
            if !qubits_used[i] && !qubits_used[j] {
                graph.cnot(i, j);
                scores[i][j] = base_value - graph.count_ones() as i32;
                graph.cnot(i, j);
            }
        }
    }
    scores
}

fn pick_best_operation(scores: &[Vec<i32>]) -> (i32, (usize, usize)) {
    let mut best_score = 0;
    let mut best_qubits: (usize, usize) = (0, 0);
    for i in 0..scores.len() {
        for j in 0..scores.len() {
            if scores[i][j] > best_score {
                best_score = scores[i][j];
                best_qubits = (i, j);
            }
        }
    }
    (best_score, best_qubits)
}

pub fn get_czs(graph: &GraphState, qubits_used: &[bool]) -> CliffordCircuit {
    let mut mgraph: UnGraph<(), i32> = UnGraph::new_undirected();
    for _ in 0..graph.n {
        mgraph.add_node(());
    }
    for i in 0..graph.n {
        for j in 0..graph.n {
            if !qubits_used[i] && !qubits_used[j] && graph.adj[i][j] {
                mgraph.add_edge(NodeIndex::new(i), NodeIndex::new(j), 1);
            }
        }
    }
    let matching = maximum_matching(&mgraph);
    let mut circuit_piece = CliffordCircuit::new(graph.n);
    for (qbit1, qbit2) in matching.edges() {
        circuit_piece
            .gates
            .push(CliffordGate::CZ(qbit1.index(), qbit2.index()));
    }
    circuit_piece
}

pub fn synthesize_graph_state_depth(input_graph: &GraphState) -> CliffordCircuit {
    let mut graph = input_graph.clone();
    let mut circuit = CliffordCircuit::new(graph.n);
    for i in 0..graph.n {
        if graph.adj[i][i] {
            graph.s(i);
            circuit.gates.push(CliffordGate::S(i));
        }
    }
    while graph.count_ones() > 0 {
        let mut qubits_used: Vec<bool> = vec![false; graph.n];
        loop {
            let scores = score_matrix(&mut graph, &qubits_used);
            let (best_move, (control, target)) = pick_best_operation(&scores);
            if best_move == 0 {
                break;
            }
            graph.cnot(control, target);
            circuit.gates.push(CliffordGate::CNOT(control, target));
            qubits_used[control] = true;
            qubits_used[target] = true;
        }
        let cz_circuit = get_czs(&graph, &qubits_used);
        circuit.extend_with(&cz_circuit);
        graph.conjugate_with_circuit(&cz_circuit);
    }

    circuit.dagger()
}
