// 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

//! Token swapper algorithm.
//!
//! Given a graph G and a mapping from nodes to tokens (a permutation),
//! find a sequence of swaps along edges that transforms the current configuration
//! into the identity (each token at its home node).
//!
//! This implements a provably-terminating greedy algorithm:
//! for each misplaced token, route it home along the shortest path,
//! using intermediate swaps to clear the path as needed.
//!
//! Reference:
//! Miltzow, T., Narins, L., Okamoto, Y., Rote, G., Thomas, A., & Uno, T. (2016).
//! "Approximation and Hardness for Token Swapping."
//! In 24th Annual European Symposium on Algorithms (ESA 2016).

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

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

// ─── BFS utilities ───────────────────────────────────────────────────────────

/// BFS predecessors from `source` to all reachable nodes.
fn bfs_path(graph: &Graph, source: NodeId, target: NodeId) -> Vec<NodeId> {
    if source == target { return vec![source]; }
    let mut pred: HashMap<NodeId, NodeId> = HashMap::new();
    let mut visited = HashSet::new();
    let mut queue = VecDeque::new();
    visited.insert(source);
    queue.push_back(source);
    while let Some(u) = queue.pop_front() {
        if u == target {
            let mut path = vec![target];
            let mut cur = target;
            while cur != source {
                cur = pred[&cur];
                path.push(cur);
            }
            path.reverse();
            return path;
        }
        for e in graph.out_neighbors(u).iter() {
            if !visited.contains(&e.target) {
                visited.insert(e.target);
                pred.insert(e.target, u);
                queue.push_back(e.target);
            }
        }
    }
    vec![] // unreachable
}

// ─── Token Swapper ───────────────────────────────────────────────────────────

/// A swap operation: exchange tokens at nodes `a` and `b`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Swap {
    pub a: NodeId,
    pub b: NodeId,
}

impl Swap {
    pub fn new(a: NodeId, b: NodeId) -> Self { Swap { a, b } }
}

/// Result of the token swapper.
pub struct TokenSwapResult {
    /// Ordered list of swaps needed to achieve the target mapping.
    pub swaps: Vec<Swap>,
}

/// Minimum token swapping using a sequential routing algorithm.
///
/// Given a graph G and a permutation `mapping: node → token` where the goal
/// is to reach the identity (each token `t` at node `t`), this produces a
/// sequence of edge swaps.
///
/// # Arguments
/// - `graph`: undirected graph (swaps allowed along edges)
/// - `mapping`: `mapping[node] = token` — current placement
///   Requires: `mapping` is a permutation of node ids (bijection)
/// - `trials`: number of random restarts (currently unused; reserved for future)
///
/// # Algorithm
/// Processes tokens sequentially. For each misplaced token `t`:
/// 1. Find the shortest path from `t`'s current position to its home `t`.
/// 2. Walk along the path, swapping adjacent nodes to move `t` one step closer.
/// 3. The displaced tokens will be placed later (their home positions are later).
///
/// Termination: Each swap moves exactly one token one step closer to home.
/// Since the total "displacement" (sum of all distances to home) strictly decreases,
/// the algorithm terminates.
pub fn token_swapper(
    graph: &Graph,
    mapping: HashMap<NodeId, NodeId>,
    _trials: usize,
) -> TokenSwapResult {
    let mut nodes: Vec<NodeId> = graph.nodes().collect();
    if nodes.is_empty() {
        return TokenSwapResult { swaps: vec![] };
    }
    nodes.sort_unstable(); // process tokens in stable order

    // Build reverse: where is each token right now?
    // token_at[token] = node
    let mut token_at: HashMap<NodeId, NodeId> = mapping.iter()
        .map(|(&node, &token)| (token, node))
        .collect();
    let mut node_has: HashMap<NodeId, NodeId> = mapping; // node_has[node] = token

    let mut swaps: Vec<Swap> = Vec::new();

    // Process each token in order of its home node id
    for token in nodes.iter().copied() {
        // Route token to its home
        loop {
            let cur = token_at[&token];
            if cur == token { break; } // already home

            // Shortest path from current position to home
            let path = bfs_path(graph, cur, token);
            if path.len() < 2 { break; } // shouldn't happen in connected graph

            // Swap along path: move token one step toward home
            let next = path[1];
            let other_token = node_has[&next];

            // Execute swap
            node_has.insert(cur, other_token);
            node_has.insert(next, token);
            token_at.insert(token, next);
            token_at.insert(other_token, cur);

            swaps.push(Swap::new(cur, next));
        }
    }

    TokenSwapResult { swaps }
}

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

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

    fn path_graph(n: usize) -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..n { g.add_node(); }
        for i in 0..(n-1) as NodeId { g.add_edge(i, i+1, None); }
        g
    }

    fn cycle_graph_g(n: usize) -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..n { g.add_node(); }
        for i in 0..n as NodeId { g.add_edge(i, (i+1) % n as NodeId, None); }
        g
    }

    /// Verify that applying swaps to `mapping` produces the identity mapping.
    fn verify_swaps(mut mapping: HashMap<NodeId, NodeId>, swaps: &[Swap]) -> bool {
        for swap in swaps {
            let a_val = mapping[&swap.a];
            let b_val = mapping[&swap.b];
            mapping.insert(swap.a, b_val);
            mapping.insert(swap.b, a_val);
        }
        mapping.iter().all(|(&node, &token)| node == token)
    }

    #[test]
    fn test_identity_mapping_no_swaps() {
        let g = path_graph(4);
        let mapping: HashMap<NodeId, NodeId> = (0..4u64).map(|i| (i, i)).collect();
        let result = token_swapper(&g, mapping, 1);
        assert_eq!(result.swaps.len(), 0, "identity needs 0 swaps");
    }

    #[test]
    fn test_adjacent_swap() {
        let g = path_graph(2);
        let mapping: HashMap<NodeId, NodeId> = [(0, 1), (1, 0)].iter().copied().collect();
        let orig = mapping.clone();
        let result = token_swapper(&g, mapping, 1);
        assert!(verify_swaps(orig, &result.swaps), "swaps should solve it");
        assert_eq!(result.swaps.len(), 1, "1 swap needed");
    }

    #[test]
    fn test_path_reversal() {
        // Path 0-1-2-3, tokens [3,2,1,0]
        let g = path_graph(4);
        let mapping: HashMap<NodeId, NodeId> = [(0,3),(1,2),(2,1),(3,0)].iter().copied().collect();
        let orig = mapping.clone();
        let result = token_swapper(&g, mapping, 1);
        assert!(verify_swaps(orig, &result.swaps), "swaps must solve reversal");
    }

    #[test]
    fn test_cycle_rotation() {
        // Cycle 0-1-2-3-0, token at i is (i+1)%4
        let g = cycle_graph_g(4);
        let mapping: HashMap<NodeId, NodeId> = [(0,1),(1,2),(2,3),(3,0)].iter().copied().collect();
        let orig = mapping.clone();
        let result = token_swapper(&g, mapping, 1);
        assert!(verify_swaps(orig, &result.swaps), "cycle rotation solved");
    }

    #[test]
    fn test_empty_graph() {
        let g = Graph::new(GraphConfig::simple());
        let result = token_swapper(&g, HashMap::new(), 1);
        assert!(result.swaps.is_empty());
    }

    #[test]
    fn test_single_node() {
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node();
        let mapping: HashMap<NodeId, NodeId> = [(0, 0)].iter().copied().collect();
        let result = token_swapper(&g, mapping, 1);
        assert!(result.swaps.is_empty());
    }

    #[test]
    fn test_star_permutation() {
        // Star graph: center=0, leaves=1,2,3
        // node 1 has token 2, node 2 has token 3, node 3 has token 1
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        for i in 1..4u64 { g.add_edge(0, i, None); }
        let mapping: HashMap<NodeId, NodeId> = [(0,0),(1,2),(2,3),(3,1)].iter().copied().collect();
        let orig = mapping.clone();
        let result = token_swapper(&g, mapping, 1);
        assert!(verify_swaps(orig, &result.swaps), "star permutation solved");
    }

    #[test]
    fn test_multiple_trials_consistency() {
        let g = path_graph(5);
        let mapping: HashMap<NodeId, NodeId> = [(0,4),(1,3),(2,2),(3,1),(4,0)].iter().copied().collect();
        let orig = mapping.clone();
        let result = token_swapper(&g, mapping, 5);
        assert!(verify_swaps(orig, &result.swaps), "multi-trial solution must be valid");
    }

    #[test]
    fn test_path_5_random_permutation() {
        // 5-node path, arbitrary permutation
        let g = path_graph(5);
        let mapping: HashMap<NodeId, NodeId> = [(0,2),(1,0),(2,4),(3,1),(4,3)].iter().copied().collect();
        let orig = mapping.clone();
        let result = token_swapper(&g, mapping, 1);
        assert!(verify_swaps(orig, &result.swaps), "arbitrary permutation solved");
    }
}
