/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2020 Paul Ramsey <pramsey@cleverelephant.ca>
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Public Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 **********************************************************************
 *
 * Last port: operation/overlayng/EdgeNodingBuilder.java 6ef89b096
 *
 **********************************************************************/

#pragma once

#include <geos/algorithm/LineIntersector.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/Envelope.h>
#include <geos/geom/Geometry.h>
#include <geos/geom/GeometryCollection.h>
#include <geos/geom/LinearRing.h>
#include <geos/geom/LineString.h>
#include <geos/geom/Polygon.h>
#include <geos/noding/IntersectionAdder.h>
#include <geos/noding/Noder.h>
#include <geos/noding/SegmentString.h>
#include <geos/operation/overlayng/Edge.h>
#include <geos/operation/overlayng/EdgeSourceInfo.h>
#include <geos/operation/overlayng/LineLimiter.h>
#include <geos/operation/overlayng/RingClipper.h>


#include <geos/export.h>
#include <array>
#include <memory>
#include <deque>



namespace geos {      // geos.
namespace operation { // geos.operation
namespace overlayng { // geos.operation.overlayng

/**
 * Builds a set of noded, unique, labelled Edges from
 * the edges of the two input geometries.
 *
 * It performs the following steps:
 *
 *  - Extracts input edges, and attaches topological information
 *  - if clipping is enabled, handles clipping or limiting input geometry
 *  - chooses a {@link noding::Noder} based on provided precision model, unless a custom one is supplied
 *  - calls the chosen noding::Noder, with precision model
 *  - removes any fully collapsed noded edges
 *  - builds {@link Edge}s and merges them
 *
 * @author mdavis
 */
class GEOS_DLL EdgeNodingBuilder {
    using PrecisionModel = geos::geom::PrecisionModel;
    using Envelope = geos::geom::Envelope;
    using GeometryCollection = geos::geom::GeometryCollection;
    using Polygon = geos::geom::Polygon;
    using CoordinateSequence = geos::geom::CoordinateSequence;
    using LinearRing = geos::geom::LinearRing;
    using LineString = geos::geom::LineString;
    using Geometry = geos::geom::Geometry;

private:

    // Constants
    static constexpr int MIN_LIMIT_PTS = 20;
    static constexpr bool IS_NODING_VALIDATED = true;

    // Members
    const PrecisionModel* pm;
    std::unique_ptr<std::vector<noding::SegmentString*>> inputEdges;
    noding::Noder* customNoder;
    std::array<bool, 2> hasEdges;
    const Envelope* clipEnv;
    std::unique_ptr<RingClipper> clipper;
    std::unique_ptr<LineLimiter> limiter;

    // For use in createFloatingPrecisionNoder()
    algorithm::LineIntersector lineInt;
    noding::IntersectionAdder intAdder;
    std::unique_ptr<noding::Noder> internalNoder;
    std::unique_ptr<noding::Noder> spareInternalNoder;
    // EdgeSourceInfo*, Edge* owned by EdgeNodingBuilder, stored in deque
    std::deque<EdgeSourceInfo> edgeSourceInfoQue;
    std::deque<Edge> edgeQue;
    bool inputHasZ;
    bool inputHasM;

    /**
    * Gets a noder appropriate for the precision model supplied.
    * This is one of:
    *
    * - Fixed precision: a snap-rounding noder (which should be fully robust)
    * - Floating precision: a conventional model (which may be non-robust).
    *   In this case, a validation step is applied to the output from the noder.
    */
    noding::Noder* getNoder();
    static std::unique_ptr<noding::Noder> createFixedPrecisionNoder(const PrecisionModel* pm);
    std::unique_ptr<noding::Noder> createFloatingPrecisionNoder(bool doValidation);


    void addCollection(const GeometryCollection* gc, uint8_t geomIndex);
    void addGeometryCollection(const GeometryCollection* gc, uint8_t geomIndex, int expectedDim);
    void addPolygon(const Polygon* poly, uint8_t geomIndex);
    void addPolygonRing(const LinearRing* ring, bool isHole, uint8_t geomIndex);
    void addLine(const LineString* line, uint8_t geomIndex);
    void addLine(std::unique_ptr<CoordinateSequence>& pts, uint8_t geomIndex);
    void addEdge(std::unique_ptr<CoordinateSequence>& cas, const EdgeSourceInfo* info);

    // Create a EdgeSourceInfo* owned by EdgeNodingBuilder
    const EdgeSourceInfo* createEdgeSourceInfo(uint8_t index, int depthDelta, bool isHole);
    const EdgeSourceInfo* createEdgeSourceInfo(uint8_t index);

    /**
    * Tests whether a geometry (represented by its envelope)
    * lies completely outside the clip extent(if any).
    */
    bool isClippedCompletely(const Envelope* env) const;

    /**
    * Tests whether it is worth limiting a line.
    * Lines that have few vertices or are covered
    * by the clip extent do not need to be limited.
    */
    bool isToBeLimited(const LineString* line) const;

    /**
    * If limiter is provided,
    * limit the line to the clip envelope.
    *
    */
    std::vector<std::unique_ptr<CoordinateSequence>>& limit(const LineString* line);

    /**
    * If a clipper is present,
    * clip the line to the clip extent.
    * Otherwise, remove duplicate points from the ring.
    *
    * If clipping is enabled, then every ring MUST
    * be clipped, to ensure that holes are clipped to
    * be inside the shell.
    * This means it is not possible to skip
    * clipping for rings with few vertices.
    *
    * @param ring the line to clip
    * @return the points in the clipped line
    */
    std::unique_ptr<CoordinateSequence> clip(const LinearRing* line);

    /**
    * Removes any repeated points from a linear component.
    * This is required so that noding can be computed correctly.
    *
    * @param line the line to process
    * @return the points of the line with repeated points removed
    */
    static std::unique_ptr<CoordinateSequence> removeRepeatedPoints(const LineString* line);

    static int computeDepthDelta(const LinearRing* ring, bool isHole);

    void add(const Geometry* g, uint8_t geomIndex);

    /**
    * Nodes a set of segment strings and creates {@link Edge}s from the result.
    * The input segment strings each carry a {@link EdgeSourceInfo} object,
    * which is used to provide source topology info to the constructed Edges
    * (and is then discarded).
    */
    std::vector<Edge*> node(std::vector<noding::SegmentString*>* segStrings);
    std::vector<Edge*> createEdges(std::vector<noding::SegmentString*>* segStrings);


public:

    /**
    * Creates a new builder, with an optional custom noder.
    * If the noder is not provided, a suitable one will
    * be used based on the supplied precision model.
    */
    EdgeNodingBuilder(const PrecisionModel* p_pm, noding::Noder* p_customNoder)
        : pm(p_pm)
        , inputEdges(new std::vector<noding::SegmentString*>)
        , customNoder(p_customNoder)
        , hasEdges{{false,false}}
        , clipEnv(nullptr)
        , intAdder(lineInt)
        , inputHasZ(false)
        , inputHasM(false)
        {};

    ~EdgeNodingBuilder()
    {
        for (noding::SegmentString* ss: *inputEdges) {
            delete ss;
        }
    }

    void setClipEnvelope(const Envelope* clipEnv);

    // returns newly allocated vector and segmentstrings
    // std::vector<noding::SegmentString*>* node();

    /**
    * Reports whether there are noded edges
    * for the given input geometry.
    * If there are none, this indicates that either
    * the geometry was empty, or has completely collapsed
    * (because it is smaller than the noding precision).
    *
    * @param geomIndex index of input geometry
    * @return true if there are edges for the geometry
    */
    bool hasEdgesFor(uint8_t geomIndex) const;

    /**
    * Creates a set of labelled {Edge}s.
    * representing the fully noded edges of the input geometries.
    * Coincident edges (from the same or both geometries)
    * are merged along with their labels
    * into a single unique, fully labelled edge.
    *
    * @param geom0 the first geometry
    * @param geom1 the second geometry
    * @return the noded, merged, labelled edges
    */
    std::vector<Edge*> build(const Geometry* geom0, const Geometry* geom1);



};


} // namespace geos.operation.overlayng
} // namespace geos.operation
} // namespace geos

