/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <sal/log.hxx>
#include <osl/diagnose.h>
#include <poly.h>
#include <tools/poly.hxx>
#include <tools/debug.hxx>
#include <tools/stream.hxx>
#include <tools/vcompat.hxx>
#include <tools/gen.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <basegfx/polygon/b2dpolygonclipper.hxx>

namespace tools {

PolyPolygon::PolyPolygon( sal_uInt16 nInitSize )
    : mpImplPolyPolygon( ImplPolyPolygon( nInitSize ) )
{
}

PolyPolygon::PolyPolygon( const tools::Polygon& rPoly )
    : mpImplPolyPolygon( rPoly )
{
}
PolyPolygon::PolyPolygon( const tools::Rectangle& rRect )
    : mpImplPolyPolygon( tools::Polygon(rRect) )
{
}

PolyPolygon::PolyPolygon( const tools::PolyPolygon& rPolyPoly )
    : mpImplPolyPolygon( rPolyPoly.mpImplPolyPolygon )
{
}

PolyPolygon::PolyPolygon( tools::PolyPolygon&& rPolyPoly ) noexcept
    : mpImplPolyPolygon( std::move(rPolyPoly.mpImplPolyPolygon) )
{
}

PolyPolygon::~PolyPolygon()
{
}

void PolyPolygon::Insert( const tools::Polygon& rPoly, sal_uInt16 nPos )
{
    assert ( mpImplPolyPolygon->mvPolyAry.size() < MAX_POLYGONS );

    if ( nPos > mpImplPolyPolygon->mvPolyAry.size() )
        nPos = mpImplPolyPolygon->mvPolyAry.size();

    mpImplPolyPolygon->mvPolyAry.insert(mpImplPolyPolygon->mvPolyAry.begin() + nPos, rPoly);
}

void PolyPolygon::Remove( sal_uInt16 nPos )
{
    assert(nPos < Count() && "PolyPolygon::Remove(): nPos >= nSize");

    mpImplPolyPolygon->mvPolyAry.erase(mpImplPolyPolygon->mvPolyAry.begin() + nPos);
}

void PolyPolygon::Replace( const tools::Polygon& rPoly, sal_uInt16 nPos )
{
    assert(nPos < Count() && "PolyPolygon::Replace(): nPos >= nSize");

    mpImplPolyPolygon->mvPolyAry[nPos] = rPoly;
}

const tools::Polygon& PolyPolygon::GetObject( sal_uInt16 nPos ) const
{
    assert(nPos < Count() && "PolyPolygon::GetObject(): nPos >= nSize");

    return mpImplPolyPolygon->mvPolyAry[nPos];
}

bool PolyPolygon::IsRect() const
{
    bool bIsRect = false;
    if ( Count() == 1 )
        bIsRect = mpImplPolyPolygon->mvPolyAry[ 0 ].IsRect();
    return bIsRect;
}

void PolyPolygon::Clear()
{
    mpImplPolyPolygon->mvPolyAry.clear();
}

void PolyPolygon::Optimize( PolyOptimizeFlags nOptimizeFlags )
{
    if(!(bool(nOptimizeFlags) && Count()))
        return;

    // #115630# ImplDrawHatch does not work with beziers included in the polypolygon, take care of that
    bool bIsCurve(false);

    for(sal_uInt16 a(0); !bIsCurve && a < Count(); a++)
    {
        if((*this)[a].HasFlags())
        {
            bIsCurve = true;
        }
    }

    if(bIsCurve)
    {
        OSL_ENSURE(false, "Optimize does *not* support curves, falling back to AdaptiveSubdivide()...");
        tools::PolyPolygon aPolyPoly;

        AdaptiveSubdivide(aPolyPoly);
        aPolyPoly.Optimize(nOptimizeFlags);
        *this = std::move(aPolyPoly);
    }
    else
    {
        double      fArea;
        const bool  bEdges = ( nOptimizeFlags & PolyOptimizeFlags::EDGES ) == PolyOptimizeFlags::EDGES;
        sal_uInt16      nPercent = 0;

        if( bEdges )
        {
            const tools::Rectangle aBound( GetBoundRect() );

            fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5;
            nPercent = 50;
            nOptimizeFlags &= ~PolyOptimizeFlags::EDGES;
        }

        // Optimize polygons
        for( sal_uInt16 i = 0, nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); i < nPolyCount; i++ )
        {
            if( bEdges )
            {
                mpImplPolyPolygon->mvPolyAry[ i ].Optimize( PolyOptimizeFlags::NO_SAME );
                tools::Polygon::ImplReduceEdges( mpImplPolyPolygon->mvPolyAry[ i ], fArea, nPercent );
            }

            if( bool(nOptimizeFlags) )
                mpImplPolyPolygon->mvPolyAry[ i ].Optimize( nOptimizeFlags );
        }
    }
}

void PolyPolygon::AdaptiveSubdivide( tools::PolyPolygon& rResult ) const
{
    rResult.Clear();

    tools::Polygon aPolygon;

    for( size_t i = 0; i < mpImplPolyPolygon->mvPolyAry.size(); i++ )
    {
        mpImplPolyPolygon->mvPolyAry[ i ].AdaptiveSubdivide( aPolygon, 1.0 );
        rResult.Insert( aPolygon );
    }
}

tools::PolyPolygon PolyPolygon::SubdivideBezier( const tools::PolyPolygon& rPolyPoly )
{
    sal_uInt16 i, nPolys = rPolyPoly.Count();
    tools::PolyPolygon aPolyPoly( nPolys );
    for( i=0; i<nPolys; ++i )
        aPolyPoly.Insert( tools::Polygon::SubdivideBezier( rPolyPoly.GetObject(i) ) );

    return aPolyPoly;
}


void PolyPolygon::GetIntersection( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const
{
    ImplDoOperation( rPolyPoly, rResult, PolyClipOp::INTERSECT );
}

void PolyPolygon::GetUnion( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const
{
    ImplDoOperation( rPolyPoly, rResult, PolyClipOp::UNION );
}

void PolyPolygon::ImplDoOperation( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult, PolyClipOp nOperation ) const
{
    // Convert to B2DPolyPolygon, temporarily. It might be
    // advantageous in the future, to have a tools::PolyPolygon adaptor that
    // just simulates a B2DPolyPolygon here...
    basegfx::B2DPolyPolygon aMergePolyPolygonA( getB2DPolyPolygon() );
    basegfx::B2DPolyPolygon aMergePolyPolygonB( rPolyPoly.getB2DPolyPolygon() );

    // normalize the two polypolygons before. Force properly oriented
    // polygons.
    aMergePolyPolygonA = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonA );
    aMergePolyPolygonB = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonB );

    switch( nOperation )
    {
        // All code extracted from svx/source/svdraw/svedtv2.cxx

        case PolyClipOp::UNION:
        {
            // merge A and B (OR)
            aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aMergePolyPolygonB);
            break;
        }

        default:
        case PolyClipOp::INTERSECT:
        {
            // cut poly 1 against polys 2..n (AND)
            aMergePolyPolygonA = basegfx::utils::solvePolygonOperationAnd(aMergePolyPolygonA, aMergePolyPolygonB);
            break;
        }
    }

    rResult = tools::PolyPolygon( aMergePolyPolygonA );
}

sal_uInt16 PolyPolygon::Count() const
{
    return mpImplPolyPolygon->mvPolyAry.size();
}

void PolyPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
{
    // Required for DrawEngine
    if( nHorzMove || nVertMove )
    {
        // move points
        sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
        for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
            mpImplPolyPolygon->mvPolyAry[i].Move( nHorzMove, nVertMove );
    }
}

void PolyPolygon::Translate( const Point& rTrans )
{
    // move points
    for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
        mpImplPolyPolygon->mvPolyAry[ i ].Translate( rTrans );
}

void PolyPolygon::Scale( double fScaleX, double fScaleY )
{
    // Move points
    for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
        mpImplPolyPolygon->mvPolyAry[ i ].Scale( fScaleX, fScaleY );
}

void PolyPolygon::Rotate( const Point& rCenter, Degree10 nAngle10 )
{
    nAngle10 %= 3600_deg10;

    if( nAngle10 )
    {
        const double fAngle = toRadians(nAngle10);
        Rotate( rCenter, sin( fAngle ), cos( fAngle ) );
    }
}

void PolyPolygon::Rotate( const Point& rCenter, double fSin, double fCos )
{
    // move points
    for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
        mpImplPolyPolygon->mvPolyAry[ i ].Rotate( rCenter, fSin, fCos );
}

void PolyPolygon::Clip( const tools::Rectangle& rRect )
{
    sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
    sal_uInt16 i;

    if ( !nPolyCount )
        return;

    // If there are bezier curves involved, Polygon::Clip() is broken.
    // Use a temporary B2DPolyPolygon for the clipping.
    for ( i = 0; i < nPolyCount; i++ )
    {
        if(mpImplPolyPolygon->mvPolyAry[i].HasFlags())
        {
            const basegfx::B2DPolyPolygon aPoly(
                basegfx::utils::clipPolyPolygonOnRange(
                    getB2DPolyPolygon(),
                    basegfx::B2DRange(
                        rRect.Left(),
                        rRect.Top(),
                        rRect.Right() + 1,
                        rRect.Bottom() + 1),
                    true,
                    false));
            *this = PolyPolygon( aPoly );
            return;
        }
    }

    // Clip every polygon, deleting the empty ones
    for ( i = 0; i < nPolyCount; i++ )
        mpImplPolyPolygon->mvPolyAry[i].Clip( rRect );
    while ( nPolyCount )
    {
        if ( GetObject( nPolyCount-1 ).GetSize() <= 2 )
            Remove( nPolyCount-1 );
        nPolyCount--;
    }
}

tools::Rectangle PolyPolygon::GetBoundRect() const
{
    tools::Long    nXMin=0, nXMax=0, nYMin=0, nYMax=0;
    bool    bFirst = true;
    sal_uInt16  nPolyCount = mpImplPolyPolygon->mvPolyAry.size();

    for ( sal_uInt16 n = 0; n < nPolyCount; n++ )
    {
        const tools::Polygon*  pPoly = &mpImplPolyPolygon->mvPolyAry[n];
        const Point*    pAry = pPoly->GetConstPointAry();
        sal_uInt16          nPointCount = pPoly->GetSize();

        for ( sal_uInt16 i = 0; i < nPointCount; i++ )
        {
            const Point* pPt = &pAry[ i ];

            if ( bFirst )
            {
                nXMin = nXMax = pPt->X();
                nYMin = nYMax = pPt->Y();
                bFirst = false;
            }
            else
            {
                if ( pPt->X() < nXMin )
                    nXMin = pPt->X();
                if ( pPt->X() > nXMax )
                    nXMax = pPt->X();
                if ( pPt->Y() < nYMin )
                    nYMin = pPt->Y();
                if ( pPt->Y() > nYMax )
                    nYMax = pPt->Y();
            }
        }
    }

    if ( !bFirst )
        return tools::Rectangle( nXMin, nYMin, nXMax, nYMax );
    else
        return tools::Rectangle();
}

Polygon& PolyPolygon::operator[]( sal_uInt16 nPos )
{
    assert(nPos < Count() && "PolyPolygon::[](): nPos >= nSize");

    return mpImplPolyPolygon->mvPolyAry[nPos];
}

PolyPolygon& PolyPolygon::operator=( const tools::PolyPolygon& rPolyPoly )
{
    mpImplPolyPolygon = rPolyPoly.mpImplPolyPolygon;
    return *this;
}

PolyPolygon& PolyPolygon::operator=( tools::PolyPolygon&& rPolyPoly ) noexcept
{
    mpImplPolyPolygon = std::move(rPolyPoly.mpImplPolyPolygon);
    return *this;
}

bool PolyPolygon::operator==( const tools::PolyPolygon& rPolyPoly ) const
{
    return rPolyPoly.mpImplPolyPolygon == mpImplPolyPolygon;
}

SvStream& ReadPolyPolygon( SvStream& rIStream, tools::PolyPolygon& rPolyPoly )
{
    sal_uInt16 nPolyCount(0);

    // Read number of polygons
    rIStream.ReadUInt16( nPolyCount );

    const size_t nMinRecordSize = sizeof(sal_uInt16);
    const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize;
    if (nPolyCount > nMaxRecords)
    {
        SAL_WARN("tools", "Parsing error: " << nMaxRecords <<
                 " max possible entries, but " << nPolyCount << " claimed, truncating");
        nPolyCount = nMaxRecords;
    }

    if( nPolyCount )
    {
        rPolyPoly.mpImplPolyPolygon->mvPolyAry.reserve(nPolyCount);

        tools::Polygon aTempPoly;
        for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
        {
            ReadPolygon( rIStream, aTempPoly );
            if (aTempPoly.GetSize() == 0)
            {
                SAL_WARN("tools", "Parsing error: polygon with 0 points, ignoring");
                continue;
            }
            rPolyPoly.mpImplPolyPolygon->mvPolyAry.push_back(aTempPoly);
        }
    }
    else
        rPolyPoly = tools::PolyPolygon();

    return rIStream;
}

SvStream& WritePolyPolygon( SvStream& rOStream, const tools::PolyPolygon& rPolyPoly )
{
    // Write number of polygons
    sal_uInt16 nPolyCount = rPolyPoly.mpImplPolyPolygon->mvPolyAry.size();
    rOStream.WriteUInt16( nPolyCount );

    // output polygons
    for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
        WritePolygon( rOStream, rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] );

    return rOStream;
}

void PolyPolygon::Read( SvStream& rIStream )
{
    VersionCompatRead aCompat(rIStream);

    sal_uInt16 nPolyCount(0);

    // Read number of polygons
    rIStream.ReadUInt16( nPolyCount );

    const size_t nMinRecordSize = sizeof(sal_uInt16);
    const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize;
    if (nPolyCount > nMaxRecords)
    {
        SAL_WARN("tools", "Parsing error: " << nMaxRecords <<
                 " max possible entries, but " << nPolyCount << " claimed, truncating");
        nPolyCount = nMaxRecords;
    }

    if( nPolyCount )
    {
        mpImplPolyPolygon->mvPolyAry.clear();

        for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
        {
            tools::Polygon aTempPoly;
            aTempPoly.ImplRead( rIStream );
            mpImplPolyPolygon->mvPolyAry.emplace_back( aTempPoly );
        }
    }
    else
        *this = tools::PolyPolygon();
}

void PolyPolygon::Write( SvStream& rOStream ) const
{
    VersionCompatWrite aCompat(rOStream, 1);

    // Write number of polygons
    sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
    rOStream.WriteUInt16( nPolyCount );

    // Output polygons
    for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
        mpImplPolyPolygon->mvPolyAry[i].ImplWrite( rOStream );
}

// convert to basegfx::B2DPolyPolygon and return
basegfx::B2DPolyPolygon PolyPolygon::getB2DPolyPolygon() const
{
    basegfx::B2DPolyPolygon aRetval;

    for(size_t a(0); a < mpImplPolyPolygon->mvPolyAry.size(); a++)
    {
        tools::Polygon const & rCandidate = mpImplPolyPolygon->mvPolyAry[a];
        aRetval.append(rCandidate.getB2DPolygon());
    }

    return aRetval;
}

// constructor to convert from basegfx::B2DPolyPolygon
PolyPolygon::PolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
    : mpImplPolyPolygon(rPolyPolygon)
{
}

PolyPolygon::iterator PolyPolygon::begin()
{
    return mpImplPolyPolygon->begin();
}

PolyPolygon::iterator PolyPolygon::end()
{
    return mpImplPolyPolygon->end();
}

PolyPolygon::const_iterator PolyPolygon::begin() const
{
    return mpImplPolyPolygon->begin();
}

PolyPolygon::const_iterator PolyPolygon::end() const
{
    return mpImplPolyPolygon->end();
}

} /* namespace tools */

ImplPolyPolygon::ImplPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
{
    const sal_uInt16 nCount(sal_uInt16(rPolyPolygon.count()));
    DBG_ASSERT(sal_uInt32(nCount) == rPolyPolygon.count(),
        "PolyPolygon::PolyPolygon: Too many sub-polygons in given basegfx::B2DPolyPolygon (!)");

    if ( nCount )
    {
        mvPolyAry.resize( nCount );

        for(sal_uInt16 a(0); a < nCount; a++)
        {
            const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(sal_uInt32(a)));
            mvPolyAry[a] = tools::Polygon( aCandidate );
        }
    }
    else
       mvPolyAry.reserve(16);
}

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