import { BinaryDataType } from "../binary/type/BinaryDataType.js";
import { Deque } from "../collection/queue/Deque.js";
import { SquareMatrix } from "../math/matrix/SquareMatrix.js";

/**
 * Produce a distance matrix from an input graph, tracing distances from each
 * node to every node specified in the target vector.
 *
 * Traversal is BFS and each neighbour is only visited once. As a consequence
 * this implementation gives correct shortest-path distances only when every
 * edge has the same weight (typically `edge.weight === 1`). On graphs with
 * heterogeneous weights the returned distances are the weight sum along the
 * BFS tree, which is not the shortest-path distance in general — use
 * Dijkstra or Floyd–Warshall for those cases instead.
 *
 * Edge costs are read from `edge.weight`, so uniform non-1 weights still
 * produce distances scaled by the common weight, but this remains correct
 * only because every path of the same hop count has the same total weight.
 *
 * Output layout: `m[row, col]` is the distance from the node at index `row`
 * to the target node at index `col`. Columns whose index is not in `targets`
 * are left at `POSITIVE_INFINITY` (except the diagonal, which is 0).
 *
 * @see "A Fast Algorithm to Find All-Pairs Shortest Paths in Complex Networks" by Wei Peng et Al. 2012
 * @template T
 * @param {Graph<T>} graph
 * @param {T[]} node_array graph nodes as an array
 * @param {number[]} targets node indices, distances to which need to be calculated
 * @param {Map<T, number>} node_index_map
 * @returns {SquareMatrix}
 */
export function graph_compute_distance_matrix(graph, node_array, targets, node_index_map) {
    const node_count = node_array.length;

    const m_distances = new SquareMatrix(node_count, BinaryDataType.Float32);

    // initialize distances
    m_distances.fill(Number.POSITIVE_INFINITY);

    // fill diagonal
    for (let i = 0; i < node_count; i++) {
        // Distance to self is 0
        m_distances.setCellValue(i, i, 0);
    }

    /**
     *
     * @type {Deque<number>}
     */
    const queue = new Deque();

    for (const target_index of targets) {

        queue.addLast(target_index);

        while (!queue.isEmpty()) {
            const index_1 = queue.removeFirst();
            const node_1 = node_array[index_1];

            const node_1_container = graph.getNodeContainer(node_1);

            const edges = node_1_container.getEdges();
            const edge_count = edges.length;

            for (let i = 0; i < edge_count; i++) {

                const edge = edges[i];

                const node_0 = edge.other(node_1);

                if (!edge.validateTransition(node_0, node_1)) {
                    continue;
                }

                const index_0 = node_index_map.get(node_0);

                // If we haven't visited this neighbor yet, its distance is the current distance + 1
                if (m_distances.getCellValue(index_0, target_index) === Number.POSITIVE_INFINITY) {

                    // Fall back to unit weight when the edge has no explicit weight, so BFS
                    // produces meaningful hop-count distances on plain (non-weighted) edges.
                    const transition_cost = edge.weight !== undefined ? edge.weight : 1;

                    const new_distance = m_distances.getCellValue(index_1, target_index) + transition_cost;

                    m_distances.setCellValue(index_0, target_index, new_distance);

                    queue.addLast(index_0);
                }
            }
        }
    }


    return m_distances;
}