/**************************************************************
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_basegfx.hxx"
#include <basegfx/tools/b2dclipstate.hxx>

#include <basegfx/range/b2drange.hxx>
#include <basegfx/range/b2dpolyrange.hxx>
#include <basegfx/range/b2drangeclipper.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>

namespace basegfx
{
namespace tools
{
    struct ImplB2DClipState
    {
    public:
        enum Operation {UNION, INTERSECT, XOR, SUBTRACT};

        ImplB2DClipState() :
            maPendingPolygons(),
            maPendingRanges(),
            maClipPoly(),
            mePendingOps(UNION)
        {}

        explicit ImplB2DClipState( const B2DRange& rRange ) :
            maPendingPolygons(),
            maPendingRanges(),
            maClipPoly(
                tools::createPolygonFromRect(rRange)),
            mePendingOps(UNION)
        {}

        explicit ImplB2DClipState( const B2DPolygon& rPoly ) :
            maPendingPolygons(),
            maPendingRanges(),
            maClipPoly(rPoly),
            mePendingOps(UNION)
        {}

        explicit ImplB2DClipState( const B2DPolyPolygon& rPoly ) :
            maPendingPolygons(),
            maPendingRanges(),
            maClipPoly(rPoly),
            mePendingOps(UNION)
        {}

        bool isCleared() const
        {
            return !maClipPoly.count()
                && !maPendingPolygons.count()
                && !maPendingRanges.count();
        }

        void makeClear()
        {
            maPendingPolygons.clear();
            maPendingRanges.clear();
            maClipPoly.clear();
            mePendingOps = UNION;
        }

        bool isNullClipPoly() const
        {
            return maClipPoly.count() == 1
                && !maClipPoly.getB2DPolygon(0).count();
        }

        bool isNull() const
        {
            return !maPendingPolygons.count()
                && !maPendingRanges.count()
                && isNullClipPoly();
        }

        void makeNull()
        {
            maPendingPolygons.clear();
            maPendingRanges.clear();
            maClipPoly.clear();
            maClipPoly.append(B2DPolygon());
            mePendingOps = UNION;
        }

        bool operator==(const ImplB2DClipState& rRHS) const
        {
            return maPendingPolygons == rRHS.maPendingPolygons
                && maPendingRanges == rRHS.maPendingRanges
                && maClipPoly == rRHS.maClipPoly
                && mePendingOps == rRHS.mePendingOps;
        }

        void addRange(const B2DRange& rRange, Operation eOp)
        {
            if( rRange.isEmpty() )
                return;

            commitPendingPolygons();
            if( mePendingOps != eOp )
                commitPendingRanges();

            mePendingOps = eOp;
            maPendingRanges.appendElement(
                rRange,
                ORIENTATION_POSITIVE);
        }

        void addPolygon(B2DPolygon aPoly, Operation eOp)
        {
            commitPendingRanges();
            if( mePendingOps != eOp )
                commitPendingPolygons();

            mePendingOps = eOp;
            maPendingPolygons.append(aPoly);
        }

        void addPolyPolygon(B2DPolyPolygon aPoly, Operation eOp)
        {
            commitPendingRanges();
            if( mePendingOps != eOp )
                commitPendingPolygons();

            mePendingOps = eOp;
            maPendingPolygons.append(aPoly);
        }

        void addClipState(const ImplB2DClipState& rOther, Operation eOp)
        {
            if( rOther.mePendingOps == mePendingOps
                && !rOther.maClipPoly.count()
                && !rOther.maPendingPolygons.count() )
            {
                maPendingRanges.appendPolyRange( rOther.maPendingRanges );
            }
            else
            {
                commitPendingRanges();
                commitPendingPolygons();
                rOther.commitPendingRanges();
                rOther.commitPendingPolygons();

                maPendingPolygons = rOther.maClipPoly;
                mePendingOps = eOp;
            }
        }

        void unionRange(const B2DRange& rRange)
        {
            if( isCleared() )
                return;

            addRange(rRange,UNION);
        }

        void unionPolygon(const B2DPolygon& rPoly)
        {
            if( isCleared() )
                return;

            addPolygon(rPoly,UNION);
        }

        void unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
        {
            if( isCleared() )
                return;

            addPolyPolygon(rPolyPoly,UNION);
        }

        void unionClipState(const ImplB2DClipState& rOther)
        {
            if( isCleared() )
                return;

            addClipState(rOther, UNION);
        }

        void intersectRange(const B2DRange& rRange)
        {
            if( isNull() )
                return;

            addRange(rRange,INTERSECT);
        }

        void intersectPolygon(const B2DPolygon& rPoly)
        {
            if( isNull() )
                return;

            addPolygon(rPoly,INTERSECT);
        }

        void intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
        {
            if( isNull() )
                return;

            addPolyPolygon(rPolyPoly,INTERSECT);
        }

        void intersectClipState(const ImplB2DClipState& rOther)
        {
            if( isNull() )
                return;

            addClipState(rOther, INTERSECT);
        }

        void subtractRange(const B2DRange& rRange )
        {
            if( isNull() )
                return;

            addRange(rRange,SUBTRACT);
        }

        void subtractPolygon(const B2DPolygon& rPoly)
        {
            if( isNull() )
                return;

            addPolygon(rPoly,SUBTRACT);
        }

        void subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
        {
            if( isNull() )
                return;

            addPolyPolygon(rPolyPoly,SUBTRACT);
        }

        void subtractClipState(const ImplB2DClipState& rOther)
        {
            if( isNull() )
                return;

            addClipState(rOther, SUBTRACT);
        }

        void xorRange(const B2DRange& rRange)
        {
            addRange(rRange,XOR);
        }

        void xorPolygon(const B2DPolygon& rPoly)
        {
            addPolygon(rPoly,XOR);
        }

        void xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
        {
            addPolyPolygon(rPolyPoly,XOR);
        }

        void xorClipState(const ImplB2DClipState& rOther)
        {
            addClipState(rOther, XOR);
        }

        B2DPolyPolygon getClipPoly() const
        {
            commitPendingRanges();
            commitPendingPolygons();

            return maClipPoly;
        }

    private:
        void commitPendingPolygons() const
        {
            if( !maPendingPolygons.count() )
                return;

            // assumption: maClipPoly has kept polygons prepared for
            // clipping; i.e. no neutral polygons & correct
            // orientation
            maPendingPolygons = tools::prepareForPolygonOperation(maPendingPolygons);
            const bool bIsEmpty=isNullClipPoly();
            const bool bIsCleared=!maClipPoly.count();
            switch(mePendingOps)
            {
                case UNION:
                    OSL_ASSERT( !bIsCleared );

                    if( bIsEmpty )
                        maClipPoly = maPendingPolygons;
                    else
                        maClipPoly = tools::solvePolygonOperationOr(
                            maClipPoly,
                            maPendingPolygons);
                    break;
                case INTERSECT:
                    OSL_ASSERT( !bIsEmpty );

                    if( bIsCleared )
                        maClipPoly = maPendingPolygons;
                    else
                        maClipPoly = tools::solvePolygonOperationAnd(
                            maClipPoly,
                            maPendingPolygons);
                    break;
                case XOR:
                    if( bIsEmpty )
                        maClipPoly = maPendingPolygons;
                    else if( bIsCleared )
                    {
                        // not representable, strictly speaking,
                        // using polygons with the common even/odd
                        // or nonzero winding number fill rule. If
                        // we'd want to represent it, fill rule
                        // would need to be "non-negative winding
                        // number" (and we then would return
                        // 'holes' here)

                        // going for an ugly hack meanwhile
                        maClipPoly = tools::solvePolygonOperationXor(
                            B2DPolyPolygon(
                                tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
                            maPendingPolygons);
                    }
                    else
                        maClipPoly = tools::solvePolygonOperationXor(
                            maClipPoly,
                            maPendingPolygons);
                    break;
                case SUBTRACT:
                    OSL_ASSERT( !bIsEmpty );

                    // first union all pending ones, subtract en bloc then
                    maPendingPolygons = solveCrossovers(maPendingPolygons);
                    maPendingPolygons = stripNeutralPolygons(maPendingPolygons);
                    maPendingPolygons = stripDispensablePolygons(maPendingPolygons, false);

                    if( bIsCleared )
                    {
                        // not representable, strictly speaking,
                        // using polygons with the common even/odd
                        // or nonzero winding number fill rule. If
                        // we'd want to represent it, fill rule
                        // would need to be "non-negative winding
                        // number" (and we then would return
                        // 'holes' here)

                        // going for an ugly hack meanwhile
                        maClipPoly = tools::solvePolygonOperationDiff(
                            B2DPolyPolygon(
                                tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
                            maPendingPolygons);
                    }
                    else
                        maClipPoly = tools::solvePolygonOperationDiff(
                            maClipPoly,
                            maPendingPolygons);
                    break;
            }

            maPendingPolygons.clear();
            mePendingOps = UNION;
        }

        void commitPendingRanges() const
        {
            if( !maPendingRanges.count() )
                return;

            // use the specialized range clipper for the win
            B2DPolyPolygon aCollectedRanges;
            const bool bIsEmpty=isNullClipPoly();
            const bool bIsCleared=!maClipPoly.count();
            switch(mePendingOps)
            {
                case UNION:
                    OSL_ASSERT( !bIsCleared );

                    aCollectedRanges = maPendingRanges.solveCrossovers();
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
                    aCollectedRanges = stripDispensablePolygons(aCollectedRanges, false);
                    if( bIsEmpty )
                        maClipPoly = aCollectedRanges;
                    else
                        maClipPoly = tools::solvePolygonOperationOr(
                            maClipPoly,
                            aCollectedRanges);
                    break;
                case INTERSECT:
                    OSL_ASSERT( !bIsEmpty );

                    aCollectedRanges = maPendingRanges.solveCrossovers();
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
                    if( maPendingRanges.count() > 1 )
                        aCollectedRanges = stripDispensablePolygons(aCollectedRanges, true);

                    if( bIsCleared )
                        maClipPoly = aCollectedRanges;
                    else
                        maClipPoly = tools::solvePolygonOperationAnd(
                            maClipPoly,
                            aCollectedRanges);
                    break;
                case XOR:
                    aCollectedRanges = maPendingRanges.solveCrossovers();
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
                    aCollectedRanges = correctOrientations(aCollectedRanges);

                    if( bIsEmpty )
                        maClipPoly = aCollectedRanges;
                    else if( bIsCleared )
                    {
                        // not representable, strictly speaking,
                        // using polygons with the common even/odd
                        // or nonzero winding number fill rule. If
                        // we'd want to represent it, fill rule
                        // would need to be "non-negative winding
                        // number" (and we then would return
                        // 'holes' here)

                        // going for an ugly hack meanwhile
                        maClipPoly = tools::solvePolygonOperationXor(
                            B2DPolyPolygon(
                                tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
                            aCollectedRanges);
                    }
                    else
                        maClipPoly = tools::solvePolygonOperationXor(
                            maClipPoly,
                            aCollectedRanges);
                    break;
                case SUBTRACT:
                    OSL_ASSERT( !bIsEmpty );

                    // first union all pending ranges, subtract en bloc then
                    aCollectedRanges = maPendingRanges.solveCrossovers();
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
                    aCollectedRanges = stripDispensablePolygons(aCollectedRanges, false);

                    if( bIsCleared )
                    {
                        // not representable, strictly speaking,
                        // using polygons with the common even/odd
                        // or nonzero winding number fill rule. If
                        // we'd want to represent it, fill rule
                        // would need to be "non-negative winding
                        // number" (and we then would return
                        // 'holes' here)

                        // going for an ugly hack meanwhile
                        maClipPoly = tools::solvePolygonOperationDiff(
                            B2DPolyPolygon(
                                tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
                            aCollectedRanges);
                    }
                    else
                        maClipPoly = tools::solvePolygonOperationDiff(
                            maClipPoly,
                            aCollectedRanges);
                    break;
            }

            maPendingRanges.clear();
            mePendingOps = UNION;
        }

        mutable B2DPolyPolygon maPendingPolygons;
        mutable B2DPolyRange   maPendingRanges;
        mutable B2DPolyPolygon maClipPoly;
        mutable Operation      mePendingOps;
    };

    B2DClipState::B2DClipState() :
        mpImpl()
    {}

    B2DClipState::~B2DClipState()
    {}

    B2DClipState::B2DClipState( const B2DClipState& rOrig ) :
        mpImpl(rOrig.mpImpl)
    {}

    B2DClipState::B2DClipState( const B2DRange& rRange ) :
        mpImpl( ImplB2DClipState(rRange) )
    {}

    B2DClipState::B2DClipState( const B2DPolygon& rPoly ) :
        mpImpl( ImplB2DClipState(rPoly) )
    {}

    B2DClipState::B2DClipState( const B2DPolyPolygon& rPolyPoly ) :
        mpImpl( ImplB2DClipState(rPolyPoly) )
    {}

    B2DClipState& B2DClipState::operator=( const B2DClipState& rRHS )
    {
        mpImpl = rRHS.mpImpl;
        return *this;
    }

    void B2DClipState::makeUnique()
    {
        mpImpl.make_unique();
    }

    void B2DClipState::makeNull()
    {
        mpImpl->makeNull();
    }

    bool B2DClipState::isNull() const
    {
        return mpImpl->isNull();
    }

    void B2DClipState::makeClear()
    {
        mpImpl->makeClear();
    }

    bool B2DClipState::isCleared() const
    {
        return mpImpl->isCleared();
    }

    bool B2DClipState::operator==(const B2DClipState& rRHS) const
    {
        if(mpImpl.same_object(rRHS.mpImpl))
            return true;

        return ((*mpImpl) == (*rRHS.mpImpl));
    }

    bool B2DClipState::operator!=(const B2DClipState& rRHS) const
    {
        return !(*this == rRHS);
    }

    void B2DClipState::unionRange(const B2DRange& rRange)
    {
        mpImpl->unionRange(rRange);
    }

    void B2DClipState::unionPolygon(const B2DPolygon& rPoly)
    {
        mpImpl->unionPolygon(rPoly);
    }

    void B2DClipState::unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
    {
        mpImpl->unionPolyPolygon(rPolyPoly);
    }

    void B2DClipState::unionClipState(const B2DClipState& rState)
    {
        mpImpl->unionClipState(*rState.mpImpl);
    }

    void B2DClipState::intersectRange(const B2DRange& rRange)
    {
        mpImpl->intersectRange(rRange);
    }

    void B2DClipState::intersectPolygon(const B2DPolygon& rPoly)
    {
        mpImpl->intersectPolygon(rPoly);
    }

    void B2DClipState::intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
    {
        mpImpl->intersectPolyPolygon(rPolyPoly);
    }

    void B2DClipState::intersectClipState(const B2DClipState& rState)
    {
        mpImpl->intersectClipState(*rState.mpImpl);
    }

    void B2DClipState::subtractRange(const B2DRange& rRange)
    {
        mpImpl->subtractRange(rRange);
    }

    void B2DClipState::subtractPolygon(const B2DPolygon& rPoly)
    {
        mpImpl->subtractPolygon(rPoly);
    }

    void B2DClipState::subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
    {
        mpImpl->subtractPolyPolygon(rPolyPoly);
    }

    void B2DClipState::subtractClipState(const B2DClipState& rState)
    {
        mpImpl->subtractClipState(*rState.mpImpl);
    }

    void B2DClipState::xorRange(const B2DRange& rRange)
    {
        mpImpl->xorRange(rRange);
    }

    void B2DClipState::xorPolygon(const B2DPolygon& rPoly)
    {
        mpImpl->xorPolygon(rPoly);
    }

    void B2DClipState::xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
    {
        mpImpl->xorPolyPolygon(rPolyPoly);
    }

    void B2DClipState::xorClipState(const B2DClipState& rState)
    {
        mpImpl->xorClipState(*rState.mpImpl);
    }

    B2DPolyPolygon B2DClipState::getClipPoly() const
    {
        return mpImpl->getClipPoly();
    }

} // end of namespace tools
} // end of namespace basegfx

// eof