// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2021
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 4.0.2020.09.01

#pragma once

// Compute the convex hull of 2D points using a divide-and-conquer algorithm.
// This is an O(N log N) algorithm for N input points. The only way to ensure
// a correct result for the input vertices is to use an exact predicate for
// computing signs of various expressions. The implementation uses interval
// arithmetic and rational arithmetic for the predicate.

#include <Mathematics/ArbitraryPrecision.h>
#include <Mathematics/FPInterval.h>
#include <Mathematics/Line.h>
#include <Mathematics/Vector2.h>

// Uncomment this to assert when an infinite loop is encountered in
// ConvexHull2::GetTangent.
//#define GTE_THROW_ON_CONVEXHULL2_INFINITE_LOOP

namespace gte
{
    // The Real must be 'float' or 'double'.
    template <typename Real>
    class ConvexHull2
    {
    public:
        // Supporting constants and types for rational arithmetic used in
        // the exact predicate for sign computations.
        static int constexpr NumWords = std::is_same<Real, float>::value ? 18 : 132;
        using Rational = BSNumber<UIntegerFP32<NumWords>>;
        using Interval = FPInterval<Real>;

        // The class is a functor to support computing the convex hull of
        // multiple data sets using the same class object.
        ConvexHull2()
            :
            mEpsilon(static_cast<Real>(0)),
            mDimension(0),
            mLine(Vector2<Real>::Zero(), Vector2<Real>::Zero()),
            mRationalPoints{},
            mConverted{},
            mNumPoints(0),
            mNumUniquePoints(0),
            mPoints(nullptr)
        {
            static_assert(std::is_floating_point<Real>::value,
                "The input type must be 'float' or 'double'.");
        }

        // The input is the array of points whose convex hull is required. The
        // epsilon value is used to determine the intrinsic dimensionality of
        // the vertices (d = 0, 1, or 2).  When epsilon is positive, the
        // determination is fuzzy: points approximately the same point,
        // approximately on a line, or planar.  The return value is 'true' if
        // and only if the hull construction is successful.
        bool operator()(int numPoints, Vector2<Real> const* points, Real epsilon)
        {
            mEpsilon = std::max(epsilon, static_cast<Real>(0));
            mDimension = 0;
            mLine.origin = Vector2<Real>::Zero();
            mLine.direction = Vector2<Real>::Zero();
            mNumPoints = numPoints;
            mNumUniquePoints = 0;
            mPoints = points;
            mMerged.clear();
            mHull.clear();

            if (mNumPoints < 3)
            {
                // ConvexHull2 should be called with at least three points.
                return false;
            }

            IntrinsicsVector2<Real> info(mNumPoints, mPoints, mEpsilon);
            if (info.dimension == 0)
            {
                // mDimension is 0
                return false;
            }

            if (info.dimension == 1)
            {
                // The set is (nearly) collinear.
                mDimension = 1;
                mLine = Line2<Real>(info.origin, info.direction[0]);
                return false;
            }

            mDimension = 2;

            // Allocate storage for any rational points that must be
            // computed in the exact predicate.
            mRationalPoints.resize(mNumPoints);
            mConverted.resize(mNumPoints);
            std::fill(mConverted.begin(), mConverted.end(), 0u);

            // Sort the points.
            mHull.resize(mNumPoints);
            for (int i = 0; i < mNumPoints; ++i)
            {
                mHull[i] = i;
            }
            std::sort(mHull.begin(), mHull.end(),
                [points](int i0, int i1)
                {
                    if (points[i0][0] < points[i1][0])
                    {
                        return true;
                    }
                    if (points[i0][0] > points[i1][0])
                    {
                        return false;
                    }
                    return points[i0][1] < points[i1][1];
                }
            );

            // Remove duplicates.
            auto newEnd = std::unique(mHull.begin(), mHull.end(),
                [points](int i0, int i1)
                {
                    return points[i0] == points[i1];
                }
            );
            mHull.erase(newEnd, mHull.end());
            mNumUniquePoints = static_cast<int>(mHull.size());

            // Use a divide-and-conquer algorithm. The merge step computes
            // the convex hull of two convex polygons.
            mMerged.resize(mNumUniquePoints);
            int i0 = 0, i1 = mNumUniquePoints - 1;
            GetHull(i0, i1);
            mHull.resize(i1 - i0 + 1);
            return true;
        }

        // Dimensional information. If GetDimension() returns 1, the points
        // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can
        // sort these if you need a polyline output by projecting onto the
        // line each vertex X = P+t*D, where t = Dot(D,X-P).
        inline Real GetEpsilon() const
        {
            return mEpsilon;
        }

        inline int GetDimension() const
        {
            return mDimension;
        }

        inline Line2<Real> const& GetLine() const
        {
            return mLine;
        }

        // Member access.
        inline int GetNumPoints() const
        {
            return mNumPoints;
        }

        inline int GetNumUniquePoints() const
        {
            return mNumUniquePoints;
        }

        inline Vector2<Real> const* GetPoints() const
        {
            return mPoints;
        }

        // The convex hull is a convex polygon whose vertices are listed in
        // counterclockwise order.
        inline std::vector<int> const& GetHull() const
        {
            return mHull;
        }

    private:
        // Support for divide-and-conquer.
        void GetHull(int& i0, int& i1)
        {
            int numVertices = i1 - i0 + 1;
            if (numVertices > 1)
            {
                // Compute the middle index of input range.
                int mid = (i0 + i1) / 2;

                // Compute the hull of subsets (mid-i0+1 >= i1-mid).
                int j0 = i0, j1 = mid, j2 = mid + 1, j3 = i1;
                GetHull(j0, j1);
                GetHull(j2, j3);

                // Merge the convex hulls into a single convex hull.
                Merge(j0, j1, j2, j3, i0, i1);
            }
            // else: The convex hull is a single point.
        }

        void Merge(int j0, int j1, int j2, int j3, int& i0, int& i1)
        {
            // Subhull0 is to the left of subhull1 because of the initial
            // sorting of the points by x-components. We need to find two
            // mutually visible points, one on the left subhull and one on
            // the right subhull.
            int size0 = j1 - j0 + 1;
            int size1 = j3 - j2 + 1;

            int i;
            Vector2<Real> p;

            // Find the right-most point of the left subhull.
            Vector2<Real> pmax0 = mPoints[mHull[j0]];
            int imax0 = j0;
            for (i = j0 + 1; i <= j1; ++i)
            {
                p = mPoints[mHull[i]];
                if (pmax0 < p)
                {
                    pmax0 = p;
                    imax0 = i;
                }
            }

            // Find the left-most point of the right subhull.
            Vector2<Real> pmin1 = mPoints[mHull[j2]];
            int imin1 = j2;
            for (i = j2 + 1; i <= j3; ++i)
            {
                p = mPoints[mHull[i]];
                if (p < pmin1)
                {
                    pmin1 = p;
                    imin1 = i;
                }
            }

            // Get the lower tangent to hulls (LL = lower-left,
            // LR = lower-right).
            int iLL = imax0, iLR = imin1;
            GetTangent(j0, j1, j2, j3, iLL, iLR);

            // Get the upper tangent to hulls (UL = upper-left,
            // UR = upper-right).
            int iUL = imax0, iUR = imin1;
            GetTangent(j2, j3, j0, j1, iUR, iUL);

            // Construct the counterclockwise-ordered merged-hull vertices.
            int k;
            int numMerged = 0;

            i = iUL;
            for (k = 0; k < size0; ++k)
            {
                mMerged[numMerged++] = mHull[i];
                if (i == iLL)
                {
                    break;
                }
                i = (i < j1 ? i + 1 : j0);
            }
            LogAssert(k < size0, "Unexpected condition.");

            i = iLR;
            for (k = 0; k < size1; ++k)
            {
                mMerged[numMerged++] = mHull[i];
                if (i == iUR)
                {
                    break;
                }
                i = (i < j3 ? i + 1 : j2);
            }
            LogAssert(k < size1, "Unexpected condition.");

            int next = j0;
            for (k = 0; k < numMerged; ++k)
            {
                mHull[next] = mMerged[k];
                ++next;
            }

            i0 = j0;
            i1 = next - 1;
        }

        void GetTangent(int j0, int j1, int j2, int j3, int& i0, int& i1)
        {
            // In theory the loop terminates in a finite number of steps,
            // but the upper bound for the loop variable is used to trap
            // problems caused by floating-point roundoff errors that might
            // lead to an infinite loop.

            int size0 = j1 - j0 + 1;
            int size1 = j3 - j2 + 1;
            int const imax = size0 + size1;
            int i, iLm1, iRp1;
            int L0index, L1index, R0index, R1index;

            for (i = 0; i < imax; i++)
            {
                // Get the endpoints of the potential tangent.
                L1index = mHull[i0];
                R0index = mHull[i1];

                // Walk along the left hull to find the point of tangency.
                if (size0 > 1)
                {
                    iLm1 = (i0 > j0 ? i0 - 1 : j1);
                    L0index = mHull[iLm1];
                    auto order = ToLineExtended(R0index, L0index, L1index);
                    if (order == Order::NEGATIVE || order == Order::COLLINEAR_RIGHT)
                    {
                        i0 = iLm1;
                        continue;
                    }
                }

                // Walk along right hull to find the point of tangency.
                if (size1 > 1)
                {
                    iRp1 = (i1 < j3 ? i1 + 1 : j2);
                    R1index = mHull[iRp1];
                    auto order = ToLineExtended(L1index, R0index, R1index);
                    if (order == Order::NEGATIVE || order == Order::COLLINEAR_LEFT)
                    {
                        i1 = iRp1;
                        continue;
                    }
                }

                // The tangent segment has been found.
                break;
            }

            // Detect an "infinite loop" caused by floating point round-off
            // errors.
#if defined(GTE_THROW_ON_CONVEXHULL2_INFINITE_LOOP)
            LogAssert(i < imax, "Unexpected condition.");
#endif
        }

        // Memoized access to the rational representation of the points.
        Vector2<Rational> const& GetRationalPoint(int index) const
        {
            if (mConverted[index] == 0)
            {
                mConverted[index] = 1;
                for (int i = 0; i < 2; ++i)
                {
                    mRationalPoints[index][i] = mPoints[index][i];
                }
            }
            return mRationalPoints[index];
        }

        // An extended classification of the relationship of a point to a line
        // segment.  For noncollinear points, the return value is
        //   POSITIVE when <P,Q0,Q1> is a counterclockwise triangle
        //   NEGATIVE when <P,Q0,Q1> is a clockwise triangle
        // For collinear points, the line direction is Q1-Q0.  The return
        // value is
        //   COLLINEAR_LEFT when the line ordering is <P,Q0,Q1>
        //   COLLINEAR_RIGHT when the line ordering is <Q0,Q1,P>
        //   COLLINEAR_CONTAIN when the line ordering is <Q0,P,Q1>
        enum class Order
        {
            Q0_EQUALS_Q1,
            P_EQUALS_Q0,
            P_EQUALS_Q1,
            POSITIVE,
            NEGATIVE,
            COLLINEAR_LEFT,
            COLLINEAR_RIGHT,
            COLLINEAR_CONTAIN
        };

        Order ToLineExtended(int pIndex, int q0Index, int q1Index) const
        {
            Vector2<Real> const& P = mPoints[pIndex];
            Vector2<Real> const& Q0 = mPoints[q0Index];
            Vector2<Real> const& Q1 = mPoints[q1Index];

            if (Q1[0] == Q0[0] && Q1[1] == Q0[1])
            {
                return Order::Q0_EQUALS_Q1;
            }

            if (P[0] == Q0[0] && P[1] == Q0[1])
            {
                return Order::P_EQUALS_Q0;
            }

            if (P[0] == Q1[0] && P[1] == Q1[1])
            {
                return Order::P_EQUALS_Q1;
            }

            // The theoretical classification relies on computing exactly the
            // sign of the determinant.  Numerical roundoff errors can cause
            // misclassification.
            Real const zero(0);
            Interval ip0(P[0]), ip1(P[1]);
            Interval iq00(Q0[0]), iq01(Q0[1]), iq10(Q1[0]), iq11(Q1[1]);
            Interval ix0 = iq10 - iq00, iy0 = iq11 - iq01;
            Interval ix1 = ip0 - iq00, iy1 = ip1 - iq01;
            Interval ix0y1 = ix0 * iy1;
            Interval ix1y0 = ix1 * iy0;
            Interval iDet = ix0y1 - ix1y0;
            int32_t sign;

            Vector2<Rational> rDiff0, rDiff1;
            Rational rDot;
            bool rDiff0Computed = false;
            bool rDiff1Computed = false;
            bool rDotComputed = false;

            if (iDet[0] > zero)
            {
                sign = +1;
            }
            else if (iDet[1] < zero)
            {
                sign = -1;
            }
            else
            {
                // The exact sign of the determinant is not known, so compute
                // the determinant using rational arithmetic.
                auto const& rP = GetRationalPoint(pIndex);
                auto const& rQ0 = GetRationalPoint(q0Index);
                auto const& rQ1 = GetRationalPoint(q1Index);
                rDiff0 = rQ1 - rQ0;
                rDiff1 = rP - rQ0;
                auto rDet = DotPerp(rDiff0, rDiff1);
                rDiff0Computed = true;
                rDiff1Computed = true;
                sign = rDet.GetSign();
            }

            if (sign > 0)
            {
                // The points form a counterclockwise triangle <P,Q0,Q1>.
                return Order::POSITIVE;
            }
            else if (sign < 0)
            {
                // The points form a clockwise triangle <P,Q1,Q0>.
                return Order::NEGATIVE;
            }
            else
            {
                // The points are collinear. P is on the line through Q0
                // and Q1.
                Interval iDot = ix0 * ix1 + iy0 * iy1;
                if (iDot[0] > zero)
                {
                    sign = +1;
                }
                else if (iDot[1] < zero)
                {
                    sign = -1;
                }
                else
                {
                    // The exact sign of the dot product is not known, so
                    // compute the dot product using rational arithmetic.
                    auto const& rP = GetRationalPoint(pIndex);
                    auto const& rQ0 = GetRationalPoint(q0Index);
                    auto const& rQ1 = GetRationalPoint(q1Index);
                    if (!rDiff0Computed)
                    {
                        rDiff0 = rQ1 - rQ0;
                    }
                    if (!rDiff1Computed)
                    {
                        rDiff1 = rP - rQ0;
                    }
                    rDot = Dot(rDiff0, rDiff1);
                    rDotComputed = true;
                    sign = rDot.GetSign();
                }

                if (sign < zero)
                {
                    // The line ordering is <P,Q0,Q1>.
                    return Order::COLLINEAR_LEFT;
                }

                Interval iSqrLength = ix0 * ix0 + iy0 * iy0;
                Interval iTest = iDot - iSqrLength;
                if (iTest[0] > zero)
                {
                    sign = +1;
                }
                else if (iTest[1] < zero)
                {
                    sign = -1;
                }
                else
                {
                    // The exact sign of the test is not known, so
                    // compute the test using rational arithmetic.
                    auto const& rP = GetRationalPoint(pIndex);
                    auto const& rQ0 = GetRationalPoint(q0Index);
                    auto const& rQ1 = GetRationalPoint(q1Index);
                    if (!rDiff0Computed)
                    {
                        rDiff0 = rQ1 - rQ0;
                    }
                    if (!rDiff1Computed)
                    {
                        rDiff1 = rP - rQ0;
                    }
                    if (!rDotComputed)
                    {
                        rDot = Dot(rDiff0, rDiff1);
                    }
                    auto rSqrLength = Dot(rDiff0, rDiff0);
                    auto rTest = rDot - rSqrLength;
                    sign = rTest.GetSign();
                }

                if (sign > 0)
                {
                    // The line ordering is <Q0,Q1,P>.
                    return Order::COLLINEAR_RIGHT;
                }

                // The line ordering is <Q0,P,Q1> with P strictly between
                // Q0 and Q1.
                return Order::COLLINEAR_CONTAIN;
            }
        }

        // The epsilon value is used for fuzzy determination of intrinsic
        // dimensionality. If the dimension is 0 or 1, the constructor returns
        // early. The caller is responsible for retrieving the dimension and
        // taking an alternate path should the dimension be smaller than 2.
        // If the dimension is 0, the caller may as well treat all points[]
        // as a single point, say, points[0]. If the dimension is 1, the
        // caller can query for the approximating line and project points[]
        // onto it for further processing.
        Real mEpsilon;
        int mDimension;
        Line2<Real> mLine;

        // The array of rational points used for the exact predicate. The
        // mConverted array is used to store 0 or 1, where initially the
        // values are 0. The first time mComputePoints[i] is encountered,
        // mConverted[i] is 0. The floating-point vector is converted to
        // a rational number, after which mConverted[1] is set to 1 to
        // avoid converting again if the floating-point vector is
        // encountered in another predicate computation.
        mutable std::vector<Vector2<Rational>> mRationalPoints;
        mutable std::vector<uint32_t> mConverted;

        int mNumPoints;
        int mNumUniquePoints;
        Vector2<Real> const* mPoints;
        std::vector<int> mMerged, mHull;
    };
}