// Copyright 2026 Gregorio Momm
// Licensed under the Apache License, Version 2.0
//
// Ported from RustworkxCore (IBM Qiskit) — Apache License 2.0
// Source: https://github.com/Qiskit/rustworkx/tree/main/rustworkx-core/src

//! Euler path and circuit algorithms.
//!
//! An Euler circuit visits every edge exactly once and returns to the starting node.
//! An Euler path visits every edge exactly once (may start and end at different nodes).
//!
//! - `has_euler_path` — check if an Euler path exists
//! - `has_euler_circuit` — check if an Euler circuit exists
//! - `euler_circuit` — find an Euler circuit (Hierholzer's algorithm)
//! - `euler_path` — find an Euler path
//!
//! Reference: Hierholzer, C. (1873). "Über die Möglichkeit, einen Linienzug ohne Wiederholung und
//! ohne Unterbrechung zu umfahren." Mathematische Annalen, 6(1), 30-32.

use std::collections::{HashSet, VecDeque};

use super::super::graph::{Graph, NodeId};

// ─── Condition checks ─────────────────────────────────────────────────────────

/// Check if the graph has an Euler circuit.
///
/// **Undirected**: Euler circuit exists iff every node has even degree and the graph is connected.
///
/// **Directed**: Euler circuit exists iff every node has equal in-degree and out-degree,
/// and the underlying undirected graph is weakly connected.
pub fn has_euler_circuit(graph: &Graph) -> bool {
    if graph.edge_count() == 0 { return graph.node_count() <= 1; }

    if graph.is_directed() {
        // Directed Euler circuit: in_degree == out_degree for every node
        if graph.nodes().any(|u| graph.in_degree(u) != graph.degree(u)) {
            return false;
        }
    } else {
        // Undirected Euler circuit: every node has even degree
        if graph.nodes().any(|u| graph.degree(u) % 2 != 0) {
            return false;
        }
    }

    // Check weak connectivity (follow both directions)
    is_weakly_connected_on_edges(graph)
}

/// Check if the graph has an Euler path.
///
/// **Undirected**: Euler path exists iff exactly 0 or 2 nodes have odd degree and graph is connected.
///
/// **Directed**: Euler path exists iff at most one node has `out_degree - in_degree == 1` (start),
/// at most one node has `in_degree - out_degree == 1` (end), all other nodes have equal degrees,
/// and the underlying undirected graph is weakly connected.
pub fn has_euler_path(graph: &Graph) -> bool {
    if graph.edge_count() == 0 { return true; }

    if graph.is_directed() {
        let mut start_count = 0usize; // nodes with out - in == 1
        let mut end_count = 0usize;   // nodes with in - out == 1
        for u in graph.nodes() {
            let out = graph.degree(u) as i64;
            let inp = graph.in_degree(u) as i64;
            let diff = out - inp;
            match diff {
                0 => {}
                1 => start_count += 1,
                -1 => end_count += 1,
                _ => return false, // |diff| > 1 → no Euler path
            }
        }
        if (start_count, end_count) != (0, 0) && (start_count, end_count) != (1, 1) {
            return false;
        }
    } else {
        let odd_degree_count = graph.nodes()
            .filter(|&u| graph.degree(u) % 2 != 0)
            .count();
        if odd_degree_count != 0 && odd_degree_count != 2 {
            return false;
        }
    }

    is_weakly_connected_on_edges(graph)
}

/// Check weak connectivity restricted to nodes that have at least one edge.
///
/// Uses BFS following both out-edges and in-edges (for directed graphs).
fn is_weakly_connected_on_edges(graph: &Graph) -> bool {
    let nodes_with_edges: Vec<NodeId> = graph.nodes()
        .filter(|&u| graph.degree(u) > 0 || graph.in_degree(u) > 0)
        .collect();

    if nodes_with_edges.is_empty() { return true; }

    // BFS following both out- and in-neighbors (weak connectivity)
    let start = nodes_with_edges[0];
    let mut visited: HashSet<NodeId> = HashSet::new();
    let mut queue = VecDeque::new();
    visited.insert(start);
    queue.push_back(start);
    while let Some(u) = queue.pop_front() {
        for e in graph.out_neighbors(u).iter() {
            if visited.insert(e.target) {
                queue.push_back(e.target);
            }
        }
        for e in graph.in_neighbors(u).iter() {
            if visited.insert(e.target) {
                queue.push_back(e.target);
            }
        }
    }

    nodes_with_edges.iter().all(|u| visited.contains(u))
}

// ─── Hierholzer's Algorithm ───────────────────────────────────────────────────

/// Find an Euler circuit using Hierholzer's algorithm.
///
/// Returns the sequence of nodes forming the circuit (first == last),
/// or `None` if no Euler circuit exists.
///
/// Runtime: O(m)
pub fn euler_circuit(graph: &Graph) -> Option<Vec<NodeId>> {
    if !has_euler_circuit(graph) { return None; }
    if graph.edge_count() == 0 {
        // Single node or empty graph
        return Some(graph.nodes().next().map(|n| vec![n]).unwrap_or_default());
    }

    let start = graph.nodes().next()?;
    Some(hierholzer(graph, start))
}

/// Find an Euler path using Hierholzer's algorithm.
///
/// Returns the sequence of nodes forming the path, or `None` if no Euler path exists.
///
/// Runtime: O(m)
pub fn euler_path(graph: &Graph) -> Option<Vec<NodeId>> {
    if !has_euler_path(graph) { return None; }
    if graph.edge_count() == 0 {
        return Some(graph.nodes().next().map(|n| vec![n]).unwrap_or_default());
    }

    // For directed Euler path: start at the node with out - in == 1 (if any)
    // For undirected: start at any odd-degree node (if any), else any node
    let start = if graph.is_directed() {
        graph.nodes()
            .find(|&u| graph.degree(u) as i64 - graph.in_degree(u) as i64 == 1)
            .or_else(|| graph.nodes().next())?
    } else {
        graph.nodes()
            .find(|&u| graph.degree(u) % 2 != 0)
            .or_else(|| graph.nodes().next())?
    };

    Some(hierholzer(graph, start))
}

/// Hierholzer's algorithm for finding Euler circuits/paths.
///
/// Works by maintaining a stack of nodes and an edge-visited set.
/// Each edge is consumed exactly once.
///
/// For **undirected** graphs: edges are deduplicated by canonical `(min, max)` pair.
/// For **directed** graphs: each directed edge is a distinct arc consumed once.
fn hierholzer(graph: &Graph, start: NodeId) -> Vec<NodeId> {
    let n = graph.upper_node_id_bound() as usize;

    let mut circuit: Vec<NodeId> = Vec::new();
    let mut stack: Vec<NodeId> = vec![start];

    // Build adjacency list with unique edge IDs for efficient traversal.
    let mut adj: Vec<Vec<(NodeId, usize)>> = vec![Vec::new(); n];
    let mut edge_counter = 0usize;

    if graph.is_directed() {
        // Directed: each out-edge is a distinct directed arc.
        for u in graph.nodes() {
            for e in graph.out_neighbors(u).iter() {
                adj[u as usize].push((e.target, edge_counter));
                edge_counter += 1;
            }
        }
    } else {
        // Undirected: deduplicate edges so each undirected edge appears once.
        for u in graph.nodes() {
            for e in graph.out_neighbors(u).iter() {
                let v = e.target;
                if u < v || u == v { // canonical direction + self-loops
                    adj[u as usize].push((v, edge_counter));
                    adj[v as usize].push((u, edge_counter));
                    edge_counter += 1;
                }
            }
        }
    }

    let mut used_edge_ids: HashSet<usize> = HashSet::new();
    let mut node_edge_idx: Vec<usize> = vec![0; n];

    while let Some(&v) = stack.last() {
        let adj_v = &adj[v as usize];

        // Find next unused edge from v
        let mut found = false;
        let mut i = node_edge_idx[v as usize];
        while i < adj_v.len() {
            let (_, eid) = adj_v[i];
            if !used_edge_ids.contains(&eid) {
                used_edge_ids.insert(eid);
                node_edge_idx[v as usize] = i + 1;
                let next = adj_v[i].0;
                stack.push(next);
                found = true;
                break;
            }
            i += 1;
        }

        if !found {
            node_edge_idx[v as usize] = i;
            circuit.push(stack.pop().unwrap());
        }
    }

    circuit.reverse();
    circuit
}

// ─── Tests ────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::graph::backends::networkit_rust::graph::GraphConfig;

    fn triangle() -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..3 { g.add_node(); }
        g.add_edge(0, 1, None); g.add_edge(1, 2, None); g.add_edge(2, 0, None);
        g
    }

    fn path4() -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        g.add_edge(0, 1, None); g.add_edge(1, 2, None); g.add_edge(2, 3, None);
        g
    }

    fn k4() -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        for i in 0..4u64 { for j in (i+1)..4 { g.add_edge(i, j, None); } }
        g
    }

    // ── has_euler_circuit ──────────────────────────────────────────────────────

    #[test]
    fn test_has_euler_circuit_triangle() {
        // Triangle: all degrees = 2 (even) → Euler circuit
        assert!(has_euler_circuit(&triangle()));
    }

    #[test]
    fn test_has_euler_circuit_k4() {
        // K4: all degrees = 3 (odd) → no Euler circuit
        assert!(!has_euler_circuit(&k4()));
    }

    #[test]
    fn test_has_euler_circuit_path4() {
        // Path 0-1-2-3: endpoints have degree 1 (odd) → no Euler circuit
        assert!(!has_euler_circuit(&path4()));
    }

    #[test]
    fn test_has_euler_circuit_empty() {
        let g = Graph::new(GraphConfig::simple());
        assert!(has_euler_circuit(&g));
    }

    // ── has_euler_path ─────────────────────────────────────────────────────────

    #[test]
    fn test_has_euler_path_path4() {
        // Path 0-1-2-3: exactly 2 odd-degree nodes (0 and 3) → Euler path
        assert!(has_euler_path(&path4()));
    }

    #[test]
    fn test_has_euler_path_triangle() {
        // Triangle: 0 odd-degree nodes → Euler circuit (which is also an Euler path)
        assert!(has_euler_path(&triangle()));
    }

    #[test]
    fn test_has_euler_path_k4() {
        // K4: 4 odd-degree nodes → no Euler path
        assert!(!has_euler_path(&k4()));
    }

    // ── euler_circuit ──────────────────────────────────────────────────────────

    fn verify_euler_circuit(g: &Graph, circuit: &[NodeId]) -> bool {
        if circuit.is_empty() { return g.edge_count() == 0; }
        if circuit.first() != circuit.last() { return false; }

        // For undirected graph, edge_count() counts both directions.
        // Number of unique edges = edge_count() / 2.
        // Circuit visits each unique edge once, so circuit.len() = unique_edges + 1.
        let unique_edges = g.nodes()
            .flat_map(|u| g.out_neighbors(u).iter().map(move |e| (u, e.target)))
            .filter(|&(u, v)| u < v)
            .count();
        if circuit.len() != unique_edges + 1 { return false; }

        // Check each edge is used exactly once
        let mut used: HashSet<(NodeId, NodeId)> = HashSet::new();
        for w in circuit.windows(2) {
            let (a, b) = (w[0].min(w[1]), w[0].max(w[1]));
            if !used.insert((a, b)) { return false; } // used twice
        }

        // Check all graph edges are covered
        for u in g.nodes() {
            for e in g.out_neighbors(u).iter() {
                if u < e.target {
                    let key = (u, e.target);
                    if !used.contains(&key) { return false; }
                }
            }
        }
        true
    }

    #[test]
    fn test_euler_circuit_triangle() {
        let g = triangle();
        let circuit = euler_circuit(&g).expect("triangle has Euler circuit");
        assert!(verify_euler_circuit(&g, &circuit),
            "triangle circuit invalid: {:?}", circuit);
    }

    #[test]
    fn test_euler_circuit_no_circuit() {
        assert!(euler_circuit(&path4()).is_none());
    }

    #[test]
    fn test_euler_circuit_single_node() {
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node();
        let circuit = euler_circuit(&g).expect("single node has trivial circuit");
        assert_eq!(circuit, vec![0]);
    }

    #[test]
    fn test_euler_circuit_square() {
        // 4-cycle: 0-1-2-3-0
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        g.add_edge(0, 1, None); g.add_edge(1, 2, None);
        g.add_edge(2, 3, None); g.add_edge(3, 0, None);
        let circuit = euler_circuit(&g).expect("4-cycle has Euler circuit");
        assert!(verify_euler_circuit(&g, &circuit),
            "4-cycle circuit invalid: {:?}", circuit);
    }

    // ── euler_path ──────────────────────────────────────────────────────────────

    fn verify_euler_path(g: &Graph, path: &[NodeId]) -> bool {
        if path.is_empty() { return g.edge_count() == 0; }

        // Edge count check (unique edges)
        let unique_edges = g.nodes()
            .flat_map(|u| g.out_neighbors(u).iter().map(move |e| (u, e.target)))
            .filter(|&(u, v)| u < v)
            .count();
        if path.len() != unique_edges + 1 { return false; }

        // Each edge exactly once
        let mut used: HashSet<(NodeId, NodeId)> = HashSet::new();
        for w in path.windows(2) {
            let (a, b) = (w[0].min(w[1]), w[0].max(w[1]));
            if !used.insert((a, b)) { return false; }
        }

        // All edges covered
        for u in g.nodes() {
            for e in g.out_neighbors(u).iter() {
                if u < e.target {
                    if !used.contains(&(u, e.target)) { return false; }
                }
            }
        }
        true
    }

    #[test]
    fn test_euler_path_path4() {
        let g = path4();
        let path = euler_path(&g).expect("path4 has Euler path");
        assert!(verify_euler_path(&g, &path),
            "path4 Euler path invalid: {:?}", path);
    }

    #[test]
    fn test_euler_path_triangle() {
        // Triangle also has an Euler path (same as circuit)
        let g = triangle();
        let path = euler_path(&g).expect("triangle has Euler path");
        assert!(verify_euler_path(&g, &path),
            "triangle Euler path invalid: {:?}", path);
    }

    #[test]
    fn test_euler_path_no_path_k4() {
        // K4 has 4 odd-degree nodes — no Euler path
        assert!(euler_path(&k4()).is_none());
    }

    #[test]
    fn test_euler_path_single_edge() {
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node(); g.add_node();
        g.add_edge(0, 1, None);
        let path = euler_path(&g).expect("single edge has Euler path");
        assert_eq!(path.len(), 2);
    }

    #[test]
    fn test_euler_path_complex() {
        // Graph with exactly 2 odd-degree nodes
        // 0-1, 1-2, 2-3, 3-1 (nodes 0 and 3 have degree 1, nodes 1 and 2 degree 2,3)
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        g.add_edge(0, 1, None);
        g.add_edge(1, 2, None);
        g.add_edge(2, 3, None);
        g.add_edge(3, 1, None);
        let path = euler_path(&g).expect("complex graph has Euler path");
        assert!(verify_euler_path(&g, &path),
            "complex Euler path invalid: {:?}", path);
    }
}
