/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 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: algorithm/ConvexHull.java r407 (JTS-1.12+)
 *
 **********************************************************************/

#pragma once

#include <geos/export.h>
#include <memory>
#include <vector>
#include <cassert>

// FIXME: avoid using Coordinate:: typedefs to avoid full include
#include <geos/algorithm/ConvexHull.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/Geometry.h>
#include <geos/util/UniqueCoordinateArrayFilter.h>
#include <geos/util/CoordinateArrayFilter.h>

#include "geos/util.h"

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class
#endif

// Forward declarations
namespace geos {
namespace geom {
class GeometryFactory;
}
}

namespace geos {
namespace algorithm { // geos::algorithm

/** \brief
 * Computes the convex hull of a Geometry.
 *
 * The convex hull is the smallest convex Geometry that contains all the
 * points in the input Geometry.
 *
 * Uses the Graham Scan algorithm.
 *
 * Last port: algorithm/ConvexHull.java rev. 1.26 (JTS-1.7)
 *
 */
class GEOS_DLL ConvexHull {
private:

    static constexpr std::size_t TUNING_REDUCE_SIZE = 50;

    const geom::Geometry* inputGeom;
    const geom::GeometryFactory* geomFactory;
    geom::Coordinate::ConstVect inputPts;

    /// Create a CoordinateSequence from the Coordinate::ConstVect
    /// This is needed to construct the geometries.
    /// Here coordinate copies happen
    /// The returned object is newly allocated !NO EXCEPTION SAFE!
    std::unique_ptr<geom::CoordinateSequence> toCoordinateSequence(geom::Coordinate::ConstVect& cv);

    void computeInnerOctolateralPts(
        const geom::Coordinate::ConstVect& src,
        geom::Coordinate::ConstVect& tgt);

    bool computeInnerOctolateralRing(
        const geom::Coordinate::ConstVect& src,
        geom::Coordinate::ConstVect& tgt);

    /**
     * Uses a heuristic to reduce the number of points scanned
     * to compute the hull.
     * The heuristic is to find a polygon guaranteed to
     * be in (or on) the hull, and eliminate all points inside it.
     * A quadrilateral defined by the extremal points
     * in the four orthogonal directions
     * can be used, but even more inclusive is
     * to use an octilateral defined by the points in the
     * 8 cardinal directions.
     *
     * Note that even if the method used to determine the polygon
     * vertices is not 100% robust, this does not affect the
     * robustness of the convex hull.
     *
     * To satisfy the requirements of the Graham Scan algorithm,
     * the resulting array has at least 3 entries.
     *
     * @param pts The vector of const Coordinate pointers
     *            to be reduced (to at least 3 elements)
     *
     * WARNING: the parameter will be modified
     *
     */
    void reduce(geom::Coordinate::ConstVect& pts);

    /// parameter will be modified
    void padArray3(geom::Coordinate::ConstVect& pts);

    /// parameter will be modified
    void preSort(geom::Coordinate::ConstVect& pts);

    /**
     * Given two points p and q compare them with respect to their radial
     * ordering about point o.  First checks radial ordering.
     * If points are collinear, the comparison is based
     * on their distance to the origin.
     *
     * p < q iff
     *
     * - ang(o-p) < ang(o-q) (e.g. o-p-q is CCW)
     * - or ang(o-p) == ang(o-q) && dist(o,p) < dist(o,q)
     *
     * @param o the origin
     * @param p a point
     * @param q another point
     * @return -1, 0 or 1 depending on whether p is less than,
     * equal to or greater than q
     */
    int polarCompare(const geom::Coordinate& o,
                     const geom::Coordinate& p,
                     const geom::Coordinate& q);

    void grahamScan(const geom::Coordinate::ConstVect& c,
                    geom::Coordinate::ConstVect& ps);

    /**
     * @param  vertices  the vertices of a linear ring,
     *                   which may or may not be
     *                   flattened (i.e. vertices collinear)
     *
     * @return           a 2-vertex LineString if the vertices are
     *                   collinear; otherwise, a Polygon with unnecessary
     *                   (collinear) vertices removed
     */
    std::unique_ptr<geom::Geometry> lineOrPolygon(const geom::Coordinate::ConstVect& vertices);

    /**
     * Write in 'cleaned' a version of 'input' with collinear
     * vertices removed.
     */
    void cleanRing(const geom::Coordinate::ConstVect& input,
                   geom::Coordinate::ConstVect& cleaned);

    /**
     * @return  whether the three coordinates are collinear
     *          and c2 lies between c1 and c3 inclusive
     */
    bool isBetween(
        const geom::Coordinate& c1,
        const geom::Coordinate& c2,
        const geom::Coordinate& c3);

    bool extractUnique(geom::Coordinate::ConstVect& pts, std::size_t maxPts);
    std::unique_ptr<geom::Geometry> createFewPointsResult();



public:

    /**
     * Create a new convex hull construction for the input Geometry.
     */
    ConvexHull(const geom::Geometry* newGeometry)
        : inputGeom(newGeometry)
        , geomFactory(newGeometry->getFactory())
    {
      util::ensureNoCurvedComponents(inputGeom);
    };

    ~ConvexHull() {};

    /**
     * Returns a Geometry that represents the convex hull of
     * the input geometry.
     * The returned geometry contains the minimal number of points
     * needed to represent the convex hull.
     * In particular, no more than two consecutive points
     * will be collinear.
     *
     * @return if the convex hull contains 3 or more points,
     *         a Polygon; 2 points, a LineString;
     *         1 point, a Point; 0 points, an empty GeometryCollection.
     */
    std::unique_ptr<geom::Geometry> getConvexHull();
};

} // namespace geos::algorithm
} // namespace geos

#ifdef _MSC_VER
#pragma warning(pop)
#endif


