"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DirectedGraph = exports.DirectedEdge = exports.DirectedVertex = void 0;
const abstract_graph_1 = require("./abstract-graph");
const utils_1 = require("../../utils");
class DirectedVertex extends abstract_graph_1.AbstractVertex {
    /**
     * The constructor function initializes a vertex with an optional value.
     * @param {VertexKey} key - The `key` parameter is of type `VertexKey` and represents the identifier of the vertex. It is
     * used to uniquely identify the vertex within a graph or data structure.
     * @param {V} [value] - The "value" parameter is an optional parameter of type V. It is used to initialize the value of the
     * vertex. If no value is provided, the vertex will be initialized with a default value.
     */
    constructor(key, value) {
        super(key, value);
    }
}
exports.DirectedVertex = DirectedVertex;
class DirectedEdge extends abstract_graph_1.AbstractEdge {
    /**
     * The constructor function initializes the source and destination vertexMap of an edge, along with an optional weight
     * and value.
     * @param {VertexKey} src - The `src` parameter is the source vertex ID. It represents the starting point of an edge in
     * a graph.
     * @param {VertexKey} dest - The `dest` parameter represents the destination vertex of an edge. It is of type
     * `VertexKey`, which is likely a unique identifier for a vertex in a graph.
     * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge.
     * @param {E} [value] - The `value` parameter is an optional parameter of type `E`. It represents the value associated with
     * the edge.
     */
    constructor(src, dest, weight, value) {
        super(weight, value);
        this.src = src;
        this.dest = dest;
    }
}
exports.DirectedEdge = DirectedEdge;
/**
 *
 */
class DirectedGraph extends abstract_graph_1.AbstractGraph {
    /**
     * The constructor function initializes an instance of a class.
     */
    constructor() {
        super();
        this._outEdgeMap = new Map();
        this._inEdgeMap = new Map();
    }
    get outEdgeMap() {
        return this._outEdgeMap;
    }
    set outEdgeMap(v) {
        this._outEdgeMap = v;
    }
    get inEdgeMap() {
        return this._inEdgeMap;
    }
    set inEdgeMap(v) {
        this._inEdgeMap = v;
    }
    /**
     * The function creates a new vertex with an optional value and returns it.
     * @param {VertexKey} key - The `key` parameter is the unique identifier for the vertex. It is of type `VertexKey`, which
     * could be a number or a string depending on how you want to identify your vertexMap.
     * @param [value] - The 'value' parameter is an optional value that can be assigned to the vertex. If a value is provided,
     * it will be assigned to the 'value' property of the vertex. If no value is provided, the 'value' property will be
     * assigned the same value as the 'key' parameter
     * @returns a new instance of a DirectedVertex object, casted as type VO.
     */
    createVertex(key, value) {
        return new DirectedVertex(key, value);
    }
    /**
     * The function creates a directed edge between two vertexMap with an optional weight and value.
     * @param {VertexKey} src - The source vertex ID of the edge. It represents the starting point of the edge.
     * @param {VertexKey} dest - The `dest` parameter is the identifier of the destination vertex for the edge.
     * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. If no
     * weight is provided, it defaults to 1.
     * @param [value] - The 'value' parameter is an optional value that can be assigned to the edge. It can be of any type and
     * is used to store additional information or data associated with the edge.
     * @returns a new instance of a DirectedEdge object, casted as type EO.
     */
    createEdge(src, dest, weight, value) {
        return new DirectedEdge(src, dest, weight !== null && weight !== void 0 ? weight : 1, value);
    }
    /**
     * Time Complexity: O(|V|) where |V| is the number of vertexMap
     * Space Complexity: O(1)
     *
     * The `getEdge` function retrieves an edge between two vertexMap based on their source and destination IDs.
     * @param {VO | VertexKey | undefined} srcOrKey - The source vertex or its ID. It can be either a vertex object or a vertex ID.
     * @param {VO | VertexKey | undefined} destOrKey - The `destOrKey` parameter in the `getEdge` function represents the
     * destination vertex of the edge. It can be either a vertex object (`VO`), a vertex ID (`VertexKey`), or `undefined` if the
     * destination is not specified.
     * @returns the first edge found between the source and destination vertexMap, or undefined if no such edge is found.
     */
    getEdge(srcOrKey, destOrKey) {
        let edgeMap = [];
        if (srcOrKey !== undefined && destOrKey !== undefined) {
            const src = this._getVertex(srcOrKey);
            const dest = this._getVertex(destOrKey);
            if (src && dest) {
                const srcOutEdges = this._outEdgeMap.get(src);
                if (srcOutEdges) {
                    edgeMap = srcOutEdges.filter(edge => edge.dest === dest.key);
                }
            }
        }
        return edgeMap[0] || undefined;
    }
    /**
     * Time Complexity: O(|E|) where |E| is the number of edgeMap
     * Space Complexity: O(1)
     *
     * The function removes an edge between two vertexMap in a graph and returns the removed edge.
     * @param {VO | VertexKey} srcOrKey - The source vertex or its ID.
     * @param {VO | VertexKey} destOrKey - The `destOrKey` parameter represents the destination vertex or its ID.
     * @returns the removed edge (EO) if it exists, or undefined if either the source or destination vertex does not exist.
     */
    deleteEdgeSrcToDest(srcOrKey, destOrKey) {
        const src = this._getVertex(srcOrKey);
        const dest = this._getVertex(destOrKey);
        let removed = undefined;
        if (!src || !dest) {
            return undefined;
        }
        const srcOutEdges = this._outEdgeMap.get(src);
        if (srcOutEdges) {
            (0, utils_1.arrayRemove)(srcOutEdges, (edge) => edge.dest === dest.key);
        }
        const destInEdges = this._inEdgeMap.get(dest);
        if (destInEdges) {
            removed = (0, utils_1.arrayRemove)(destInEdges, (edge) => edge.src === src.key)[0] || undefined;
        }
        return removed;
    }
    /**
     * Time Complexity: O(E) where E is the number of edgeMap
     * Space Complexity: O(1)
     *
     * The `deleteEdge` function removes an edge from a graph and returns the removed edge.
     * @param {EO | VertexKey} edgeOrSrcVertexKey - The `edge` parameter can be either an `EO` object (edge object) or
     * a `VertexKey` (key of a vertex).
     * @param {VertexKey} [destVertexKey] - The `destVertexKey` parameter is an optional parameter that
     * represents the key of the destination vertex of the edge. It is used to specify the destination
     * vertex when the `edge` parameter is a vertex key. If `destVertexKey` is not provided, the function
     * assumes that the `edge`
     * @returns the removed edge (EO) or undefined if no edge was removed.
     */
    deleteEdge(edgeOrSrcVertexKey, destVertexKey) {
        let removed = undefined;
        let src, dest;
        if (this.isVertexKey(edgeOrSrcVertexKey)) {
            if (this.isVertexKey(destVertexKey)) {
                src = this._getVertex(edgeOrSrcVertexKey);
                dest = this._getVertex(destVertexKey);
            }
            else {
                return;
            }
        }
        else {
            src = this._getVertex(edgeOrSrcVertexKey.src);
            dest = this._getVertex(edgeOrSrcVertexKey.dest);
        }
        if (src && dest) {
            const srcOutEdges = this._outEdgeMap.get(src);
            if (srcOutEdges && srcOutEdges.length > 0) {
                (0, utils_1.arrayRemove)(srcOutEdges, (edge) => edge.src === src.key && edge.dest === (dest === null || dest === void 0 ? void 0 : dest.key));
            }
            const destInEdges = this._inEdgeMap.get(dest);
            if (destInEdges && destInEdges.length > 0) {
                removed = (0, utils_1.arrayRemove)(destInEdges, (edge) => edge.src === src.key && edge.dest === dest.key)[0];
            }
        }
        return removed;
    }
    /**
     * Time Complexity: O(1) - Constant time for Map operations.
     * Space Complexity: O(1) - Constant space, as it creates only a few variables.
     *
     * The `deleteVertex` function removes a vertex from a graph by its ID or by the vertex object itself.
     * @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can be either a vertex object (`VO`) or a vertex ID
     * (`VertexKey`).
     * @returns The method is returning a boolean value.
     */
    deleteVertex(vertexOrKey) {
        let vertexKey;
        let vertex;
        if (this.isVertexKey(vertexOrKey)) {
            vertex = this.getVertex(vertexOrKey);
            vertexKey = vertexOrKey;
        }
        else {
            vertex = vertexOrKey;
            vertexKey = this._getVertexKey(vertexOrKey);
        }
        if (vertex) {
            const neighbors = this.getNeighbors(vertex);
            for (const neighbor of neighbors) {
                // this._inEdgeMap.delete(neighbor);
                this.deleteEdgeSrcToDest(vertex, neighbor);
            }
            this._outEdgeMap.delete(vertex);
            this._inEdgeMap.delete(vertex);
        }
        return this._vertexMap.delete(vertexKey);
    }
    /**
     * Time Complexity: O(|E|) where |E| is the number of edgeMap
     * Space Complexity: O(1)
     *
     * The function removes edgeMap between two vertexMap and returns the removed edgeMap.
     * @param {VertexKey | VO} v1 - The parameter `v1` can be either a `VertexKey` or a `VO`. A `VertexKey` represents the
     * unique identifier of a vertex in a graph, while `VO` represents the actual vertex object.
     * @param {VertexKey | VO} v2 - The parameter `v2` represents either a `VertexKey` or a `VO` object. It is used to specify
     * the second vertex in the edge that needs to be removed.
     * @returns an array of removed edgeMap (EO[]).
     */
    deleteEdgesBetween(v1, v2) {
        const removed = [];
        if (v1 && v2) {
            const v1ToV2 = this.deleteEdgeSrcToDest(v1, v2);
            const v2ToV1 = this.deleteEdgeSrcToDest(v2, v1);
            if (v1ToV2)
                removed.push(v1ToV2);
            if (v2ToV1)
                removed.push(v2ToV1);
        }
        return removed;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function `incomingEdgesOf` returns an array of incoming edgeMap for a given vertex or vertex ID.
     * @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can be either a vertex object (`VO`) or a vertex ID
     * (`VertexKey`).
     * @returns The method `incomingEdgesOf` returns an array of edgeMap (`EO[]`).
     */
    incomingEdgesOf(vertexOrKey) {
        const target = this._getVertex(vertexOrKey);
        if (target) {
            return this.inEdgeMap.get(target) || [];
        }
        return [];
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function `outgoingEdgesOf` returns an array of outgoing edgeMap from a given vertex or vertex ID.
     * @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can accept either a vertex object (`VO`) or a vertex ID
     * (`VertexKey`).
     * @returns The method `outgoingEdgesOf` returns an array of edgeMap (`EO[]`).
     */
    outgoingEdgesOf(vertexOrKey) {
        const target = this._getVertex(vertexOrKey);
        if (target) {
            return this._outEdgeMap.get(target) || [];
        }
        return [];
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "degreeOf" returns the total degree of a vertex, which is the sum of its out-degree and in-degree.
     * @param {VertexKey | VO} vertexOrKey - The parameter `vertexOrKey` can be either a `VertexKey` or a `VO`.
     * @returns The sum of the out-degree and in-degree of the specified vertex or vertex ID.
     */
    degreeOf(vertexOrKey) {
        return this.outDegreeOf(vertexOrKey) + this.inDegreeOf(vertexOrKey);
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "inDegreeOf" returns the number of incoming edgeMap for a given vertex.
     * @param {VertexKey | VO} vertexOrKey - The parameter `vertexOrKey` can be either a `VertexKey` or a `VO`.
     * @returns The number of incoming edgeMap of the specified vertex or vertex ID.
     */
    inDegreeOf(vertexOrKey) {
        return this.incomingEdgesOf(vertexOrKey).length;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function `outDegreeOf` returns the number of outgoing edgeMap from a given vertex.
     * @param {VertexKey | VO} vertexOrKey - The parameter `vertexOrKey` can be either a `VertexKey` or a `VO`.
     * @returns The number of outgoing edgeMap from the specified vertex or vertex ID.
     */
    outDegreeOf(vertexOrKey) {
        return this.outgoingEdgesOf(vertexOrKey).length;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "edgesOf" returns an array of both outgoing and incoming edgeMap of a given vertex or vertex ID.
     * @param {VertexKey | VO} vertexOrKey - The parameter `vertexOrKey` can be either a `VertexKey` or a `VO`.
     * @returns The function `edgesOf` returns an array of edgeMap.
     */
    edgesOf(vertexOrKey) {
        return [...this.outgoingEdgesOf(vertexOrKey), ...this.incomingEdgesOf(vertexOrKey)];
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "getEdgeSrc" returns the source vertex of an edge, or undefined if the edge does not exist.
     * @param {EO} e - The parameter "e" is of type EO, which represents an edge in a graph.
     * @returns either a vertex object (VO) or undefined.
     */
    getEdgeSrc(e) {
        return this._getVertex(e.src);
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "getEdgeDest" returns the destination vertex of an edge.
     * @param {EO} e - The parameter "e" is of type "EO", which represents an edge in a graph.
     * @returns either a vertex object of type VO or undefined.
     */
    getEdgeDest(e) {
        return this._getVertex(e.dest);
    }
    /**
     * Time Complexity: O(|E|) where |E| is the number of edgeMap
     * Space Complexity: O(1)
     *
     * The function `getDestinations` returns an array of destination vertexMap connected to a given vertex.
     * @param {VO | VertexKey | undefined} vertex - The `vertex` parameter represents the starting vertex from which we want to
     * find the destinations. It can be either a `VO` object, a `VertexKey` value, or `undefined`.
     * @returns an array of vertexMap (VO[]).
     */
    getDestinations(vertex) {
        if (vertex === undefined) {
            return [];
        }
        const destinations = [];
        const outgoingEdges = this.outgoingEdgesOf(vertex);
        for (const outEdge of outgoingEdges) {
            const child = this.getEdgeDest(outEdge);
            if (child) {
                destinations.push(child);
            }
        }
        return destinations;
    }
    /**
     * Time Complexity: O(|V| + |E|) where |V| is the number of vertexMap and |E| is the number of edgeMap
     * Space Complexity: O(|V|)
     *
     * The `topologicalSort` function performs a topological sort on a graph and returns an array of vertexMap or vertex IDs
     * in the sorted order, or undefined if the graph contains a cycle.
     * @param {'vertex' | 'key'} [propertyName] - The `propertyName` parameter is an optional parameter that specifies the
     * property to use for sorting the vertexMap. It can have two possible values: 'vertex' or 'key'. If 'vertex' is
     * specified, the vertexMap themselves will be used for sorting. If 'key' is specified, the ids of
     * @returns an array of vertexMap or vertex IDs in topological order. If there is a cycle in the graph, it returns undefined.
     */
    topologicalSort(propertyName) {
        propertyName = propertyName !== null && propertyName !== void 0 ? propertyName : 'key';
        // When judging whether there is a cycle in the undirected graph, all nodes with degree of **<= 1** are enqueued
        // When judging whether there is a cycle in the directed graph, all nodes with **in degree = 0** are enqueued
        const statusMap = new Map();
        for (const entry of this.vertexMap) {
            statusMap.set(entry[1], 0);
        }
        let sorted = [];
        let hasCycle = false;
        const dfs = (cur) => {
            statusMap.set(cur, 1);
            const children = this.getDestinations(cur);
            for (const child of children) {
                const childStatus = statusMap.get(child);
                if (childStatus === 0) {
                    dfs(child);
                }
                else if (childStatus === 1) {
                    hasCycle = true;
                }
            }
            statusMap.set(cur, 2);
            sorted.push(cur);
        };
        for (const entry of this.vertexMap) {
            if (statusMap.get(entry[1]) === 0) {
                dfs(entry[1]);
            }
        }
        if (hasCycle)
            return undefined;
        if (propertyName === 'key')
            sorted = sorted.map(vertex => (vertex instanceof DirectedVertex ? vertex.key : vertex));
        return sorted.reverse();
    }
    /**
     * Time Complexity: O(|E|) where |E| is the number of edgeMap
     * Space Complexity: O(|E|)
     *
     * The `edgeSet` function returns an array of all the edgeMap in the graph.
     * @returns The `edgeSet()` method returns an array of edgeMap (`EO[]`).
     */
    edgeSet() {
        let edgeMap = [];
        this._outEdgeMap.forEach(outEdges => {
            edgeMap = [...edgeMap, ...outEdges];
        });
        return edgeMap;
    }
    /**
     * Time Complexity: O(|E|) where |E| is the number of edgeMap
     * Space Complexity: O(1)
     *
     * The function `getNeighbors` returns an array of neighboring vertexMap of a given vertex or vertex ID in a graph.
     * @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can be either a vertex object (`VO`) or a vertex ID
     * (`VertexKey`).
     * @returns an array of vertexMap (VO[]).
     */
    getNeighbors(vertexOrKey) {
        const neighbors = [];
        const vertex = this._getVertex(vertexOrKey);
        if (vertex) {
            const outEdges = this.outgoingEdgesOf(vertex);
            for (const outEdge of outEdges) {
                const neighbor = this._getVertex(outEdge.dest);
                // TODO after no-non-undefined-assertion not ensure the logic
                if (neighbor) {
                    neighbors.push(neighbor);
                }
            }
        }
        return neighbors;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "getEndsOfEdge" returns the source and destination vertexMap of an edge if it exists in the graph,
     * otherwise it returns undefined.
     * @param {EO} edge - The parameter `edge` is of type `EO`, which represents an edge in a graph.
     * @returns The function `getEndsOfEdge` returns an array containing two vertexMap `[VO, VO]` if the edge exists in the
     * graph. If the edge does not exist, it returns `undefined`.
     */
    getEndsOfEdge(edge) {
        if (!this.hasEdge(edge.src, edge.dest)) {
            return undefined;
        }
        const v1 = this._getVertex(edge.src);
        const v2 = this._getVertex(edge.dest);
        if (v1 && v2) {
            return [v1, v2];
        }
        else {
            return undefined;
        }
    }
    /**
     * The isEmpty function checks if the graph is empty.
     *
     * @return A boolean value
     */
    isEmpty() {
        return this.vertexMap.size === 0 && this.inEdgeMap.size === 0 && this.outEdgeMap.size === 0;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The clear function resets the vertex map, in-edge map, and out-edge map.
     */
    clear() {
        this._vertexMap = new Map();
        this._inEdgeMap = new Map();
        this._outEdgeMap = new Map();
    }
    /**
     * The clone function creates a new DirectedGraph object with the same vertices and edges as the original.
     *
     * @return A new instance of the directedgraph class
     */
    clone() {
        const cloned = new DirectedGraph();
        cloned.vertexMap = new Map(this.vertexMap);
        cloned.inEdgeMap = new Map(this.inEdgeMap);
        cloned.outEdgeMap = new Map(this.outEdgeMap);
        return cloned;
    }
    /**
     *  Time Complexity: O(V + E)
     *  Space Complexity: O(V)
     *  Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
     *  Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs.
     *
     * The function `tarjan` implements the Tarjan's algorithm to find strongly connected components in a
     * graph.
     * @returns The function `tarjan()` returns an object with three properties: `dfnMap`, `lowMap`, and
     * `SCCs`.
     */
    tarjan() {
        const dfnMap = new Map();
        const lowMap = new Map();
        const SCCs = new Map();
        let time = 0;
        const stack = [];
        const inStack = new Set();
        const dfs = (vertex) => {
            dfnMap.set(vertex, time);
            lowMap.set(vertex, time);
            time++;
            stack.push(vertex);
            inStack.add(vertex);
            const neighbors = this.getNeighbors(vertex);
            for (const neighbor of neighbors) {
                if (!dfnMap.has(neighbor)) {
                    dfs(neighbor);
                    lowMap.set(vertex, Math.min(lowMap.get(vertex), lowMap.get(neighbor)));
                }
                else if (inStack.has(neighbor)) {
                    lowMap.set(vertex, Math.min(lowMap.get(vertex), dfnMap.get(neighbor)));
                }
            }
            if (dfnMap.get(vertex) === lowMap.get(vertex)) {
                const SCC = [];
                let poppedVertex;
                do {
                    poppedVertex = stack.pop();
                    inStack.delete(poppedVertex);
                    SCC.push(poppedVertex);
                } while (poppedVertex !== vertex);
                SCCs.set(SCCs.size, SCC);
            }
        };
        for (const vertex of this.vertexMap.values()) {
            if (!dfnMap.has(vertex)) {
                dfs(vertex);
            }
        }
        return { dfnMap, lowMap, SCCs };
    }
    /**
     * Time Complexity: O(V + E) - Depends on the implementation (Tarjan's algorithm).
     * Space Complexity: O(V) - Depends on the implementation (Tarjan's algorithm).
     *
     * The function returns a map that associates each vertex object with its corresponding depth-first
     * number.
     * @returns A Map object with keys of type VO and values of type number.
     */
    getDFNMap() {
        return this.tarjan().dfnMap;
    }
    /**
     * The function returns a Map object that contains the low values of each vertex in a Tarjan
     * algorithm.
     * @returns The method `getLowMap()` is returning a `Map` object with keys of type `VO` and values of
     * type `number`.
     */
    getLowMap() {
        return this.tarjan().lowMap;
    }
    /**
     * The function "getSCCs" returns a map of strongly connected components (SCCs) using the Tarjan
     * algorithm.
     * @returns a map where the keys are numbers and the values are arrays of VO objects.
     */
    getSCCs() {
        return this.tarjan().SCCs;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function `_addEdge` adds an edge to a graph if the source and destination vertexMap exist.
     * @param {EO} edge - The parameter `edge` is of type `EO`, which represents an edge in a graph. It is the edge that
     * needs to be added to the graph.
     * @returns a boolean value. It returns true if the edge was successfully added to the graph, and false if either the
     * source or destination vertex does not exist in the graph.
     */
    _addEdge(edge) {
        if (!(this.hasVertex(edge.src) && this.hasVertex(edge.dest))) {
            return false;
        }
        const srcVertex = this._getVertex(edge.src);
        const destVertex = this._getVertex(edge.dest);
        // TODO after no-non-undefined-assertion not ensure the logic
        if (srcVertex && destVertex) {
            const srcOutEdges = this._outEdgeMap.get(srcVertex);
            if (srcOutEdges) {
                srcOutEdges.push(edge);
            }
            else {
                this._outEdgeMap.set(srcVertex, [edge]);
            }
            const destInEdges = this._inEdgeMap.get(destVertex);
            if (destInEdges) {
                destInEdges.push(edge);
            }
            else {
                this._inEdgeMap.set(destVertex, [edge]);
            }
            return true;
        }
        else {
            return false;
        }
    }
}
exports.DirectedGraph = DirectedGraph;
//# sourceMappingURL=directed-graph.js.map