"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var Node_1 = __importDefault(require("./Node"));
var Edge_1 = __importDefault(require("./Edge"));
var EdgeRing_1 = __importDefault(require("./EdgeRing"));
var meta_1 = require("@turf/meta");
var invariant_1 = require("@turf/invariant");
/**
 * Validates the geoJson.
 *
 * @param {GeoJSON} geoJson - input geoJson.
 * @throws {Error} if geoJson is invalid.
 */
function validateGeoJson(geoJson) {
    if (!geoJson)
        throw new Error("No geojson passed");
    if (geoJson.type !== "FeatureCollection" &&
        geoJson.type !== "GeometryCollection" &&
        geoJson.type !== "MultiLineString" &&
        geoJson.type !== "LineString" &&
        geoJson.type !== "Feature")
        throw new Error("Invalid input type '" + geoJson.type + "'. Geojson must be FeatureCollection, GeometryCollection, LineString, MultiLineString or Feature");
}
/**
 * Represents a planar graph of edges and nodes that can be used to compute a polygonization.
 *
 * Although, this class is inspired by GEOS's `geos::operation::polygonize::PolygonizeGraph`,
 * it isn't a rewrite. As regards algorithm, this class implements the same logic, but it
 * isn't a javascript transcription of the C++ source.
 *
 * This graph is directed (both directions are created)
 */
var Graph = /** @class */ (function () {
    function Graph() {
        this.edges = []; //< {Edge[]} dirEdges
        // The key is the `id` of the Node (ie: coordinates.join(','))
        this.nodes = {};
    }
    /**
     * Creates a graph from a GeoJSON.
     *
     * @param {FeatureCollection<LineString>} geoJson - it must comply with the restrictions detailed in the index
     * @returns {Graph} - The newly created graph
     * @throws {Error} if geoJson is invalid.
     */
    Graph.fromGeoJson = function (geoJson) {
        validateGeoJson(geoJson);
        var graph = new Graph();
        meta_1.flattenEach(geoJson, function (feature) {
            invariant_1.featureOf(feature, "LineString", "Graph::fromGeoJson");
            // When a LineString if formed by many segments, split them
            meta_1.coordReduce(feature, function (prev, cur) {
                if (prev) {
                    var start = graph.getNode(prev), end = graph.getNode(cur);
                    graph.addEdge(start, end);
                }
                return cur;
            });
        });
        return graph;
    };
    /**
     * Creates or get a Node.
     *
     * @param {number[]} coordinates - Coordinates of the node
     * @returns {Node} - The created or stored node
     */
    Graph.prototype.getNode = function (coordinates) {
        var id = Node_1.default.buildId(coordinates);
        var node = this.nodes[id];
        if (!node)
            node = this.nodes[id] = new Node_1.default(coordinates);
        return node;
    };
    /**
     * Adds an Edge and its symetricall.
     *
     * Edges are added symetrically, i.e.: we also add its symetric
     *
     * @param {Node} from - Node which starts the Edge
     * @param {Node} to - Node which ends the Edge
     */
    Graph.prototype.addEdge = function (from, to) {
        var edge = new Edge_1.default(from, to), symetricEdge = edge.getSymetric();
        this.edges.push(edge);
        this.edges.push(symetricEdge);
    };
    /**
     * Removes Dangle Nodes (nodes with grade 1).
     */
    Graph.prototype.deleteDangles = function () {
        var _this = this;
        Object.keys(this.nodes)
            .map(function (id) { return _this.nodes[id]; })
            .forEach(function (node) { return _this._removeIfDangle(node); });
    };
    /**
     * Check if node is dangle, if so, remove it.
     *
     * It calls itself recursively, removing a dangling node might cause another dangling node
     *
     * @param {Node} node - Node to check if it's a dangle
     */
    Graph.prototype._removeIfDangle = function (node) {
        var _this = this;
        // As edges are directed and symetrical, we count only innerEdges
        if (node.innerEdges.length <= 1) {
            var outerNodes = node.getOuterEdges().map(function (e) { return e.to; });
            this.removeNode(node);
            outerNodes.forEach(function (n) { return _this._removeIfDangle(n); });
        }
    };
    /**
     * Delete cut-edges (bridge edges).
     *
     * The graph will be traversed, all the edges will be labeled according the ring
     * in which they are. (The label is a number incremented by 1). Edges with the same
     * label are cut-edges.
     */
    Graph.prototype.deleteCutEdges = function () {
        var _this = this;
        this._computeNextCWEdges();
        this._findLabeledEdgeRings();
        // Cut-edges (bridges) are edges where both edges have the same label
        this.edges.forEach(function (edge) {
            if (edge.label === edge.symetric.label) {
                _this.removeEdge(edge.symetric);
                _this.removeEdge(edge);
            }
        });
    };
    /**
     * Set the `next` property of each Edge.
     *
     * The graph will be transversed in a CW form, so, we set the next of the symetrical edge as the previous one.
     * OuterEdges are sorted CCW.
     *
     * @param {Node} [node] - If no node is passed, the function calls itself for every node in the Graph
     */
    Graph.prototype._computeNextCWEdges = function (node) {
        var _this = this;
        if (typeof node === "undefined") {
            Object.keys(this.nodes).forEach(function (id) {
                return _this._computeNextCWEdges(_this.nodes[id]);
            });
        }
        else {
            node.getOuterEdges().forEach(function (edge, i) {
                node.getOuterEdge((i === 0 ? node.getOuterEdges().length : i) - 1).symetric.next = edge;
            });
        }
    };
    /**
     * Computes the next edge pointers going CCW around the given node, for the given edgering label.
     *
     * This algorithm has the effect of converting maximal edgerings into minimal edgerings
     *
     * XXX: method literally transcribed from `geos::operation::polygonize::PolygonizeGraph::computeNextCCWEdges`,
     * could be written in a more javascript way.
     *
     * @param {Node} node - Node
     * @param {number} label - Ring's label
     */
    Graph.prototype._computeNextCCWEdges = function (node, label) {
        var edges = node.getOuterEdges();
        var firstOutDE, prevInDE;
        for (var i = edges.length - 1; i >= 0; --i) {
            var de = edges[i], sym = de.symetric, outDE = void 0, inDE = void 0;
            if (de.label === label)
                outDE = de;
            if (sym.label === label)
                inDE = sym;
            if (!outDE || !inDE)
                // This edge is not in edgering
                continue;
            if (inDE)
                prevInDE = inDE;
            if (outDE) {
                if (prevInDE) {
                    prevInDE.next = outDE;
                    prevInDE = undefined;
                }
                if (!firstOutDE)
                    firstOutDE = outDE;
            }
        }
        if (prevInDE)
            prevInDE.next = firstOutDE;
    };
    /**
     * Finds rings and labels edges according to which rings are.
     *
     * The label is a number which is increased for each ring.
     *
     * @returns {Edge[]} edges that start rings
     */
    Graph.prototype._findLabeledEdgeRings = function () {
        var edgeRingStarts = [];
        var label = 0;
        this.edges.forEach(function (edge) {
            if (edge.label >= 0)
                return;
            edgeRingStarts.push(edge);
            var e = edge;
            do {
                e.label = label;
                e = e.next;
            } while (!edge.isEqual(e));
            label++;
        });
        return edgeRingStarts;
    };
    /**
     * Computes the EdgeRings formed by the edges in this graph.
     *
     * @returns {EdgeRing[]} - A list of all the EdgeRings in the graph.
     */
    Graph.prototype.getEdgeRings = function () {
        var _this = this;
        this._computeNextCWEdges();
        // Clear labels
        this.edges.forEach(function (edge) {
            edge.label = undefined;
        });
        this._findLabeledEdgeRings().forEach(function (edge) {
            // convertMaximalToMinimalEdgeRings
            _this._findIntersectionNodes(edge).forEach(function (node) {
                _this._computeNextCCWEdges(node, edge.label);
            });
        });
        var edgeRingList = [];
        // find all edgerings
        this.edges.forEach(function (edge) {
            if (edge.ring)
                return;
            edgeRingList.push(_this._findEdgeRing(edge));
        });
        return edgeRingList;
    };
    /**
     * Find all nodes in a Maxima EdgeRing which are self-intersection nodes.
     *
     * @param {Node} startEdge - Start Edge of the Ring
     * @returns {Node[]} - intersection nodes
     */
    Graph.prototype._findIntersectionNodes = function (startEdge) {
        var intersectionNodes = [];
        var edge = startEdge;
        var _loop_1 = function () {
            // getDegree
            var degree = 0;
            edge.from.getOuterEdges().forEach(function (e) {
                if (e.label === startEdge.label)
                    ++degree;
            });
            if (degree > 1)
                intersectionNodes.push(edge.from);
            edge = edge.next;
        };
        do {
            _loop_1();
        } while (!startEdge.isEqual(edge));
        return intersectionNodes;
    };
    /**
     * Get the edge-ring which starts from the provided Edge.
     *
     * @param {Edge} startEdge - starting edge of the edge ring
     * @returns {EdgeRing} - EdgeRing which start Edge is the provided one.
     */
    Graph.prototype._findEdgeRing = function (startEdge) {
        var edge = startEdge;
        var edgeRing = new EdgeRing_1.default();
        do {
            edgeRing.push(edge);
            edge.ring = edgeRing;
            edge = edge.next;
        } while (!startEdge.isEqual(edge));
        return edgeRing;
    };
    /**
     * Removes a node from the Graph.
     *
     * It also removes edges asociated to that node
     * @param {Node} node - Node to be removed
     */
    Graph.prototype.removeNode = function (node) {
        var _this = this;
        node.getOuterEdges().forEach(function (edge) { return _this.removeEdge(edge); });
        node.innerEdges.forEach(function (edge) { return _this.removeEdge(edge); });
        delete this.nodes[node.id];
    };
    /**
     * Remove edge from the graph and deletes the edge.
     *
     * @param {Edge} edge - Edge to be removed
     */
    Graph.prototype.removeEdge = function (edge) {
        this.edges = this.edges.filter(function (e) { return !e.isEqual(edge); });
        edge.deleteEdge();
    };
    return Graph;
}());
exports.default = Graph;
