"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StedGraphGenerator = void 0;
const graphlib_1 = require("graphlib");
const deepClone = require('lodash/cloneDeep');
const debug = require('debug')('sted:sted-graph-generator');
class StedGraphGenerator {
    constructor(graphData) {
        this.stedGraph = graphData;
        this.supplyNodes = Array();
        this.exhaustNodes = Array();
        this.otherNodes = Array();
        this.cutoffNodes = Array();
    }
    print() {
        console.log(JSON.stringify(this.stedGraph));
    }
    generateGraph(stedNodeList) {
        var generatedGraph = new graphlib_1.Graph({ directed: true, multigraph: true });
        stedNodeList.forEach(stedNode => {
            generatedGraph.setNode(stedNode.name, stedNode.name);
            stedNode.outlets.forEach(out => generatedGraph.setEdge(stedNode.name, out.dstNode, `${stedNode.name}-${out.name}-${out.dstNode}`, out.name));
        });
        return generatedGraph;
    }
    prepareStedNodes() {
        let genGraph = this.generateGraph(this.stedGraph.stedNodes);
        let tarjanResults = graphlib_1.alg.tarjan(genGraph);
        // find supply & exhaust modules first
        let foundNode;
        tarjanResults.forEach(tarjanNodes => {
            tarjanNodes.forEach(tarjanNode => {
                // find supply modules & push them to supplyNodes array
                foundNode = this.stedGraph.stedNodes.find(stedNode => tarjanNode === stedNode.name);
                if (foundNode && foundNode.type.match(StedGraphGenerator.supplyPrefix)) {
                    this.supplyNodes.push(foundNode.name);
                }
                else if (foundNode && foundNode.type.match(StedGraphGenerator.exhaustPrefix)) {
                    this.exhaustNodes.push(foundNode.name);
                }
                else {
                    this.otherNodes.push(foundNode.name);
                }
            });
        });
        // find and arrange other nodes except supply & exhaust
        let preOrderResult = [];
        preOrderResult = graphlib_1.alg.preorder(genGraph, this.otherNodes);
        preOrderResult = preOrderResult.filter(item => !this.supplyNodes.includes(item) && !this.exhaustNodes.includes(item));
        this.otherNodes = preOrderResult;
        let stedArrangedGraph = { stedNodes: [] };
        // reorder the nodes of sted graph by prefix & sufix modules
        this.supplyNodes
            .concat(this.otherNodes)
            .concat(this.exhaustNodes)
            .forEach(nodeName => {
            stedArrangedGraph.stedNodes.push(this.stedGraph.stedNodes.find(stedNode => nodeName === stedNode.name));
        });
        return stedArrangedGraph;
    }
    findAllCycles(stedGraph) {
        let referenceGraph = this.generateGraph(stedGraph.stedNodes);
        let edges = {};
        referenceGraph.nodes().forEach(node => {
            edges[node] = referenceGraph.successors(node) || [];
        });
        const findCycle = require('find-cycle/directed');
        const getConnectedNodes = node => referenceGraph.successors(node) || [];
        var resultCycles = [];
        let cycle = [];
        do {
            cycle = findCycle(referenceGraph.nodes(), getConnectedNodes);
            if (cycle) {
                resultCycles.push(cycle);
                const cycleNodeEdges = referenceGraph.nodeEdges(cycle[0], cycle[1]);
                if (cycleNodeEdges) {
                    cycleNodeEdges.forEach(edge => {
                        referenceGraph.removeEdge(cycle[0], cycle[1], edge.name);
                    });
                }
            }
        } while (cycle);
        return resultCycles;
    }
    // Removed the following codes someday...
    // findSubCycles(cycles: string[][], referGraph: Graph) {
    //   var finalSubCycles: string[][] = []
    //   // finalSubCycles.push({ completed: false, cycle: [startCycle] })
    //   cycles.forEach(cycle => {
    //     let foundSubCycle = []
    //     let done = false
    //     const startCycle = cycle[0]
    //     while (done) {
    //       foundSubCycle.push(startCycle)
    //       let cycleSuccessors
    //       do {
    //         cycleSuccessors = referGraph.successors(startCycle)
    //         if (cycleSuccessors) {
    //           for (let cycleSuccessor of cycleSuccessors) {
    //             foundSubCycle.push(cycleSuccessor)
    //             const processed = finalSubCycles.every(subCycle => cycleSuccessor.every(succ => subCycle.includes(succ)))
    //             if (processed) {
    //               break
    //             }
    //           }
    //         }
    //       } while (cycleSuccessors)
    //     }
    //   })
    //   // start with cycles[0]
    //   const startCycle = cycles[0]
    //   let subCycles: [] = []
    //   while (startCycle) {
    //     const cycleSuccessors = referGraph.successors(startCycle)
    //     if (cycleSuccessors) {
    //       cycleSuccessors.forEach(successor => {})
    //     }
    //   }
    //   return subCycles
    // }
    findCycles(stedGraph) {
        let reorderedCycles = [];
        // transform its own json graph to graphlib graph
        let genGraph = this.generateGraph(stedGraph.stedNodes);
        // find cycles
        // let oneStepCycles: string[][] = alg.findCycles(this.generateGraph(stedGraph.stedNodes))
        // let cycles = this.findSubCycles(oneStepCycles, genGraph)
        let cycles = this.findAllCycles(stedGraph);
        cycles.forEach((cycle, idx) => {
            let preOrderResult = [];
            preOrderResult = graphlib_1.alg.preorder(genGraph, cycle);
            preOrderResult = preOrderResult.filter(item => cycle.includes(item));
            reorderedCycles[idx] = preOrderResult;
        });
        return reorderedCycles;
    }
    sortNodes(stedBaseGraph) {
        var genGraph = this.generateGraph(stedBaseGraph.stedNodes);
        return graphlib_1.alg.topsort(genGraph);
    }
    lineUpTwoNodes(nodes, refGraph) {
        let node0 = refGraph.stedNodes.find(node => node.name === nodes[0]);
        let node1 = refGraph.stedNodes.find(node => node.name === nodes[1]);
        return node0.outlets.find(outl => outl.dstNode === nodes[1]) ? [node0, node1] : [node1, node0];
    }
    sortGraph(cycles, stedNodes) {
        // generate a Graph instance using STED nodes
        let generatedGraph = this.generateGraph(stedNodes);
        // remove cycled edges to make it acyclic
        cycles.forEach(cycle => {
            // clone generatedGraph
            let copiedGraph = Object.assign(Object.create(Object.getPrototypeOf(generatedGraph)), generatedGraph);
            // find one edge cut to  make this graph acyclic
            let dstCycleIdx = 0;
            let srcCycleIdx = cycle.length - 1;
            let targetNodeEdges = copiedGraph.nodeEdges(cycle[srcCycleIdx], cycle[dstCycleIdx]);
            if (targetNodeEdges) {
                let targetNodeEdge = targetNodeEdges.filter(edge => edge.v === cycle[srcCycleIdx] && edge.w === cycle[dstCycleIdx]);
                let tempGraph = copiedGraph.removeEdge(cycle[srcCycleIdx], cycle[dstCycleIdx], targetNodeEdge[0].name);
                generatedGraph = tempGraph;
                this.cutoffNodes.push([srcCycleIdx, dstCycleIdx]);
            }
            // for (let cycleIdx = 0; cycleIdx < cycle.length; cycleIdx++) {
            //   let dstCycleIdx = cycleIdx + 1 >= cycle.index ? 0 : cycleIdx + 1
            //   let targetNodeEdges = copiedGraph.nodeEdges(cycle[cycleIdx], cycle[dstCycleIdx])
            //   if (targetNodeEdges) {
            //     let targetNodeEdge = targetNodeEdges.filter(
            //       edge => edge.v === cycle[cycleIdx] && edge.w === cycle[dstCycleIdx]
            //     )
            //     let tempGraph = copiedGraph.removeEdge(cycle[cycleIdx], cycle[dstCycleIdx], targetNodeEdge[0].name)
            //     // if (alg.isAcyclic(tempGraph))
            //     {
            //       generatedGraph = tempGraph
            //       this.cutoffNodes.push([cycleIdx, dstCycleIdx])
            //       break
            //     }
            //   }
            // }
            // original
            // let targetNodeEdges = generatedGraph.nodeEdges(cycle[0], cycle[1])
            // if (targetNodeEdges) {
            //   let targetNodeEdge = targetNodeEdges.filter(edge => edge.v === cycle[0] && edge.w === cycle[1])
            //   generatedGraph = generatedGraph.removeEdge(cycle[0], cycle[1], targetNodeEdge[0].name)
            // }
        });
        // check if the Graph is a acyclic and apply sorting to it.
        let sortedList = graphlib_1.alg.isAcyclic(generatedGraph) ? graphlib_1.alg.topsort(generatedGraph) : [];
        // sort the STED graph based on Graph node sequence by sorting algorithm
        sortedList.forEach((element, sortedIndex) => {
            let originalIndex = stedNodes.findIndex(node => node.name == element);
            if (originalIndex > 0) {
                stedNodes.splice(sortedIndex, 0, stedNodes.splice(originalIndex, 1)[0]);
            }
        });
    }
    _changeNodeOrder(stedGraph, prevNodeIndex, toIndex, targetNode) {
        stedGraph.stedNodes.splice(prevNodeIndex, 1);
        stedGraph.stedNodes.splice(toIndex, 0, targetNode);
    }
    _arrangeGraphWithCuttingEdges(stedGraph, cycles) {
        let arrangedGraph = deepClone(stedGraph);
        cycles.forEach((cycle, idx) => {
            // choose first two nodes as a cutting edge
            // TODO: need to check if there are only two nodes with a cycle?
            let [nextNode, prevNode] = this.lineUpTwoNodes([cycle[this.cutoffNodes[idx][0]], cycle[this.cutoffNodes[idx][1]]], arrangedGraph);
            let foundIndex = arrangedGraph.stedNodes.findIndex(node => node.name == nextNode.name);
            let foundNode = arrangedGraph.stedNodes[foundIndex];
            foundNode.inlets.forEach(inlet => {
                if (inlet.srcNode == prevNode.name) {
                    inlet.srcNode = '';
                    inlet.srcEdge = '';
                }
            });
        });
        return arrangedGraph;
    }
    _reorderStedGraph(tempGraph, baseGraph) {
        //let reorderedStedGraph: StedGraph = deepClone(tempGraph)
        tempGraph.stedNodes.forEach((node, idx) => {
            let foundIndexList = [];
            if (idx > 0) {
                node.inlets.forEach(inlet => {
                    let foundIndex = tempGraph.stedNodes.findIndex(node => inlet.srcNode == node.name);
                    foundIndexList.push(foundIndex);
                });
                let maxInletIdx = Math.max(...foundIndexList);
                // 그래프의 경우에 따라서, sorting이 되었지만, 실제 계산에 필요한 순서가 맞지 않을 수 있다. (자세한 사항은 연구 노트에 명시 예정)
                // 이와 같은 경우를 극복하기 위해서,
                // 각 노드의 inlet의 srcNode의 index를 찾아서, 현재 노드의 index보다 작은 경우에는, 현재 노드를 inlet의 srcNode의 index보다 뒤로 보내는 작업을 수행한다.
                if (maxInletIdx > idx) {
                    this._changeNodeOrder(baseGraph, idx, maxInletIdx, node);
                }
            }
        });
        return baseGraph;
    }
    run() {
        let stedGraph = this.prepareStedNodes();
        let cycles = this.findCycles(stedGraph);
        debug('cycles: ', cycles);
        // clone input graph
        //var newGraph = deepClone(stedGraph)
        // sort the graph
        this.sortGraph(cycles, stedGraph.stedNodes);
        debug('sorted graph: ', stedGraph);
        // arrange the graph with cutting edges
        let tempGraph = this._arrangeGraphWithCuttingEdges(stedGraph, cycles);
        // change some sequence of the nodes for some exceptional cases
        stedGraph = this._reorderStedGraph(tempGraph, stedGraph);
        // insert recycle node between cycled nodes
        cycles.forEach((cycle, idx) => {
            var _a, _b, _c, _d, _e, _f;
            // choose first two nodes as a cutting edge
            // TODO: need to check if there are only two nodes with a cycle?
            let [nextNode, prevNode] = this.lineUpTwoNodes([cycle[this.cutoffNodes[idx][0]], cycle[this.cutoffNodes[idx][1]]], stedGraph);
            // // CHECK LATER: adjust the sequence of the final node array
            // const prevNodeIndex = stedGraph.stedNodes.indexOf(prevNode)
            // const toIndex = stedGraph.stedNodes.length
            // stedGraph.stedNodes.splice(prevNodeIndex, 1)
            // stedGraph.stedNodes.splice(toIndex, 0, prevNode)
            // create new node name(prevName_nextName)
            let cycleNodeName = prevNode.name + '_' + nextNode.name;
            debug('[DEBUG] cycleNodeName: ', cycleNodeName);
            // TODO: chekc if two nodes have two cycles simultaneously..
            //  if so, we need to set up an inlet and an outlet based on both nodes(prevNode & nextNode)
            // append new cycle node to the exsiting graph
            stedGraph.stedNodes.push({
                name: cycleNodeName,
                type: StedGraphGenerator.recycleNodeName,
                params: {},
                inlets: [
                    {
                        name: (_a = prevNode.outlets.find(outlet => outlet.dstNode.toString() === nextNode.name.toString())) === null || _a === void 0 ? void 0 : _a.name,
                        kind: 'InStr',
                        type: (_b = prevNode.outlets.find(outlet => outlet.dstNode.toString() === nextNode.name.toString())) === null || _b === void 0 ? void 0 : _b.type,
                        srcNode: prevNode.name,
                        srcEdge: (_c = prevNode.outlets.find(outlet => outlet.dstNode.toString() === nextNode.name.toString())) === null || _c === void 0 ? void 0 : _c.kind
                    }
                ],
                outlets: [
                    {
                        name: (_d = nextNode.inlets.find(inlet => inlet.srcNode.toString() === prevNode.name.toString())) === null || _d === void 0 ? void 0 : _d.name,
                        kind: 'OutStr',
                        type: (_e = nextNode.inlets.find(inlet => inlet.srcNode.toString() === prevNode.name.toString())) === null || _e === void 0 ? void 0 : _e.type,
                        dstNode: nextNode.name,
                        dstEdge: (_f = nextNode.inlets.find(inlet => inlet.srcNode.toString() === prevNode.name.toString())) === null || _f === void 0 ? void 0 : _f.kind
                    }
                ]
            });
            // update the adjacent nodes for a new generated node
            prevNode.outlets.map(outl => {
                if (outl.dstNode.toString() === nextNode.name.toString()) {
                    outl.dstNode = cycleNodeName;
                    outl.dstEdge = 'InStr';
                }
            });
            nextNode.inlets.map(inl => {
                if (inl.srcNode.toString() === prevNode.name.toString()) {
                    inl.srcNode = cycleNodeName;
                    inl.srcEdge = 'OutStr';
                }
            });
        });
        return stedGraph;
        // reorder the STED graph based on the node sequence for some exceptional cases
        // let orderedStedGraph = this._reorderStedGraph(stedGraph)
        // return orderedStedGraph
    }
}
exports.StedGraphGenerator = StedGraphGenerator;
StedGraphGenerator.recycleNodeName = 'Recycle';
StedGraphGenerator.supplyPrefix = /[me]s-[a-z-]*/;
StedGraphGenerator.exhaustPrefix = /[me]e-[a-z-]*/;
//# sourceMappingURL=sted-graph-generator.js.map