/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/vector/b2dvector.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>

#include <vector>
#include <algorithm>
#include <memory>

#define SUBDIVIDE_FOR_CUT_TEST_COUNT        (50)

namespace basegfx
{
    namespace
    {

        class temporaryPoint
        {
            B2DPoint                            maPoint;        // the new point
            sal_uInt32                          mnIndex;        // index after which to insert
            double                              mfCut;          // parametric cut description [0.0 .. 1.0]

        public:
            temporaryPoint(const B2DPoint& rNewPoint, sal_uInt32 nIndex, double fCut)
            :   maPoint(rNewPoint),
                mnIndex(nIndex),
                mfCut(fCut)
            {
            }

            bool operator<(const temporaryPoint& rComp) const
            {
                if(mnIndex == rComp.mnIndex)
                {
                    return (mfCut < rComp.mfCut);
                }

                return (mnIndex < rComp.mnIndex);
            }

            const B2DPoint& getPoint() const { return maPoint; }
            sal_uInt32 getIndex() const { return mnIndex; }
            double getCut() const { return mfCut; }
        };

        typedef std::vector< temporaryPoint > temporaryPointVector;

        class temporaryPolygonData
        {
            B2DPolygon                              maPolygon;
            B2DRange                                maRange;
            temporaryPointVector                    maPoints;

        public:
            const B2DPolygon& getPolygon() const { return maPolygon; }
            void setPolygon(const B2DPolygon& rNew) { maPolygon = rNew; maRange = utils::getRange(maPolygon); }
            const B2DRange& getRange() const { return maRange; }
            temporaryPointVector& getTemporaryPointVector() { return maPoints; }
        };

        B2DPolygon mergeTemporaryPointsAndPolygon(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints)
        {
            // #i76891# mergeTemporaryPointsAndPolygon redesigned to be able to correctly handle
            // single edges with/without control points
            // #i101491# added counter for non-changing element count
            const sal_uInt32 nTempPointCount(rTempPoints.size());

            if(nTempPointCount)
            {
                B2DPolygon aRetval;
                const sal_uInt32 nCount(rCandidate.count());

                if(nCount)
                {
                    // sort temp points to assure increasing fCut values and increasing indices
                    std::sort(rTempPoints.begin(), rTempPoints.end());

                    // prepare loop
                    B2DCubicBezier aEdge;
                    sal_uInt32 nNewInd(0);

                    // add start point
                    aRetval.append(rCandidate.getB2DPoint(0));

                    for(sal_uInt32 a(0); a < nCount; a++)
                    {
                        // get edge
                        rCandidate.getBezierSegment(a, aEdge);

                        if(aEdge.isBezier())
                        {
                            // control vectors involved for this edge
                            double fLeftStart(0.0);

                            // now add all points targeted to be at this index
                            while (nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a && fLeftStart < 1.0)
                            {
                                const temporaryPoint& rTempPoint = rTempPoints[nNewInd++];

                                // split curve segment. Splits need to come sorted and need to be < 1.0. Also,
                                // since original segment is consumed from left to right, the cut values need
                                // to be scaled to the remaining part
                                B2DCubicBezier aLeftPart;
                                const double fRelativeSplitPoint((rTempPoint.getCut() - fLeftStart) / (1.0 - fLeftStart));
                                aEdge.split(fRelativeSplitPoint, &aLeftPart, &aEdge);
                                fLeftStart = rTempPoint.getCut();

                                // add left bow
                                aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), rTempPoint.getPoint());
                            }

                            // add remaining bow
                            aRetval.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
                        }
                        else
                        {
                            // add all points targeted to be at this index
                            while(nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a)
                            {
                                const temporaryPoint& rTempPoint = rTempPoints[nNewInd++];
                                const B2DPoint& aNewPoint(rTempPoint.getPoint());

                                // do not add points double
                                if(!aRetval.getB2DPoint(aRetval.count() - 1).equal(aNewPoint))
                                {
                                    aRetval.append(aNewPoint);
                                }
                            }

                            // add edge end point
                            aRetval.append(aEdge.getEndPoint());
                        }
                    }
                }

                if(rCandidate.isClosed())
                {
                    // set closed flag and correct last point (which is added double now).
                    utils::closeWithGeometryChange(aRetval);
                }

                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }

        void adaptAndTransferCutsWithBezierSegment(
            const temporaryPointVector& rPointVector, const B2DPolygon& rPolygon,
            sal_uInt32 nInd, temporaryPointVector& rTempPoints)
        {
            // assuming that the subdivision to create rPolygon used equidistant pieces
            // (as in adaptiveSubdivideByCount) it is now possible to calculate back the
            // cut positions in the polygon to relative cut positions on the original bezier
            // segment.
            const sal_uInt32 nEdgeCount(rPolygon.count() ? rPolygon.count() - 1 : 0);

            if(!rPointVector.empty() && nEdgeCount)
            {
                for( const auto& rTempPoint : rPointVector )
                {
                    const double fCutPosInPolygon(static_cast<double>(rTempPoint.getIndex()) + rTempPoint.getCut());
                    const double fRelativeCutPos(fCutPosInPolygon / static_cast<double>(nEdgeCount));
                    rTempPoints.emplace_back(rTempPoint.getPoint(), nInd, fRelativeCutPos);
                }
            }
        }

    } // end of anonymous namespace
} // end of namespace basegfx

namespace basegfx
{
    namespace
    {

        // predefines for calls to this methods before method implementation

        void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints, size_t* pPointLimit = nullptr);
        void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints);
        void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB);

        void findEdgeCutsTwoEdges(
            const B2DPoint& rCurrA, const B2DPoint& rNextA,
            const B2DPoint& rCurrB, const B2DPoint& rNextB,
            sal_uInt32 nIndA, sal_uInt32 nIndB,
            temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
        {
            // no null length edges
            if(rCurrA.equal(rNextA) || rCurrB.equal(rNextB))
                return;

            // no common start/end points, this can be no cuts
            if(rCurrB.equal(rCurrA) || rCurrB.equal(rNextA) || rNextB.equal(rCurrA) || rNextB.equal(rNextA))
                return;

            const B2DVector aVecA(rNextA - rCurrA);
            const B2DVector aVecB(rNextB - rCurrB);
            double fCut(aVecA.cross(aVecB));

            if(fTools::equalZero(fCut))
                return;

            const double fZero(0.0);
            const double fOne(1.0);
            fCut = (aVecB.getY() * (rCurrB.getX() - rCurrA.getX()) + aVecB.getX() * (rCurrA.getY() - rCurrB.getY())) / fCut;

            if (!fTools::betweenOrEqualEither(fCut, fZero, fOne))
                return;

            // it's a candidate, but also need to test parameter value of cut on line 2
            double fCut2;

            // choose the more precise version
            if(fabs(aVecB.getX()) > fabs(aVecB.getY()))
            {
                fCut2 = (rCurrA.getX() + (fCut * aVecA.getX()) - rCurrB.getX()) / aVecB.getX();
            }
            else
            {
                fCut2 = (rCurrA.getY() + (fCut * aVecA.getY()) - rCurrB.getY()) / aVecB.getY();
            }

            if (fTools::betweenOrEqualEither(fCut2, fZero, fOne))
            {
                // cut is in range, add point. Two edges can have only one cut, but
                // add a cut point to each list. The lists may be the same for
                // self intersections.
                const B2DPoint aCutPoint(interpolate(rCurrA, rNextA, fCut));
                rTempPointsA.emplace_back(aCutPoint, nIndA, fCut);
                rTempPointsB.emplace_back(aCutPoint, nIndB, fCut2);
            }
        }

        void findCutsAndTouchesAndCommonForBezier(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
        {
            // #i76891#
            // This new method is necessary since in findEdgeCutsBezierAndEdge and in findEdgeCutsTwoBeziers
            // it is not sufficient to use findCuts() recursively. This will indeed find the cuts between the
            // segments of the two temporarily adaptive subdivided bezier segments, but not the touches or
            // equal points of them.
            // It would be possible to find the touches using findTouches(), but at last with common points
            // the adding of cut points (temporary points) would fail. But for these temporarily adaptive
            // subdivided bezier segments, common points may be not very likely, but the bug shows that it
            // happens.
            // Touch points are a little bit more likely than common points. All in all it is best to use
            // a specialized method here which can profit from knowing that it is working on a special
            // family of B2DPolygons: no curve segments included and not closed.
            OSL_ENSURE(!rCandidateA.areControlPointsUsed() && !rCandidateB.areControlPointsUsed(), "findCutsAndTouchesAndCommonForBezier only works with subdivided polygons (!)");
            OSL_ENSURE(!rCandidateA.isClosed() && !rCandidateB.isClosed(), "findCutsAndTouchesAndCommonForBezier only works with opened polygons (!)");
            const sal_uInt32 nPointCountA(rCandidateA.count());
            const sal_uInt32 nPointCountB(rCandidateB.count());

            if(nPointCountA <= 1 || nPointCountB <= 1)
                return;

            const sal_uInt32 nEdgeCountA(nPointCountA - 1);
            const sal_uInt32 nEdgeCountB(nPointCountB - 1);
            B2DPoint aCurrA(rCandidateA.getB2DPoint(0));

            for(sal_uInt32 a(0); a < nEdgeCountA; a++)
            {
                const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1));
                const B2DRange aRangeA(aCurrA, aNextA);
                B2DPoint aCurrB(rCandidateB.getB2DPoint(0));

                for(sal_uInt32 b(0); b < nEdgeCountB; b++)
                {
                    const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1));
                    const B2DRange aRangeB(aCurrB, aNextB);

                    if(aRangeA.overlaps(aRangeB))
                    {
                        // no null length edges
                        if(!(aCurrA.equal(aNextA) || aCurrB.equal(aNextB)))
                        {
                            const B2DVector aVecA(aNextA - aCurrA);
                            const B2DVector aVecB(aNextB - aCurrB);
                            double fCutA(aVecA.cross(aVecB));

                            if(!fTools::equalZero(fCutA))
                            {
                                const double fZero(0.0);
                                const double fOne(1.0);
                                fCutA = (aVecB.getY() * (aCurrB.getX() - aCurrA.getX()) + aVecB.getX() * (aCurrA.getY() - aCurrB.getY())) / fCutA;

                                // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered
                                // as 0.0 cut. The 1.0 cut will be registered in the next loop step
                                if(fTools::moreOrEqual(fCutA, fZero) && fTools::less(fCutA, fOne))
                                {
                                    // it's a candidate, but also need to test parameter value of cut on line 2
                                    double fCutB;

                                    // choose the more precise version
                                    if(fabs(aVecB.getX()) > fabs(aVecB.getY()))
                                    {
                                        fCutB = (aCurrA.getX() + (fCutA * aVecA.getX()) - aCurrB.getX()) / aVecB.getX();
                                    }
                                    else
                                    {
                                        fCutB = (aCurrA.getY() + (fCutA * aVecA.getY()) - aCurrB.getY()) / aVecB.getY();
                                    }

                                    // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered
                                    // as 0.0 cut. The 1.0 cut will be registered in the next loop step
                                    if(fTools::moreOrEqual(fCutB, fZero) && fTools::less(fCutB, fOne))
                                    {
                                        // cut is in both ranges. Add points for A and B
                                        // #i111715# use fTools::equal instead of fTools::equalZero for better accuracy
                                        if(fTools::equal(fCutA, fZero))
                                        {
                                            // ignore for start point in first edge; this is handled
                                            // by outer methods and would just produce a double point
                                            if(a)
                                            {
                                                rTempPointsA.emplace_back(aCurrA, a, 0.0);
                                            }
                                        }
                                        else
                                        {
                                            const B2DPoint aCutPoint(interpolate(aCurrA, aNextA, fCutA));
                                            rTempPointsA.emplace_back(aCutPoint, a, fCutA);
                                        }

                                        // #i111715# use fTools::equal instead of fTools::equalZero for better accuracy
                                        if(fTools::equal(fCutB, fZero))
                                        {
                                            // ignore for start point in first edge; this is handled
                                            // by outer methods and would just produce a double point
                                            if(b)
                                            {
                                                rTempPointsB.emplace_back(aCurrB, b, 0.0);
                                            }
                                        }
                                        else
                                        {
                                            const B2DPoint aCutPoint(interpolate(aCurrB, aNextB, fCutB));
                                            rTempPointsB.emplace_back(aCutPoint, b, fCutB);
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // prepare next step
                    aCurrB = aNextB;
                }

                // prepare next step
                aCurrA = aNextA;
            }
        }

        void findEdgeCutsBezierAndEdge(
            const B2DCubicBezier& rCubicA,
            const B2DPoint& rCurrB, const B2DPoint& rNextB,
            sal_uInt32 nIndA, sal_uInt32 nIndB,
            temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
        {
            // find all cuts between given bezier segment and edge. Add an entry to the tempPoints
            // for each common point with the cut value describing the relative position on given
            // bezier segment and edge.
            B2DPolygon aTempPolygonA;
            B2DPolygon aTempPolygonEdge;
            temporaryPointVector aTempPointVectorA;
            temporaryPointVector aTempPointVectorEdge;

            // create subdivided polygons and find cuts between them
            // Keep adaptiveSubdivideByCount due to needed quality
            aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
            aTempPolygonA.append(rCubicA.getStartPoint());
            rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT);
            aTempPolygonEdge.append(rCurrB);
            aTempPolygonEdge.append(rNextB);

            // #i76891# using findCuts recursively is not sufficient here
            findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonEdge, aTempPointVectorA, aTempPointVectorEdge);

            if(!aTempPointVectorA.empty())
            {
                // adapt tempVector entries to segment
                adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA);
            }

            // append remapped tempVector entries for edge to tempPoints for edge
            for(const temporaryPoint & rTempPoint : aTempPointVectorEdge)
            {
                rTempPointsB.emplace_back(rTempPoint.getPoint(), nIndB, rTempPoint.getCut());
            }
        }

        void findEdgeCutsTwoBeziers(
            const B2DCubicBezier& rCubicA,
            const B2DCubicBezier& rCubicB,
            sal_uInt32 nIndA, sal_uInt32 nIndB,
            temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
        {
            // find all cuts between the two given bezier segments. Add an entry to the tempPoints
            // for each common point with the cut value describing the relative position on given
            // bezier segments.
            B2DPolygon aTempPolygonA;
            B2DPolygon aTempPolygonB;
            temporaryPointVector aTempPointVectorA;
            temporaryPointVector aTempPointVectorB;

            // create subdivided polygons and find cuts between them
            // Keep adaptiveSubdivideByCount due to needed quality
            aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
            aTempPolygonA.append(rCubicA.getStartPoint());
            rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT);
            aTempPolygonB.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
            aTempPolygonB.append(rCubicB.getStartPoint());
            rCubicB.adaptiveSubdivideByCount(aTempPolygonB, SUBDIVIDE_FOR_CUT_TEST_COUNT);

            // #i76891# using findCuts recursively is not sufficient here
            findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonB, aTempPointVectorA, aTempPointVectorB);

            if(!aTempPointVectorA.empty())
            {
                // adapt tempVector entries to segment
                adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA);
            }

            if(!aTempPointVectorB.empty())
            {
                // adapt tempVector entries to segment
                adaptAndTransferCutsWithBezierSegment(aTempPointVectorB, aTempPolygonB, nIndB, rTempPointsB);
            }
        }

        void findEdgeCutsOneBezier(
            const B2DCubicBezier& rCubicA,
            sal_uInt32 nInd, temporaryPointVector& rTempPoints)
        {
            // avoid expensive part of this method if possible
            // TODO: use hasAnyExtremum() method instead when it becomes available
            double fDummy;
            const bool bHasAnyExtremum = rCubicA.getMinimumExtremumPosition( fDummy );
            if( !bHasAnyExtremum )
                return;

            // find all self-intersections on the given bezier segment. Add an entry to the tempPoints
            // for each self intersection point with the cut value describing the relative position on given
            // bezier segment.
            B2DPolygon aTempPolygon;
            temporaryPointVector aTempPointVector;

            // create subdivided polygon and find cuts on it
            // Keep adaptiveSubdivideByCount due to needed quality
            aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
            aTempPolygon.append(rCubicA.getStartPoint());
            rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT);
            findCuts(aTempPolygon, aTempPointVector);

            if(!aTempPointVector.empty())
            {
                // adapt tempVector entries to segment
                adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints);
            }
        }

        void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints, size_t* pPointLimit)
        {
            // find out if there are edges with intersections (self-cuts). If yes, add
            // entries to rTempPoints accordingly
            const sal_uInt32 nPointCount(rCandidate.count());

            if(!nPointCount)
                return;

            const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);

            if(!nEdgeCount)
                return;

            const bool bCurvesInvolved(rCandidate.areControlPointsUsed());

            if(bCurvesInvolved)
            {
                B2DCubicBezier aCubicA;
                B2DCubicBezier aCubicB;

                for(sal_uInt32 a(0); a < nEdgeCount - 1; a++)
                {
                    rCandidate.getBezierSegment(a, aCubicA);
                    aCubicA.testAndSolveTrivialBezier();
                    const bool bEdgeAIsCurve(aCubicA.isBezier());
                    const B2DRange aRangeA(aCubicA.getRange());

                    if(bEdgeAIsCurve)
                    {
                        // curved segments may have self-intersections, do not forget those (!)
                        findEdgeCutsOneBezier(aCubicA, a, rTempPoints);
                    }

                    for(sal_uInt32 b(a + 1); b < nEdgeCount; b++)
                    {
                        rCandidate.getBezierSegment(b, aCubicB);
                        aCubicB.testAndSolveTrivialBezier();
                        const B2DRange aRangeB(aCubicB.getRange());

                        // only overlapping segments need to be tested
                        // consecutive segments touch of course
                        bool bOverlap = false;
                        if( b > a+1)
                            bOverlap = aRangeA.overlaps(aRangeB);
                        else
                            bOverlap = aRangeA.overlapsMore(aRangeB);
                        if( bOverlap)
                        {
                            const bool bEdgeBIsCurve(aCubicB.isBezier());
                            if(bEdgeAIsCurve && bEdgeBIsCurve)
                            {
                                // test for bezier-bezier cuts
                                findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPoints, rTempPoints);
                            }
                            else if(bEdgeAIsCurve)
                            {
                                // test for bezier-edge cuts
                                findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPoints, rTempPoints);
                            }
                            else if(bEdgeBIsCurve)
                            {
                                // test for bezier-edge cuts
                                findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPoints, rTempPoints);
                            }
                            else
                            {
                                // test for simple edge-edge cuts
                                findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(),
                                    a, b, rTempPoints, rTempPoints);
                            }
                        }
                    }
                }
            }
            else
            {
                B2DPoint aCurrA(rCandidate.getB2DPoint(0));

                for(sal_uInt32 a(0); a < nEdgeCount - 1; a++)
                {
                    const B2DPoint aNextA(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
                    const B2DRange aRangeA(aCurrA, aNextA);
                    B2DPoint aCurrB(rCandidate.getB2DPoint(a + 1));

                    for(sal_uInt32 b(a + 1); b < nEdgeCount; b++)
                    {
                        const B2DPoint aNextB(rCandidate.getB2DPoint(b + 1 == nPointCount ? 0 : b + 1));
                        const B2DRange aRangeB(aCurrB, aNextB);

                        // consecutive segments touch of course
                        bool bOverlap = false;
                        if( b > a+1)
                            bOverlap = aRangeA.overlaps(aRangeB);
                        else
                            bOverlap = aRangeA.overlapsMore(aRangeB);
                        if( bOverlap)
                        {
                            findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPoints, rTempPoints);
                        }

                        if (pPointLimit && rTempPoints.size() > *pPointLimit)
                            break;

                        // prepare next step
                        aCurrB = aNextB;
                    }

                    // prepare next step
                    aCurrA = aNextA;
                }
            }

            if (pPointLimit)
            {
                if (rTempPoints.size() > *pPointLimit)
                    *pPointLimit = 0;
                else
                    *pPointLimit -= rTempPoints.size();
            }
        }

    } // end of anonymous namespace
} // end of namespace basegfx

namespace basegfx
{
    namespace
    {

        void findTouchesOnEdge(
            const B2DPoint& rCurr, const B2DPoint& rNext, const B2DPolygon& rPointPolygon,
            sal_uInt32 nInd, temporaryPointVector& rTempPoints)
        {
            // find out if points from rPointPolygon are positioned on given edge. If Yes, add
            // points there to represent touches (which may be enter or leave nodes later).
            const sal_uInt32 nPointCount(rPointPolygon.count());

            if(!nPointCount)
                return;

            const B2DRange aRange(rCurr, rNext);
            const B2DVector aEdgeVector(rNext - rCurr);
            bool bTestUsingX(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY()));

            for(sal_uInt32 a(0); a < nPointCount; a++)
            {
                const B2DPoint aTestPoint(rPointPolygon.getB2DPoint(a));

                if(aRange.isInside(aTestPoint))
                {
                    if(!aTestPoint.equal(rCurr) && !aTestPoint.equal(rNext))
                    {
                        const B2DVector aTestVector(aTestPoint - rCurr);

                        if(areParallel(aEdgeVector, aTestVector))
                        {
                            const double fCut(bTestUsingX
                                ? aTestVector.getX() / aEdgeVector.getX()
                                : aTestVector.getY() / aEdgeVector.getY());
                            const double fZero(0.0);
                            const double fOne(1.0);

                            if(fTools::more(fCut, fZero) && fTools::less(fCut, fOne))
                            {
                                rTempPoints.emplace_back(aTestPoint, nInd, fCut);
                            }
                        }
                    }
                }
            }
        }

        void findTouchesOnCurve(
            const B2DCubicBezier& rCubicA, const B2DPolygon& rPointPolygon,
            sal_uInt32 nInd, temporaryPointVector& rTempPoints)
        {
            // find all points from rPointPolygon which touch the given bezier segment. Add an entry
            // for each touch to the given pointVector. The cut for that entry is the relative position on
            // the given bezier segment.
            B2DPolygon aTempPolygon;
            temporaryPointVector aTempPointVector;

            // create subdivided polygon and find cuts on it
            // Keep adaptiveSubdivideByCount due to needed quality
            aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
            aTempPolygon.append(rCubicA.getStartPoint());
            rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT);
            findTouches(aTempPolygon, rPointPolygon, aTempPointVector);

            if(!aTempPointVector.empty())
            {
                // adapt tempVector entries to segment
                adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints);
            }
        }

        void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints)
        {
            // find out if points from rPointPolygon touch edges from rEdgePolygon. If yes,
            // add entries to rTempPoints
            const sal_uInt32 nPointCount(rPointPolygon.count());
            const sal_uInt32 nEdgePointCount(rEdgePolygon.count());

            if(!(nPointCount && nEdgePointCount))
                return;

            const sal_uInt32 nEdgeCount(rEdgePolygon.isClosed() ? nEdgePointCount : nEdgePointCount - 1);
            B2DPoint aCurr(rEdgePolygon.getB2DPoint(0));

            for(sal_uInt32 a(0); a < nEdgeCount; a++)
            {
                const sal_uInt32 nNextIndex((a + 1) % nEdgePointCount);
                const B2DPoint aNext(rEdgePolygon.getB2DPoint(nNextIndex));

                if(!aCurr.equal(aNext))
                {
                    bool bHandleAsSimpleEdge(true);

                    if(rEdgePolygon.areControlPointsUsed())
                    {
                        const B2DPoint aNextControlPoint(rEdgePolygon.getNextControlPoint(a));
                        const B2DPoint aPrevControlPoint(rEdgePolygon.getPrevControlPoint(nNextIndex));
                        const bool bEdgeIsCurve(!aNextControlPoint.equal(aCurr) || !aPrevControlPoint.equal(aNext));

                        if(bEdgeIsCurve)
                        {
                            bHandleAsSimpleEdge = false;
                            const B2DCubicBezier aCubicA(aCurr, aNextControlPoint, aPrevControlPoint, aNext);
                            findTouchesOnCurve(aCubicA, rPointPolygon, a, rTempPoints);
                        }
                    }

                    if(bHandleAsSimpleEdge)
                    {
                        findTouchesOnEdge(aCurr, aNext, rPointPolygon, a, rTempPoints);
                    }
                }

                // next step
                aCurr = aNext;
            }
        }

    } // end of anonymous namespace
} // end of namespace basegfx

namespace basegfx
{
    namespace
    {

        void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
        {
            // find out if edges from both polygons cut. If so, add entries to rTempPoints which
            // should be added to the polygons accordingly
            const sal_uInt32 nPointCountA(rCandidateA.count());
            const sal_uInt32 nPointCountB(rCandidateB.count());

            if(!(nPointCountA && nPointCountB))
                return;

            const sal_uInt32 nEdgeCountA(rCandidateA.isClosed() ? nPointCountA : nPointCountA - 1);
            const sal_uInt32 nEdgeCountB(rCandidateB.isClosed() ? nPointCountB : nPointCountB - 1);

            if(!(nEdgeCountA && nEdgeCountB))
                return;

            const bool bCurvesInvolved(rCandidateA.areControlPointsUsed() || rCandidateB.areControlPointsUsed());

            if(bCurvesInvolved)
            {
                B2DCubicBezier aCubicA;
                B2DCubicBezier aCubicB;

                for(sal_uInt32 a(0); a < nEdgeCountA; a++)
                {
                    rCandidateA.getBezierSegment(a, aCubicA);
                    aCubicA.testAndSolveTrivialBezier();
                    const bool bEdgeAIsCurve(aCubicA.isBezier());
                    const B2DRange aRangeA(aCubicA.getRange());

                    for(sal_uInt32 b(0); b < nEdgeCountB; b++)
                    {
                        rCandidateB.getBezierSegment(b, aCubicB);
                        aCubicB.testAndSolveTrivialBezier();
                        const B2DRange aRangeB(aCubicB.getRange());

                        // consecutive segments touch of course
                        bool bOverlap = false;
                        if( b > a+1)
                            bOverlap = aRangeA.overlaps(aRangeB);
                        else
                            bOverlap = aRangeA.overlapsMore(aRangeB);
                        if( bOverlap)
                        {
                            const bool bEdgeBIsCurve(aCubicB.isBezier());
                            if(bEdgeAIsCurve && bEdgeBIsCurve)
                            {
                                // test for bezier-bezier cuts
                                findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPointsA, rTempPointsB);
                            }
                            else if(bEdgeAIsCurve)
                            {
                                // test for bezier-edge cuts
                                findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPointsA, rTempPointsB);
                            }
                            else if(bEdgeBIsCurve)
                            {
                                // test for bezier-edge cuts
                                findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPointsB, rTempPointsA);
                            }
                            else
                            {
                                // test for simple edge-edge cuts
                                findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(),
                                    a, b, rTempPointsA, rTempPointsB);
                            }
                        }
                    }
                }
            }
            else
            {
                B2DPoint aCurrA(rCandidateA.getB2DPoint(0));

                for(sal_uInt32 a(0); a < nEdgeCountA; a++)
                {
                    const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1 == nPointCountA ? 0 : a + 1));
                    const B2DRange aRangeA(aCurrA, aNextA);
                    B2DPoint aCurrB(rCandidateB.getB2DPoint(0));

                    for(sal_uInt32 b(0); b < nEdgeCountB; b++)
                    {
                        const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1 == nPointCountB ? 0 : b + 1));
                        const B2DRange aRangeB(aCurrB, aNextB);

                        // consecutive segments touch of course
                        bool bOverlap = false;
                        if( b > a+1)
                            bOverlap = aRangeA.overlaps(aRangeB);
                        else
                            bOverlap = aRangeA.overlapsMore(aRangeB);
                        if( bOverlap)
                        {
                            // test for simple edge-edge cuts
                            findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPointsA, rTempPointsB);
                        }

                        // prepare next step
                        aCurrB = aNextB;
                    }

                    // prepare next step
                    aCurrA = aNextA;
                }
            }
        }

    } // end of anonymous namespace
} // end of namespace basegfx

namespace basegfx::utils
{

        B2DPolygon addPointsAtCutsAndTouches(const B2DPolygon& rCandidate, size_t* pPointLimit)
        {
            if(rCandidate.count())
            {
                temporaryPointVector aTempPoints;

                findTouches(rCandidate, rCandidate, aTempPoints);
                findCuts(rCandidate, aTempPoints, pPointLimit);
                if (pPointLimit && !*pPointLimit)
                {
                    SAL_WARN("basegfx", "addPointsAtCutsAndTouches hit point limit");
                    return rCandidate;
                }

                return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
            }
            else
            {
                return rCandidate;
            }
        }

        B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rCandidate, size_t* pPointLimit)
        {
            const sal_uInt32 nCount(rCandidate.count());

            if(nCount)
            {
                B2DPolyPolygon aRetval;

                if(nCount == 1)
                {
                    // remove self intersections
                    aRetval.append(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(0), pPointLimit));
                }
                else
                {
                    // first solve self cuts and self touches for all contained single polygons
                    std::unique_ptr<temporaryPolygonData[]> pTempData(new temporaryPolygonData[nCount]);
                    sal_uInt32 a, b;

                    for(a = 0; a < nCount; a++)
                    {
                        // use polygons with solved self intersections
                        pTempData[a].setPolygon(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(a), pPointLimit));
                    }

                    if (pPointLimit && !*pPointLimit)
                    {
                        SAL_WARN("basegfx", "addPointsAtCutsAndTouches hit point limit");
                        return rCandidate;
                    }

                    // now cuts and touches between the polygons
                    for(a = 0; a < nCount; a++)
                    {
                        for(b = 0; b < nCount; b++)
                        {
                            if(a != b)
                            {
                                // look for touches, compare each edge polygon to all other points
                                if(pTempData[a].getRange().overlaps(pTempData[b].getRange()))
                                {
                                    findTouches(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector());
                                }
                            }

                            if(a < b)
                            {
                                // look for cuts, compare each edge polygon to following ones
                                if(pTempData[a].getRange().overlaps(pTempData[b].getRange()))
                                {
                                    findCuts(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector(), pTempData[b].getTemporaryPointVector());
                                }
                            }
                        }
                    }

                    // consolidate the result
                    for(a = 0; a < nCount; a++)
                    {
                        aRetval.append(mergeTemporaryPointsAndPolygon(pTempData[a].getPolygon(), pTempData[a].getTemporaryPointVector()));
                    }
                }

                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }

        B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd)
        {
            const sal_uInt32 nCount(rCandidate.count());

            if(nCount && !rStart.equal(rEnd))
            {
                const B2DRange aPolygonRange(rCandidate.getB2DRange());
                const B2DRange aEdgeRange(rStart, rEnd);

                if(aPolygonRange.overlaps(aEdgeRange))
                {
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1);
                    temporaryPointVector aTempPoints;
                    temporaryPointVector aUnusedTempPoints;
                    B2DCubicBezier aCubic;

                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        rCandidate.getBezierSegment(a, aCubic);
                        B2DRange aCubicRange(aCubic.getStartPoint(), aCubic.getEndPoint());

                        if(aCubic.isBezier())
                        {
                            aCubicRange.expand(aCubic.getControlPointA());
                            aCubicRange.expand(aCubic.getControlPointB());

                            if(aCubicRange.overlaps(aEdgeRange))
                            {
                                findEdgeCutsBezierAndEdge(aCubic, rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints);
                            }
                        }
                        else
                        {
                            if(aCubicRange.overlaps(aEdgeRange))
                            {
                                findEdgeCutsTwoEdges(aCubic.getStartPoint(), aCubic.getEndPoint(), rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints);
                            }
                        }
                    }

                    return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
                }
            }

            return rCandidate;
        }

        B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPolyPolygon& rPolyMask)
        {
            const sal_uInt32 nCountA(rCandidate.count());
            const sal_uInt32 nCountM(rPolyMask.count());

            if(nCountA && nCountM)
            {
                const B2DRange aRangeA(rCandidate.getB2DRange());
                const B2DRange aRangeM(rPolyMask.getB2DRange());

                if(aRangeA.overlaps(aRangeM))
                {
                    const sal_uInt32 nEdgeCountA(rCandidate.isClosed() ? nCountA : nCountA - 1);
                    temporaryPointVector aTempPointsA;
                    temporaryPointVector aUnusedTempPointsB;

                    for(sal_uInt32 m(0); m < nCountM; m++)
                    {
                        const B2DPolygon& aMask(rPolyMask.getB2DPolygon(m));
                        const sal_uInt32 nCountB(aMask.count());

                        if(nCountB)
                        {
                            B2DCubicBezier aCubicA;
                            B2DCubicBezier aCubicB;

                            for(sal_uInt32 a(0); a < nEdgeCountA; a++)
                            {
                                rCandidate.getBezierSegment(a, aCubicA);
                                const bool bCubicAIsCurve(aCubicA.isBezier());
                                B2DRange aCubicRangeA(aCubicA.getStartPoint(), aCubicA.getEndPoint());

                                if(bCubicAIsCurve)
                                {
                                    aCubicRangeA.expand(aCubicA.getControlPointA());
                                    aCubicRangeA.expand(aCubicA.getControlPointB());
                                }

                                for(sal_uInt32 b(0); b < nCountB; b++)
                                {
                                    aMask.getBezierSegment(b, aCubicB);
                                    const bool bCubicBIsCurve(aCubicB.isBezier());
                                    B2DRange aCubicRangeB(aCubicB.getStartPoint(), aCubicB.getEndPoint());

                                    if(bCubicBIsCurve)
                                    {
                                        aCubicRangeB.expand(aCubicB.getControlPointA());
                                        aCubicRangeB.expand(aCubicB.getControlPointB());
                                    }

                                    if(aCubicRangeA.overlaps(aCubicRangeB))
                                    {
                                        if(bCubicAIsCurve && bCubicBIsCurve)
                                        {
                                            findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, aTempPointsA, aUnusedTempPointsB);
                                        }
                                        else if(bCubicAIsCurve)
                                        {
                                            findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB);
                                        }
                                        else if(bCubicBIsCurve)
                                        {
                                            findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, aUnusedTempPointsB, aTempPointsA);
                                        }
                                        else
                                        {
                                            findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB);
                                        }
                                    }
                                }
                            }
                        }
                    }

                    return mergeTemporaryPointsAndPolygon(rCandidate, aTempPointsA);
                }
            }

            return rCandidate;
        }

} // end of namespace

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
