/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2021 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.
 *
 **********************************************************************/

#pragma once

#include <geos/geom/Geometry.h>
#include <geos/util/IllegalArgumentException.h>
#include <geos/simplify/RingHull.h>


namespace geos {
namespace geom {
class GeometryFactory;
class LinearRing;
class MultiPolygon;
class Polygon;
}
namespace algorithm {
namespace hull {
class RingHullIndex;
}
}
}

namespace geos {
namespace simplify { // geos::simplify

/**
 * Computes topology-preserving simplified hulls of polygonal geometry.
 * Both outer and inner hulls can be computed.
 * Outer hulls contain the input geometry and are larger in area.
 * Inner hulls are contained by the input geometry and are smaller in area.
 * In both the hull vertices are a subset of the input vertices.
 * The hull construction attempts to minimize the area difference
 * with the input geometry.
 * Hulls are generally concave if the input is.
 * Computed hulls are topology-preserving:
 * they do not contain any self-intersections or overlaps,
 * so the result polygonal geometry is valid.
 *
 * Polygons with holes and MultiPolygons are supported.
 * The result has the same geometric type and structure as the input.
 *
 * The number of vertices in the computed hull is determined by a target parameter.
 * Two parameters are supported:
 *
 *  * Vertex Number fraction: the fraction of the input vertices retained in the result.
 *    Value 1 produces the original geometry.
 *    Smaller values produce less concave results.
 *    For outer hulls, value 0 produces the convex hull (with triangles for any holes).
 *    For inner hulls, value 0 produces a triangle (if no holes are present).
 *
 *  * Area Delta ratio: the ratio of the change in area to the input area.
 *    Value 0 produces the original geometry.
 *    Larger values produce less concave results.
 *
 * The algorithm ensures that the result does not cause the target parameter
 * to be exceeded.  This allows computing outer or inner hulls
 * with a small area delta ratio as an effective way of removing
 * narrow gores and spikes.
 *
 * @author Martin Davis
 *
 */
class GEOS_DLL PolygonHullSimplifier
{
    using Envelope = geos::geom::Envelope;
    using Geometry = geos::geom::Geometry;
    using GeometryFactory = geos::geom::GeometryFactory;
    using LinearRing = geos::geom::LinearRing;
    using MultiPolygon = geos::geom::MultiPolygon;
    using Polygon = geos::geom::Polygon;

public:

    /**
    * Creates a new instance
    * to compute a simplified hull of a polygonal geometry.
    * An outer or inner hull is computed
    * depending on the value of "isOuter".
    *
    * @param geom the polygonal geometry to process
    * @param bOuter indicates whether to compute an outer or inner hull
    */
    PolygonHullSimplifier(const Geometry* geom, bool bOuter)
        : inputGeom(geom)
        , geomFactory(geom->getFactory())
        , isOuter(bOuter)
        , vertexNumFraction(-1.0)
        , areaDeltaRatio(-1.0)
    {
        if (!geom->isPolygonal()) {
            throw util::IllegalArgumentException("Input geometry must be polygonal");
        }
    };

    /**
    * Computes a topology-preserving simplified hull of a polygonal geometry,
    * with hull shape determined by a target parameter
    * specifying the fraction of the input vertices retained in the result.
    * Larger values compute less concave results.
    * A value of 1 produces the convex hull; a value of 0 produces the original geometry.
    * Either outer or inner hulls can be computed.
    *
    * @param geom the polygonal geometry to process
    * @param isOuter indicates whether to compute an outer or inner hull
    * @param vertexNumFraction the target fraction of number of input vertices in result
    * @return the hull geometry
    */
    static std::unique_ptr<Geometry> hull(
        const Geometry* geom,
        bool isOuter,
        double vertexNumFraction);

    /**
    * Computes a topology-preserving simplified hull of a polygonal geometry,
    * with hull shape determined by a target parameter
    * specifying the ratio of maximum difference in area to original area.
    * Larger values compute less concave results.
    * A value of 0 produces the original geometry.
    * Either outer or inner hulls can be computed..
    *
    * @param geom the polygonal geometry to process
    * @param isOuter indicates whether to compute an outer or inner hull
    * @param areaDeltaRatio the target ratio of area difference to original area
    * @return the hull geometry
    */
    static std::unique_ptr<Geometry> hullByAreaDelta(
        const Geometry* geom,
        bool isOuter,
        double areaDeltaRatio);


    /**
    * Sets the target fraction of input vertices
    * which are retained in the result.
    * The value should be in the range [0,1].
    *
    * @param p_vertexNumFraction a fraction of the number of input vertices
    */
    void setVertexNumFraction(double p_vertexNumFraction);
    /**
    * Sets the target maximum ratio of the change in area of the result to the input area.
    * The value must be 0 or greater.
    *
    * @param p_areaDeltaRatio a ratio of the change in area of the result
    */
    void setAreaDeltaRatio(double p_areaDeltaRatio);

    /**
    * Gets the result polygonal hull geometry.
    *
    * @return the polygonal geometry for the hull
    */
    std::unique_ptr<Geometry> getResult();



private:

    // Members
    const Geometry* inputGeom;
    const GeometryFactory* geomFactory;
    bool isOuter;
    double vertexNumFraction;
    double areaDeltaRatio;
    // Allocate the RingHull* in here so they are cleaned
    // up with PolygonHullSimplifier
    std::vector<std::unique_ptr<RingHull>> ringStore;

    /**
    * Computes hulls for MultiPolygon elements for
    * the cases where hulls might overlap.
    *
    * @param multiPoly the MultiPolygon to process
    * @return the hull geometry
    */
    std::unique_ptr<Geometry> computeMultiPolygonAll(const MultiPolygon* multiPoly);
    std::unique_ptr<Geometry> computeMultiPolygonEach(const MultiPolygon* multiPoly);
    std::unique_ptr<Polygon> computePolygon(const Polygon* poly);

    /**
    * Create all ring hulls for the rings of a polygon,
    * so that all are in the hull index if required.
    *
    * @param poly the polygon being processed
    * @param hullIndex the hull index if present, or null
    * @return the list of ring hulls
    */
    std::vector<RingHull*> initPolygon(const Polygon* poly,
        RingHullIndex& hullIndex);

    double ringArea(const Polygon* poly) const;

    RingHull* createRingHull(
        const LinearRing* ring,
        bool isOuter,
        double areaTotal,
        RingHullIndex& hullIndex);

    std::unique_ptr<Polygon> polygonHull(
        const Polygon* poly,
        std::vector<RingHull*>& ringHulls,
        RingHullIndex& hullIndex) const;

    /**
     * Disable copy construction and assignment. Needed to make this
     * class compile under MSVC. (See https://stackoverflow.com/q/29565299)
     */
    PolygonHullSimplifier(const PolygonHullSimplifier&) = delete;
    PolygonHullSimplifier& operator=(const PolygonHullSimplifier&) = delete;

}; // PolygonHullSimplifier


} // geos::simplify
} // geos

