/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2020 Sandro Santilli <strk@kbt.io>
 * 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/OverlayNG.java 4c88fea52
 *
 **********************************************************************/

#pragma once

#include <geos/geom/Geometry.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/operation/overlayng/OverlayGraph.h>
#include <geos/operation/overlayng/OverlayEdgeRing.h>
#include <geos/operation/overlayng/InputGeometry.h>
#include <geos/export.h>

// Forward declarations
namespace geos {
namespace geom {
class GeometryFactory;
class PrecisionModel;
}
namespace noding {
class Noder;
}
namespace operation {
namespace overlayng {
}
}
}

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

/**
 * Computes the geometric overlay of two {@link geom::Geometry}s,
 * using an explicit precision model to allow robust computation.
 * The overlay can be used to determine any of the
 * following set-theoretic operations (boolean combinations) of the geometries:
 *
 * * INTERSECTION - all points which lie in both geometries
 * * UNION - all points which lie in at least one geometry
 * * DIFFERENCE - all points which lie in the first geometry but not the second
 * * SYMDIFFERENCE - all points which lie in one geometry but not both
 *
 * The requirements for overlay input are:
 * * Input collections must be homogeneous
 *   (all elements must have the same dimension).
 * * Inputs may be simple link GeometryCollections.
 *   A GeometryCollection is simple if it can be flattened into a valid Multi-geometry;
 *   i.e. it is homogeneous and does not contain any overlapping Polygons.
 * * In general, inputs must be valid geometries.
 *   However, polygonal inputs may contain the following two kinds of "mild" invalid topology:
 *   (i) rings which self-touch at discrete points (sometimes called inverted shells and exverted holes).
 *   (ii) rings which touch along line segments (i.e. topology collapse).
 *
 * The precision model used for the computation can be supplied
 * independent of the precision model of the input geometry.
 * The main use for this is to allow using a fixed precision
 * for geometry with a floating precision model.
 * This does two things: ensures robust computation;
 * and forces the output to be validly rounded to the precision model.
 *
 * For fixed precision models noding is performed using a {@link noding::snapround::SnapRoundingNoder}.
 * This provides robust computation (as long as precision is limited to
 * around 13 decimal digits).
 *
 * For floating precision an {@link noding::MCIndexNoder} is used.
 * This is not fully robust, so can sometimes result in
 * {@link util::TopologyException}s being thrown.
 * For robust full-precision overlay see {@link OverlayNGRobust}.
 *
 * Note: If a {@link noding::snap::SnappingNoder} is used
 * it is best to specify a fairly small snap tolerance,
 * since the intersection clipping optimization can
 * interact with the snapping to alter the result.
 *
 * Optionally the overlay computation can process using strict mode
 * (via setStrictMode(boolean). In strict mode result semantics are:
 *
 *  - Lines and Points resulting from topology collapses are not included
 *    in the result
 *  - Result geometry is homogeneous for the
 *    INTERSECTION and DIFFERENCE operations.
 *  - Result geometry is homogeneous for the
 *    UNION and SYMDIFFERENCE operations if
 *    the inputs have the same dimension.
 *
 * Strict mode has the following benefits:
 *
 *  - Results are simpler
 *  - Overlay operations are chainable without needing to remove
 *    lower-dimension elements
 *
 * The original JTS overlay semantics correspond to non-strict mode.
 *
 * If a robustness error occurs, a TopologyException is thrown.
 * These are usually caused by numerical rounding causing the noding
 * output to not be fully noded.
 * For robust computation with full-precision {@link OverlayNGRobust}
 * can be used.
 *
 * @author mdavis
 * @see OverlayNGRobust
 *
 */
class GEOS_DLL OverlayNG {
    using Geometry = geos::geom::Geometry;
    using PrecisionModel = geos::geom::PrecisionModel;
    using Location = geos::geom::Location;

private:

    // Members
    const geom::PrecisionModel* pm;
    InputGeometry inputGeom;
    const geom::GeometryFactory* geomFact;
    int opCode;
    noding::Noder* noder;
    bool isStrictMode;
    bool isOptimized;
    bool isAreaResultOnly;
    bool isOutputEdges;
    bool isOutputResultEdges;
    bool isOutputNodedEdges;

    // Methods
    std::unique_ptr<geom::Geometry> computeEdgeOverlay();
    void labelGraph(OverlayGraph* graph);

    /**
    * Extracts the result geometry components from the fully labelled topology graph.
    *
    * This method implements the semantic that the result of an
    * intersection operation is homogeneous with highest dimension.
    * In other words,
    * if an intersection has components of a given dimension
    * no lower-dimension components are output.
    * For example, if two polygons intersect in an area,
    * no linestrings or points are included in the result,
    * even if portions of the input do meet in lines or points.
    * This semantic choice makes more sense for typical usage,
    * in which only the highest dimension components are of interest.
    */
    std::unique_ptr<geom::Geometry> extractResult(int opCode, OverlayGraph* graph);
    std::unique_ptr<geom::Geometry> createEmptyResult();



public:
    /**
    * The default setting for Strict Mode.
    *
    * The original JTS overlay semantics used non-strict result
    * semantics, including;
    * - An Intersection result can be mixed-dimension,
    *   due to inclusion of intersection components of all dimensions
    * - Results can include lines caused by Area topology collapse
    */
    static constexpr bool STRICT_MODE_DEFAULT = false;

    static constexpr int INTERSECTION   = 1;
    static constexpr int UNION          = 2;
    static constexpr int DIFFERENCE     = 3;
    static constexpr int SYMDIFFERENCE  = 4;

    /**
    * Creates an overlay operation on the given geometries,
    * with a defined precision model.
    * The noding strategy is determined by the precision model.
    */
    OverlayNG(const geom::Geometry* geom0, const geom::Geometry* geom1, const geom::GeometryFactory* p_geomFact, int p_opCode)
        : pm(p_geomFact->getPrecisionModel())
        , inputGeom(geom0, geom1)
        , geomFact(p_geomFact)
        , opCode(p_opCode)
        , noder(nullptr)
        , isStrictMode(STRICT_MODE_DEFAULT)
        , isOptimized(true)
        , isAreaResultOnly(false)
        , isOutputEdges(false)
        , isOutputResultEdges(false)
        , isOutputNodedEdges(false)
    {}

    /**
    * Creates an overlay operation on the given geometries,
    * with a defined precision model.
    * The noding strategy is determined by the precision model.
    */
    OverlayNG(const geom::Geometry* geom0, const geom::Geometry* geom1, const geom::PrecisionModel* p_pm, int p_opCode)
        : pm(p_pm)
        , inputGeom(geom0, geom1)
        , geomFact(geom0->getFactory())
        , opCode(p_opCode)
        , noder(nullptr)
        , isStrictMode(STRICT_MODE_DEFAULT)
        , isOptimized(true)
        , isAreaResultOnly(false)
        , isOutputEdges(false)
        , isOutputResultEdges(false)
        , isOutputNodedEdges(false)
    {}

    /**
    * Creates an overlay operation on the given geometries
    * using the precision model of the geometries.
    *
    * The noder is chosen according to the precision model specified.
    *
    *  - For {@link PrecisionModel#FIXED}
    * a snap-rounding noder is used, and the computation is robust.
    *  - For {@link PrecisionModel#FLOATING}
    * a non-snapping noder is used,
    * and this computation may not be robust.
    * If errors occur a {@link util::TopologyException} is thrown.
    */
    OverlayNG(const geom::Geometry* geom0, const geom::Geometry* geom1, int p_opCode)
        : OverlayNG(geom0, geom1, geom0->getFactory()->getPrecisionModel(), p_opCode)
    {}

    OverlayNG(const geom::Geometry* geom0, const geom::PrecisionModel* p_pm)
        : OverlayNG(geom0, nullptr, p_pm, UNION)
    {}

    /**
    * Sets whether overlay processing optimizations are enabled.
    * It may be useful to disable optimizations
    * for testing purposes.
    * Default is TRUE (optimization enabled).
    *
    * @param p_isOptimized whether to optimize processing
    */
    void setOptimized(bool p_isOptimized) { isOptimized = p_isOptimized; }
    void setStrictMode(bool p_isStrictMode) { isStrictMode = p_isStrictMode; }
    void setAreaResultOnly(bool p_areaResultOnly) { isAreaResultOnly = p_areaResultOnly; }
    void setOutputEdges(bool p_isOutputEdges) { isOutputEdges = p_isOutputEdges; }
    void setOutputResultEdges(bool p_isOutputResultEdges) { isOutputResultEdges = p_isOutputResultEdges; }
    void setNoder(noding::Noder* p_noder) { noder = p_noder; }

    void setOutputNodedEdges(bool p_isOutputNodedEdges)
    {
        isOutputEdges = true;
        isOutputNodedEdges = p_isOutputNodedEdges;
    }

    /**
     * Gets the result of the overlay operation.
     *
     * @return the result of the overlay operation.
     *
     * @throws IllegalArgumentException if the input is not supported (e.g. a mixed-dimension geometry)
     * @throws TopologyException if a robustness error occurs
     */
    std::unique_ptr<Geometry> getResult();

    /**
    * Tests whether a point with a given topological {@link OverlayLabel}
    * relative to two geometries is contained in
    * the result of overlaying the geometries using
    * a given overlay operation.
    *
    * The method handles arguments of {@link geom::Location::NONE} correctly
    */
    static bool isResultOfOpPoint(const OverlayLabel* label, int opCode);

    /**
    * Tests whether a point with given {@link geom::Location}s
    * relative to two geometries would be contained in
    * the result of overlaying the geometries using
    * a given overlay operation.
    * This is used to determine whether components
    * computed during the overlay process should be
    * included in the result geometry.
    *
    * The method handles arguments of {@link geom::Location::NONE} correctly.
    */
    static bool isResultOfOp(int overlayOpCode, Location loc0, Location loc1);

    /**
    * Computes an overlay operation for
    * the given geometry operands, with the
    * noding strategy determined by the precision model.
    *
    * @param geom0 the first geometry argument
    * @param geom1 the second geometry argument
    * @param opCode the code for the desired overlay operation
    * @param pm the precision model to use
    * @return the result of the overlay operation
    */
    static std::unique_ptr<Geometry>
    overlay(const Geometry* geom0, const Geometry* geom1,
            int opCode, const PrecisionModel* pm);


    /**
    * Computes an overlay operation on the given geometry operands,
    * using a supplied {@link noding::Noder}.
    *
    * @param geom0 the first geometry argument
    * @param geom1 the second geometry argument
    * @param opCode the code for the desired overlay operation
    * @param pm the precision model to use (which may be null if the noder does not use one)
    * @param noder the noder to use
    * @return the result of the overlay operation
    */
    static std::unique_ptr<Geometry>
    overlay(const Geometry* geom0, const Geometry* geom1,
            int opCode, const PrecisionModel* pm, noding::Noder* noder);


    /**
    * Computes an overlay operation on the given geometry operands,
    * using a supplied {@link noding::Noder}.
    *
    * @param geom0 the first geometry argument
    * @param geom1 the second geometry argument
    * @param opCode the code for the desired overlay operation
    * @param noder the noder to use
    * @return the result of the overlay operation
    */
    static std::unique_ptr<Geometry>
    overlay(const Geometry* geom0, const Geometry* geom1,
            int opCode, noding::Noder* noder);

    /**
    * Computes an overlay operation on
    * the given geometry operands,
    * using the precision model of the geometry.
    * and an appropriate noder.
    *
    * The noder is chosen according to the precision model specified.
    *
    *  - For {@link geom::PrecisionModel#FIXED}
    *    a snap-rounding noder is used, and the computation is robust.
    *  - For {@link geom::PrecisionModel#FLOATING}
    *    a non-snapping noder is used,
    *    and this computation may not be robust.
    * If errors occur a {@link util::TopologyException} is thrown.
    *
    * @param geom0 the first argument geometry
    * @param geom1 the second argument geometry
    * @param opCode the code for the desired overlay operation
    * @return the result of the overlay operation
    */
    static std::unique_ptr<Geometry>
    overlay(const Geometry* geom0, const Geometry* geom1, int opCode);


    /**
    * Computes a union operation on
    * the given geometry, with the supplied precision model.
    * The primary use for this is to perform precision reduction
    * (round the geometry to the supplied precision).
    *
    * The input must be a valid geometry.
    * Collections must be homogeneous.
    * IMPORTANT: You probably want OverlayNGUnaryUnion, not this.
    *
    * @param geom the geometry
    * @param pm the precision model to use
    * @return the result of the union operation
    *
    * @see OverlayMixedPoints
    * @see PrecisionReducer
    * @see UnaryUnionNG
    * @see CoverageUnion
    */
    static std::unique_ptr<Geometry>
    geomunion(const Geometry* geom, const PrecisionModel* pm);


    /**
    * Computes a union of a single geometry using a custom noder.
    *
    * The primary use of this is to support coverage union.
    *
    * The input must be a valid geometry.
    * Collections must be homogeneous.
    * IMPORTANT: You probably want OverlayNGUnaryUnion, not this.
    *
    * @param geom the geometry to union
    * @param pm the precision model to use (maybe be null)
    * @param noder the noder to use
    * @return the result geometry
    *
    * @see CoverageUnion
    */
    static std::unique_ptr<Geometry>
    geomunion(const Geometry* geom, const PrecisionModel* pm, noding::Noder* noder);




};


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