/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2009 2011 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2005 2006 Refractions Research Inc.
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
 *
 * 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: geom/Geometry.java rev. 1.112
 *
 **********************************************************************/

#pragma once

#ifndef USE_UNSTABLE_GEOS_CPP_API
#ifndef _MSC_VER
# warning "The GEOS C++ API is unstable, please use the C API instead"
# warning "HINT: #include geos_c.h"
#else
#pragma message("The GEOS C++ API is unstable, please use the C API instead")
#pragma message("HINT: #include geos_c.h")
#endif
#endif

#include <geos/export.h>
#include <geos/geom/Envelope.h>
#include <geos/geom/Dimension.h> // for Dimension::DimensionType
#include <geos/geom/GeometryComponentFilter.h> // for inheritance
#include <geos/geom/CoordinateSequence.h> // to materialize CoordinateSequence

#include <algorithm>
#include <string>
#include <iostream>
#include <vector>
#include <memory>

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class
#pragma warning(disable: 4355) // warning C4355: 'this' : used in base member initializer list
#endif

// Forward declarations
namespace geos {
namespace geom {
class Coordinate;
class CoordinateFilter;
class CoordinateSequence;
class CoordinateSequenceFilter;
class GeometryComponentFilter;
class GeometryFactory;
class GeometryFilter;
class PrecisionModel;
class Point;
class IntersectionMatrix;
}
namespace io { // geos.io
} // namespace geos.io
}

namespace geos { // geos
namespace geom { // geos::geom

/// Geometry types
enum GeometryTypeId : int {
    /// a point
    GEOS_POINT,
    /// a linestring
    GEOS_LINESTRING,
    /// a linear ring (linestring with 1st point == last point)
    GEOS_LINEARRING,
    /// a polygon
    GEOS_POLYGON,
    /// a collection of points
    GEOS_MULTIPOINT,
    /// a collection of linestrings
    GEOS_MULTILINESTRING,
    /// a collection of polygons
    GEOS_MULTIPOLYGON,
    /// a collection of heterogeneus geometries
    GEOS_GEOMETRYCOLLECTION,
    GEOS_CIRCULARSTRING,
    GEOS_COMPOUNDCURVE,
    GEOS_CURVEPOLYGON,
    GEOS_MULTICURVE,
    GEOS_MULTISURFACE,
};

enum GeometrySortIndex {
    SORTINDEX_POINT = 0,
    SORTINDEX_MULTIPOINT = 1,
    SORTINDEX_LINESTRING = 2,
    SORTINDEX_LINEARRING = 3,
    SORTINDEX_MULTILINESTRING = 4,
    SORTINDEX_POLYGON = 5,
    SORTINDEX_MULTIPOLYGON = 6,
    SORTINDEX_GEOMETRYCOLLECTION = 7,
    SORTINDEX_CIRCULARSTRING = 8,
    SORTINDEX_COMPOUNDCURVE = 9,
    SORTINDEX_CURVEPOLYGON = 10,
    SORTINDEX_MULTICURVE = 11,
    SORTINDEX_MULTISURFACE = 12,
};

/**
 * \class Geometry geom.h geos.h
 *
 * \brief Basic implementation of Geometry, constructed and
 * destructed by GeometryFactory.
 *
 *  <code>clone</code> returns a deep copy of the object.
 *  Use GeometryFactory to construct.
 *
 *  <H3>Binary Predicates</H3>
 * Because it is not clear at this time
 * what semantics for spatial
 *  analysis methods involving <code>GeometryCollection</code>s would be useful,
 *  <code>GeometryCollection</code>s are not supported as arguments to binary
 *  predicates (other than <code>convexHull</code>) or the <code>relate</code>
 *  method.
 *
 *  <H3>Set-Theoretic Methods</H3>
 *
 *  The spatial analysis methods will
 *  return the most specific class possible to represent the result. If the
 *  result is homogeneous, a <code>Point</code>, <code>LineString</code>, or
 *  <code>Polygon</code> will be returned if the result contains a single
 *  element; otherwise, a <code>MultiPoint</code>, <code>MultiLineString</code>,
 *  or <code>MultiPolygon</code> will be returned. If the result is
 *  heterogeneous a <code>GeometryCollection</code> will be returned. <P>
 *
 *  Because it is not clear at this time what semantics for set-theoretic
 *  methods involving <code>GeometryCollection</code>s would be useful,
 * <code>GeometryCollections</code>
 *  are not supported as arguments to the set-theoretic methods.
 *
 *  <H4>Representation of Computed Geometries </H4>
 *
 *  The SFS states that the result
 *  of a set-theoretic method is the "point-set" result of the usual
 *  set-theoretic definition of the operation (SFS 3.2.21.1). However, there are
 *  sometimes many ways of representing a point set as a <code>Geometry</code>.
 *  <P>
 *
 *  The SFS does not specify an unambiguous representation of a given point set
 *  returned from a spatial analysis method. One goal of JTS is to make this
 *  specification precise and unambiguous. JTS will use a canonical form for
 *  <code>Geometry</code>s returned from spatial analysis methods. The canonical
 *  form is a <code>Geometry</code> which is simple and noded:
 *  <UL>
 *    <LI> Simple means that the Geometry returned will be simple according to
 *    the JTS definition of <code>isSimple</code>.
 *    <LI> Noded applies only to overlays involving <code>LineString</code>s. It
 *    means that all intersection points on <code>LineString</code>s will be
 *    present as endpoints of <code>LineString</code>s in the result.
 *  </UL>
 *  This definition implies that non-simple geometries which are arguments to
 *  spatial analysis methods must be subjected to a line-dissolve process to
 *  ensure that the results are simple.
 *
 *  <H4> Constructed Points And The Precision Model </H4>
 *
 *  The results computed by the set-theoretic methods may
 *  contain constructed points which are not present in the input Geometry.
 *  These new points arise from intersections between line segments in the
 *  edges of the input Geometry. In the general case it is not
 *  possible to represent constructed points exactly. This is due to the fact
 *  that the coordinates of an intersection point may contain twice as many bits
 *  of precision as the coordinates of the input line segments. In order to
 *  represent these constructed points explicitly, JTS must truncate them to fit
 *  the PrecisionModel.
 *
 *  Unfortunately, truncating coordinates moves them slightly. Line segments
 *  which would not be coincident in the exact result may become coincident in
 *  the truncated representation. This in turn leads to "topology collapses" --
 *  situations where a computed element has a lower dimension than it would in
 *  the exact result.
 *
 *  When JTS detects topology collapses during the computation of spatial
 *  analysis methods, it will throw an exception. If possible the exception will
 *  report the location of the collapse.
 *
 *  equals(Object) and hashCode are not overridden, so that when two
 *  topologically equal Geometries are added to HashMaps and HashSets, they
 *  remain distinct. This behaviour is desired in many cases.
 *
 */
class GEOS_DLL Geometry {

public:

    friend class GeometryFactory;

    /// A vector of const Geometry pointers
    using ConstVect = std::vector<const Geometry*>;

    /// A vector of non-const Geometry pointers
    using NonConstVect = std::vector<Geometry*>;

    /// An unique_ptr of Geometry
    using Ptr = std::unique_ptr<Geometry> ;

    /// Make a deep-copy of this Geometry
    std::unique_ptr<Geometry> clone() const { return std::unique_ptr<Geometry>(cloneImpl()); }

    /// Destroy Geometry and all components
    virtual ~Geometry();


    /**
     * \brief
     * Gets the factory which contains the context in which this
     * geometry was created.
     *
     * @return the factory for this geometry
     */
    const GeometryFactory*
    getFactory() const
    {
        return _factory;
    }

    /**
    * \brief
    * A simple scheme for applications to add their own custom data to
    * a Geometry.
    * An example use might be to add an object representing a
    * Coordinate Reference System.
    *
    * Note that user data objects are not present in geometries created
    * by construction methods.
    *
    * @param newUserData an object, the semantics for which are
    * defined by the application using this Geometry
    */
    void
    setUserData(void* newUserData)
    {
        _userData = newUserData;
    }

    /**
    * \brief
    * Gets the user data object for this geometry, if any.
    *
    * @return the user data object, or <code>null</code> if none set
    */
    void*
    getUserData() const
    {
        return _userData;
    }

    /** \brief
     * Returns the ID of the Spatial Reference System used by the Geometry.
     *
     * GEOS supports Spatial Reference System information in the simple way
     * defined in the SFS. A Spatial Reference System ID (SRID) is present
     * in each Geometry object. Geometry provides basic accessor operations
     * for this field, but no others. The SRID is represented as an integer.
     *
     * @return the ID of the coordinate space in which the Geometry is defined.
     */
    int getSRID() const
    {
        return SRID;
    }

    /** \brief
     * Sets the ID of the Spatial Reference System used by the Geometry.
     */
    virtual void
    setSRID(int newSRID)
    {
        SRID = newSRID;
    }

    /**
     * \brief
     * Get the PrecisionModel used to create this Geometry.
     */
    const PrecisionModel* getPrecisionModel() const;

    /// Returns a vertex of this Geometry, or NULL if this is the empty geometry.
    virtual const CoordinateXY* getCoordinate() const = 0; //Abstract

    /**
     * \brief
     * Returns this Geometry vertices.
     * Caller takes ownership of the returned object.
     */
    virtual std::unique_ptr<CoordinateSequence> getCoordinates() const = 0; //Abstract

    /// Returns the count of this Geometrys vertices.
    virtual std::size_t getNumPoints() const = 0; //Abstract

    /// Returns false if the Geometry not simple.
    virtual bool isSimple() const;

    /// Return a string representation of this Geometry type
    virtual std::string getGeometryType() const = 0; //Abstract

    /// Returns whether the Geometry contains curved components
    virtual bool hasCurvedComponents() const;

    /// Return an integer representation of this Geometry type
    virtual GeometryTypeId getGeometryTypeId() const = 0; //Abstract

    /**
     * \brief Returns the number of geometries in this collection,
     * or 1 if this is not a collection.
     *
     * Empty collection or multi-geometry types return 0,
     * and empty simple geometry types return 1.
     */
    virtual std::size_t
    getNumGeometries() const
    {
        return 1;
    }

    /// \brief Returns a pointer to the nth Geometry in this collection
    /// (or self if this is not a collection)
    virtual const Geometry*
    getGeometryN(std::size_t /*n*/) const
    {
        return this;
    }

    /**
     * \brief Tests the validity of this <code>Geometry</code>.
     *
     * Subclasses provide their own definition of "valid".
     *
     * @return <code>true</code> if this <code>Geometry</code> is valid
     *
     * @see IsValidOp
     */
    bool isValid() const;

    /// Returns whether or not the set of points in this Geometry is empty.
    virtual bool isEmpty() const = 0; //Abstract

    /// Polygon overrides to check for actual rectangle
    virtual bool
    isRectangle() const
    {
        return false;
    }

    /// Returns the dimension of this Geometry (0=point, 1=line, 2=surface)
    virtual Dimension::DimensionType getDimension() const = 0; //Abstract

    /// Checks whether any component of this geometry has dimension d
    virtual bool hasDimension(Dimension::DimensionType d) const {
        return getDimension() == d;
    }

    /// Checks whether this Geometry consists only of components having dimension d.
    virtual bool isDimensionStrict(Dimension::DimensionType d) const {
        return d == getDimension();
    }

    bool isPuntal() const {
        return isDimensionStrict(Dimension::P);
    }

    bool isLineal() const {
        return isDimensionStrict(Dimension::L);
    }

    bool isPolygonal() const {
        return isDimensionStrict(Dimension::A);
    }

    bool isMixedDimension() const;
    bool isMixedDimension(Dimension::DimensionType* baseDim) const;

    bool isCollection() const {
        int t = getGeometryTypeId();
        return t == GEOS_GEOMETRYCOLLECTION ||
               t == GEOS_MULTIPOINT ||
               t == GEOS_MULTILINESTRING ||
               t == GEOS_MULTIPOLYGON;
    }

    static GeometryTypeId multiTypeId(GeometryTypeId typeId) {
        switch (typeId) {
            case GEOS_POINT: return GEOS_MULTIPOINT;
            case GEOS_LINESTRING: return GEOS_MULTILINESTRING;
            case GEOS_POLYGON: return GEOS_MULTIPOLYGON;
            default: return typeId;
        }
    }

    /// Returns the coordinate dimension of this Geometry (2=XY, 3=XYZ or XYM, 4=XYZM).
    virtual uint8_t getCoordinateDimension() const = 0; //Abstract

    virtual bool hasZ() const = 0;

    virtual bool hasM() const = 0;

    /**
     * \brief
     * Returns the boundary, or an empty geometry of appropriate
     * dimension if this <code>Geometry</code>  is empty.
     *
     * (In the case of zero-dimensional geometries,
     * an empty GeometryCollection is returned.)
     * For a discussion of this function, see the OpenGIS Simple
     * Features Specification. As stated in SFS Section 2.1.13.1,
     * "the boundary of a Geometry is a set of Geometries of the
     * next lower dimension."
     *
     * @return  the closure of the combinatorial boundary
     *          of this <code>Geometry</code>.
     *          Ownershipof the returned object transferred to caller.
     */
    virtual std::unique_ptr<Geometry> getBoundary() const = 0; //Abstract

    /// Returns the dimension of this Geometrys inherent boundary.
    virtual int getBoundaryDimension() const = 0; //Abstract

    /// Returns this Geometrys bounding box.
    virtual std::unique_ptr<Geometry> getEnvelope() const;

    /** \brief
     * Returns the minimum and maximum x and y values in this Geometry,
     * or a null Envelope if this Geometry is empty.
     */
    virtual const Envelope* getEnvelopeInternal() const = 0;

    /**
     * Tests whether this geometry is disjoint from the specified geometry.
     *
     * The <code>disjoint</code> predicate has the following equivalent
     * definitions:
     *  - The two geometries have no point in common
     *  - The DE-9IM Intersection Matrix for the two geometries matches
     *    <code>[FF*FF****]</code>
     *  - <code>! g.intersects(this)</code>
     *    (<code>disjoint</code> is the inverse of <code>intersects</code>)
     *
     * @param  other  the Geometry with which to compare this Geometry
     * @return true if the two <code>Geometry</code>s are disjoint
     *
     * @see Geometry::intersects
     */
    virtual bool disjoint(const Geometry* other) const;

    /** \brief
     * Returns true if the DE-9IM intersection matrix for the two
     * Geometrys is FT*******, F**T***** or F***T****.
     */
    virtual bool touches(const Geometry* other) const;

    /// Returns true if disjoint returns false.
    virtual bool intersects(const Geometry* g) const;

    /**
     * Tests whether this geometry crosses the specified geometry.
     *
     * The <code>crosses</code> predicate has the following equivalent
     * definitions:
     *  - The geometries have some but not all interior points in common.
     *  - The DE-9IM Intersection Matrix for the two geometries matches
     *    - <code>[T*T******]</code> (for P/L, P/A, and L/A situations)
     *    - <code>[T*****T**]</code> (for L/P, A/P, and A/L situations)
     *    - <code>[0********]</code> (for L/L situations)
     * For any other combination of dimensions this predicate returns
     * <code>false</code>.
     *
     * The SFS defined this predicate only for P/L, P/A, L/L, and L/A
     * situations.
     * JTS extends the definition to apply to L/P, A/P and A/L situations
     * as well, in order to make the relation symmetric.
     *
     * @param  g  the <code>Geometry</code> with which to compare this
     *            <code>Geometry</code>
     *@return  <code>true</code> if the two <code>Geometry</code>s cross.
     */
    virtual bool crosses(const Geometry* g) const;

    /** \brief
     * Returns true if the DE-9IM intersection matrix for the two
     * Geometrys is T*F**F***.
     */
    virtual bool within(const Geometry* g) const;

    /// Returns true if other.within(this) returns true.
    virtual bool contains(const Geometry* g) const;

    /** \brief
     * Returns true if the DE-9IM intersection matrix for the two
     * Geometrys is T*T***T** (for two points or two surfaces)
     * 1*T***T** (for two curves).
     */
    virtual bool overlaps(const Geometry* g) const;

    /**
     * \brief
     * Returns true if the elements in the DE-9IM intersection matrix
     * for the two Geometrys match the elements in intersectionPattern.
     *
     * IntersectionPattern elements may be: 0 1 2 T ( = 0, 1 or 2)
     * F ( = -1) * ( = -1, 0, 1 or 2).
     *
     * For more information on the DE-9IM, see the OpenGIS Simple
     * Features Specification.
     *
     * @throws util::IllegalArgumentException if either arg is a collection
     *
     */
    bool relate(const Geometry* g,
                        const std::string& intersectionPattern) const;

    bool
    relate(const Geometry& g, const std::string& intersectionPattern) const
    {
        return relate(&g, intersectionPattern);
    }

    /// Returns the DE-9IM intersection matrix for the two Geometrys.
    std::unique_ptr<IntersectionMatrix> relate(const Geometry* g) const;

    std::unique_ptr<IntersectionMatrix> relate(const Geometry& g) const;

    /**
     * \brief
     * Returns true if the DE-9IM intersection matrix for the two
     * Geometrys is T*F**FFF*.
     */
    bool equals(const Geometry* g) const;

    /** \brief
     * Returns <code>true</code> if this geometry covers the
     * specified geometry.
     *
     * The <code>covers</code> predicate has the following
     * equivalent definitions:
     *
     * - Every point of the other geometry is a point of this geometry.
     * - The DE-9IM Intersection Matrix for the two geometries is
     *    <code>T*****FF*</code>
     *    or <code>*T****FF*</code>
     *    or <code>***T**FF*</code>
     *    or <code>****T*FF*</code>
     * - <code>g.coveredBy(this)</code>
     *   (<code>covers</code> is the inverse of <code>coveredBy</code>)
     *
     * If either geometry is empty, the value of this predicate
     * is <tt>false</tt>.
     *
     * This predicate is similar to {@link #contains},
     * but is more inclusive (i.e. returns <tt>true</tt> for more cases).
     * In particular, unlike <code>contains</code> it does not distinguish
     * between points in the boundary and in the interior of geometries.
     * For most situations, <code>covers</code> should be used in
     * preference to <code>contains</code>.
     * As an added benefit, <code>covers</code> is more amenable to
     * optimization, and hence should be more performant.
     *
     * @param  g
     *         the <code>Geometry</code> with which to compare this
     *         <code>Geometry</code>
     *
     * @return <code>true</code> if this <code>Geometry</code>
     *                           covers <code>g</code>
     *
     * @see Geometry::contains
     * @see Geometry::coveredBy
     */
    bool covers(const Geometry* g) const;

    /** \brief
     * Tests whether this geometry is covered by the
     * specified geometry.
     *
     * The <code>coveredBy</code> predicate has the following
     * equivalent definitions:
     *
     *  - Every point of this geometry is a point of the other geometry.
     *  - The DE-9IM Intersection Matrix for the two geometries matches
     *       <code>[T*F**F***]</code>
     *    or <code>[*TF**F***]</code>
     *    or <code>[**FT*F***]</code>
     *    or <code>[**F*TF***]</code>
     *  - <code>g.covers(this)</code>
     *    (<code>coveredBy</code> is the converse of <code>covers</code>)
     *
     * If either geometry is empty, the value of this predicate
     * is <tt>false</tt>.
     *
     * This predicate is similar to {@link #within},
     * but is more inclusive (i.e. returns <tt>true</tt> for more cases).
     *
     * @param  g  the <code>Geometry</code> with which to compare
     *            this <code>Geometry</code>
     * @return  <code>true</code> if this <code>Geometry</code>
     *                            is covered by <code>g</code>
     *
     * @see Geometry#within
     * @see Geometry#covers
     */
    bool coveredBy(const Geometry* g) const;


    /// Returns the Well-known Text representation of this Geometry.
    virtual std::string toString() const;

    virtual std::string toText() const;

    /// Returns a buffer region around this Geometry having the given width.
    ///
    /// @throws util::TopologyException if a robustness error occurs
    ///
    std::unique_ptr<Geometry> buffer(double distance) const;

    /// \brief
    /// Returns a buffer region around this Geometry having the
    /// given width and with a specified number of segments used
    /// to approximate curves.
    ///
    /// @throws util::TopologyException if a robustness error occurs
    ///
    std::unique_ptr<Geometry> buffer(double distance, int quadrantSegments) const;

    /** \brief
     * Computes a buffer area around this geometry having the given
     * width and with a specified accuracy of approximation for circular
     * arcs, and using a specified end cap style.
     *
     * Buffer area boundaries can contain circular arcs.
     * To represent these arcs using linear geometry they must be
     * approximated with line segments.
     *
     * The <code>quadrantSegments</code> argument allows controlling the
     * accuracy of the approximation by specifying the number of line
     * segments used to represent a quadrant of a circle
     *
     * The end cap style specifies the buffer geometry that will be
     * created at the ends of linestrings.  The styles provided are:
     *
     * - BufferOp::CAP_ROUND - (default) a semi-circle
     * - BufferOp::CAP_BUTT  - a straight line perpendicular to the
     *                         end segment
     * - BufferOp::CAP_SQUARE - a half-square
     *
     *
     * @param distance the width of the buffer
     *                 (may be positive, negative or 0)
     *
     * @param quadrantSegments the number of line segments used
     *                         to represent a quadrant of a circle
     *
     * @param endCapStyle the end cap style to use
     *
     * @return an area geometry representing the buffer region
     *
     * @throws util::TopologyException if a robustness error occurs
     *
     * @see BufferOp
     */
    std::unique_ptr<Geometry> buffer(double distance, int quadrantSegments,
                             int endCapStyle) const;

    /// \brief
    /// Returns the smallest convex Polygon that contains
    /// all the points in the Geometry.
    virtual std::unique_ptr<Geometry> convexHull() const;

    /** \brief
     * Computes a new geometry which has all component coordinate sequences
     * in reverse order (opposite orientation) to this one.
     *
     * @return a reversed geometry
     */
    std::unique_ptr<Geometry> reverse() const { return std::unique_ptr<Geometry>(reverseImpl()); }

    /** \brief
     * Returns a Geometry representing the points shared by
     * this Geometry and other.
     *
     * @throws util::TopologyException if a robustness error occurs
     * @throws util::IllegalArgumentException if either input is a
     *         non-empty GeometryCollection
     *
     */
    std::unique_ptr<Geometry> intersection(const Geometry* other) const;

    /** \brief
     * Returns a Geometry representing all the points in this Geometry
     * and other.
     *
     * @throws util::TopologyException if a robustness error occurs
     * @throws util::IllegalArgumentException if either input is a
     *         non-empty GeometryCollection
     *
     */
    std::unique_ptr<Geometry> Union(const Geometry* other) const;
    // throw(IllegalArgumentException *, TopologyException *);

    /** \brief
     * Computes the union of all the elements of this geometry. Heterogeneous
     * [GeometryCollections](@ref GeometryCollection) are fully supported.
     *
     * The result obeys the following contract:
     *
     * - Unioning a set of [LineStrings](@ref LineString) has the effect of fully noding
     *   and dissolving the linework.
     * - Unioning a set of [Polygons](@ref Polygon) will always
     *   return a polygonal geometry (unlike Geometry::Union(const Geometry* other) const),
     *   which may return geometries of lower dimension if a topology collapse
     *   occurred.
     *
     * @return the union geometry
     *
     * @see UnaryUnionOp
     */
    Ptr Union() const;
    // throw(IllegalArgumentException *, TopologyException *);

    /**
     * \brief
     * Returns a Geometry representing the points making up this
     * Geometry that do not make up other.
     *
     * @throws util::TopologyException if a robustness error occurs
     * @throws util::IllegalArgumentException if either input is a
     *         non-empty GeometryCollection
     *
     */
    std::unique_ptr<Geometry> difference(const Geometry* other) const;

    /** \brief
     * Returns a set combining the points in this Geometry not in other,
     * and the points in other not in this Geometry.
     *
     * @throws util::TopologyException if a robustness error occurs
     * @throws util::IllegalArgumentException if either input is a
     *         non-empty GeometryCollection
     *
     */
    std::unique_ptr<Geometry> symDifference(const Geometry* other) const;

    /** \brief
     * Returns true iff the two Geometrys are of the same type and their
     * vertices corresponding by index are equal up to a specified distance
     * tolerance. Geometries are not required to have the same dimemsion;
     * any Z/M values are ignored.
     */
    virtual bool equalsExact(const Geometry* other, double tolerance = 0)
        const = 0; // Abstract

    /** \brief
     * Returns true if the two geometries are of the same type and their
     * vertices corresponding by index are equal in all dimensions.
     */
    virtual bool equalsIdentical(const Geometry* other) const = 0;

    virtual void apply_rw(const CoordinateFilter* filter) = 0; //Abstract
    virtual void apply_ro(CoordinateFilter* filter) const = 0; //Abstract
    virtual void apply_rw(GeometryFilter* filter);
    virtual void apply_ro(GeometryFilter* filter) const;
    virtual void apply_rw(GeometryComponentFilter* filter);
    virtual void apply_ro(GeometryComponentFilter* filter) const;

    /**
     *  Performs an operation on the coordinates in this Geometry's
     *  CoordinateSequences.
     *  If the filter reports that a coordinate value has been changed,
     *  {@link #geometryChanged} will be called automatically.
     *
     * @param  filter  the filter to apply
     */
    virtual void apply_rw(CoordinateSequenceFilter& filter) = 0;

    /**
     *  Performs a read-only operation on the coordinates in this
     *  Geometry's CoordinateSequences.
     *
     * @param  filter  the filter to apply
     */
    virtual void apply_ro(CoordinateSequenceFilter& filter) const = 0;

    /** \brief
     * Apply a filter to each component of this geometry.
     * The filter is expected to provide a .filter(const Geometry*)
     * method.
     *
     * I intend similar templated methods to replace
     * all the virtual apply_rw and apply_ro functions...
     *                --strk(2005-02-06);
     */
    template <class T>
    void
    applyComponentFilter(T& f) const
    {
        for(std::size_t i = 0, n = getNumGeometries(); i < n; ++i) {
            f.filter(getGeometryN(i));
        }
    }

    /**
     * Reorganizes this Geometry into normal form (or canonical form).
     * Starting point of rings is lower left, collections are ordered
     * by geometry type, etc.
     */
    virtual void normalize() = 0; //Abstract

    /// Comparator for sorting geometry
    int compareTo(const Geometry* geom) const;

    /// Returns the area of this Geometry.
    virtual double getArea() const;

    /// Returns the length of this Geometry.
    virtual double getLength() const;

    /** Returns the minimum distance between this Geometry and the Geometry g
     *
     * @param g the Geometry to calculate distance to
     * @return the distance in Cartesian units
     */
    virtual double distance(const Geometry* g) const;


    /** \brief
     * Tests whether the distance from this Geometry  to another
     * is less than or equal to a specified value.
     *
     * @param geom the Geometry to check the distance to
     * @param cDistance the distance value to compare
     * @return <code>true</code> if the geometries are less than
     *  <code>distance</code> apart.
     *
     * @todo doesn't seem to need being virtual, make it concrete
     */
    virtual bool isWithinDistance(const Geometry* geom,
                                  double cDistance) const;

    /** \brief
     * Computes the centroid of this <code>Geometry</code>.
     *
     * The centroid is equal to the centroid of the set of component
     * Geometries of highest dimension (since the lower-dimension geometries
     * contribute zero "weight" to the centroid)
     *
     * @return a {@link Point} which is the centroid of this Geometry
     */
    virtual std::unique_ptr<Point> getCentroid() const;

    /// Computes the centroid of this Geometry as a Coordinate
    //
    /// Returns false if centroid cannot be computed (EMPTY geometry)
    ///
    virtual bool getCentroid(CoordinateXY& ret) const;

    /** \brief
     * Computes an interior point of this <code>Geometry</code>.
     *
     * An interior point is guaranteed to lie in the interior of the Geometry,
     * if it possible to calculate such a point exactly. Otherwise,
     * the point may lie on the boundary of the geometry.
     *
     * @return a Point which is in the interior of this Geometry, or
     *         null if the geometry doesn't have an interior (empty)
     */
    std::unique_ptr<Point> getInteriorPoint() const;

    /**
     * \brief
     * Notifies this Geometry that its Coordinates have been changed
     * by an external party (using a CoordinateFilter, for example).
     */
    void geometryChanged();

    /**
     * \brief
     * Notifies this Geometry that its Coordinates have been changed
     * by an external party.
     */
    virtual void geometryChangedAction() = 0;

protected:
    /// Make a deep-copy of this Geometry
    virtual Geometry* cloneImpl() const = 0;

    /// Make a geometry with coordinates in reverse order
    virtual Geometry* reverseImpl() const = 0;

    /// Returns true if the array contains any non-empty Geometrys.
    template<typename T>
    static bool hasNonEmptyElements(const std::vector<T>* geometries) {
        return std::any_of(geometries->begin(), geometries->end(), [](const T& g) { return !g->isEmpty(); });
    }

    /// Returns true if the CoordinateSequence contains any null elements.
    static bool hasNullElements(const CoordinateSequence* list);

    /// Returns true if the vector contains any null elements.
    template<typename T>
    static bool hasNullElements(const std::vector<T>* geometries) {
        return std::any_of(geometries->begin(), geometries->end(), [](const T& g) { return g == nullptr; });
    }

//	static void reversePointOrder(CoordinateSequence* coordinates);
//	static Coordinate& minCoordinate(CoordinateSequence* coordinates);
//	static void scroll(CoordinateSequence* coordinates,Coordinate* firstCoordinate);
//	static int indexOf(Coordinate* coordinate,CoordinateSequence* coordinates);
//
    /** \brief
     * Returns whether the two Geometrys are equal, from the point
     * of view of the equalsExact method.
     */
    virtual bool isEquivalentClass(const Geometry* other) const;

    static void checkNotGeometryCollection(const Geometry* g);

    virtual int compareToSameClass(const Geometry* geom) const = 0; //Abstract

    template<typename T>
    static int compare(const T& a, const T& b)
    {
        std::size_t i = 0;
        std::size_t j = 0;
        while(i < a.size() && j < b.size()) {
            const auto& aGeom = *a[i];
            const auto& bGeom = *b[j];

            int comparison = aGeom.compareTo(&bGeom);
            if(comparison != 0) {
                return comparison;
            }

            i++;
            j++;
        }

        if(i < a.size()) {
            return 1;
        }

        if(j < b.size()) {
            return -1;
        }

        return 0;
    }

    bool equal(const CoordinateXY& a, const CoordinateXY& b,
               double tolerance) const;
    int SRID;

    Geometry(const Geometry& geom);

    /** \brief
     * Construct a geometry with the given GeometryFactory.
     *
     * Will keep a reference to the factory, so don't
     * delete it until al Geometry objects referring to
     * it are deleted.
     *
     * @param factory
     */
    Geometry(const GeometryFactory* factory);

    template<typename T>
    static std::vector<std::unique_ptr<Geometry>> toGeometryArray(std::vector<std::unique_ptr<T>> && v) {
        static_assert(std::is_base_of<Geometry, T>::value, "");
        std::vector<std::unique_ptr<Geometry>> gv(v.size());
        for (std::size_t i = 0; i < v.size(); i++) {
            gv[i] = std::move(v[i]);
        }
        return gv;
    }

    static std::vector<std::unique_ptr<Geometry>> toGeometryArray(std::vector<std::unique_ptr<Geometry>> && v) {
        return std::move(v);
    }

protected:

    virtual int getSortIndex() const = 0;


private:

    class GEOS_DLL GeometryChangedFilter : public GeometryComponentFilter {
    public:
        void filter_rw(Geometry* geom) override;
    };

    static GeometryChangedFilter geometryChangedFilter;

    /// The GeometryFactory used to create this Geometry
    ///
    /// Externally owned
    ///
    const GeometryFactory* _factory;

    void* _userData;
};

/// \brief
/// Write the Well-known Binary representation of this Geometry
/// as an HEX string to the given output stream
///
GEOS_DLL std::ostream& operator<< (std::ostream& os, const Geometry& geom);

struct GEOS_DLL GeometryGreaterThen {
    bool operator()(const Geometry* first, const Geometry* second);
};


/// Return current GEOS version
GEOS_DLL std::string geosversion();

/**
 * \brief
 * Return the version of JTS this GEOS
 * release has been ported from.
 */
GEOS_DLL std::string jtsport();

// We use this instead of std::pair<unique_ptr<Geometry>> because C++11
// forbids that construct:
// http://lwg.github.com/issues/lwg-closed.html#2068
struct GeomPtrPair {
    typedef std::unique_ptr<Geometry> GeomPtr;
    GeomPtr first;
    GeomPtr second;
};

} // namespace geos::geom
} // namespace geos

#ifdef _MSC_VER
#pragma warning(pop)
#endif
