/* -*- 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 <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx>
#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <svtools/borderhelper.hxx>

namespace
{
    double snapToDiscreteUnit(
        double fValue,
        double fMinimalDiscreteUnit)
    {
        if(0.0 != fValue)
        {
            fValue = std::max(fValue, fMinimalDiscreteUnit);
        }

        return fValue;
    }

    class StyleVectorCombination
    {
    private:
        struct OffsetAndHalfWidthAndColor
        {
            double          mfOffset;
            double          mfHalfWidth;
            Color           maColor;

            OffsetAndHalfWidthAndColor(double offset, double halfWidth, Color color) :
                mfOffset(offset),
                mfHalfWidth(halfWidth),
                maColor(color)
            {}
        };

        double                                      mfRefModeOffset;
        basegfx::B2DVector                          maB2DVector;
        double                                      mfAngle;
        std::vector< OffsetAndHalfWidthAndColor >   maOffsets;

    public:
        StyleVectorCombination(
            const svx::frame::Style& rStyle,
            const basegfx::B2DVector& rB2DVector,
            double fAngle,
            bool bMirrored,
            const Color* pForceColor,
            double fMinimalDiscreteUnit)
        :   mfRefModeOffset(0.0),
            maB2DVector(rB2DVector),
            mfAngle(fAngle),
            maOffsets()
        {
            if (rStyle.IsUsed())
            {
                svx::frame::RefMode aRefMode(rStyle.GetRefMode());
                Color aPrim(rStyle.GetColorPrim());
                Color aSecn(rStyle.GetColorSecn());
                const bool bSecnUsed(0.0 != rStyle.Secn());

                // Get the single segment line widths. This is the point where the
                // minimal discrete unit will be used if given (fMinimalDiscreteUnit). If
                // not given it's 0.0 and thus will have no influence.
                double fPrim(snapToDiscreteUnit(rStyle.Prim(), fMinimalDiscreteUnit));
                const double fDist(snapToDiscreteUnit(rStyle.Dist(), fMinimalDiscreteUnit));
                double fSecn(snapToDiscreteUnit(rStyle.Secn(), fMinimalDiscreteUnit));

                // Of course also do not use svx::frame::Style::GetWidth() for obvious
                // reasons.
                const double fStyleWidth(fPrim + fDist + fSecn);

                if(bMirrored)
                {
                    switch(aRefMode)
                    {
                        case svx::frame::RefMode::Begin: aRefMode = svx::frame::RefMode::End; break;
                        case svx::frame::RefMode::End: aRefMode = svx::frame::RefMode::Begin; break;
                        default: break;
                    }

                    if(bSecnUsed)
                    {
                        std::swap(aPrim, aSecn);
                        std::swap(fPrim, fSecn);
                    }
                }

                if (svx::frame::RefMode::Centered != aRefMode)
                {
                    const double fHalfWidth(fStyleWidth * 0.5);

                    if (svx::frame::RefMode::Begin == aRefMode)
                    {
                        // move aligned below vector
                        mfRefModeOffset = fHalfWidth;
                    }
                    else if (svx::frame::RefMode::End == aRefMode)
                    {
                        // move aligned above vector
                        mfRefModeOffset = -fHalfWidth;
                    }
                }

                if (bSecnUsed)
                {
                    // both or all three lines used
                    const bool bPrimTransparent(0xff == rStyle.GetColorPrim().GetTransparency());
                    const bool bDistTransparent(!rStyle.UseGapColor() || 0xff == rStyle.GetColorGap().GetTransparency());
                    const bool bSecnTransparent(0xff == aSecn.GetTransparency());

                    if(!bPrimTransparent || !bDistTransparent || !bSecnTransparent)
                    {
                        const double a(mfRefModeOffset - (fStyleWidth * 0.5));
                        const double b(a + fPrim);
                        const double c(b + fDist);
                        const double d(c + fSecn);

                        maOffsets.push_back(
                            OffsetAndHalfWidthAndColor(
                                (a + b) * 0.5,
                                fPrim * 0.5,
                                nullptr != pForceColor ? *pForceColor : aPrim));

                        maOffsets.push_back(
                            OffsetAndHalfWidthAndColor(
                                (b + c) * 0.5,
                                fDist * 0.5,
                                rStyle.UseGapColor()
                                    ? (nullptr != pForceColor ? *pForceColor : rStyle.GetColorGap())
                                    : COL_TRANSPARENT));

                        maOffsets.push_back(
                            OffsetAndHalfWidthAndColor(
                                (c + d) * 0.5,
                                fSecn * 0.5,
                                nullptr != pForceColor ? *pForceColor : aSecn));
                    }
                }
                else
                {
                    // one line used, push two values, from outer to inner
                    if(0xff != rStyle.GetColorPrim().GetTransparency())
                    {
                        maOffsets.push_back(
                            OffsetAndHalfWidthAndColor(
                                mfRefModeOffset,
                                fPrim * 0.5,
                                nullptr != pForceColor ? *pForceColor : aPrim));
                    }
                }
            }
        }

        double getRefModeOffset() const { return mfRefModeOffset; }
        const basegfx::B2DVector& getB2DVector() const { return maB2DVector; }
        double getAngle() const { return mfAngle; }
        bool empty() const { return maOffsets.empty(); }
        size_t size() const { return maOffsets.size(); }

        void getColorAndOffsetAndHalfWidth(size_t nIndex, Color& rColor, double& rfOffset, double& rfHalfWidth) const
        {
            if(nIndex >= maOffsets.size())
                return;
            const OffsetAndHalfWidthAndColor& rCandidate(maOffsets[nIndex]);
            rfOffset = rCandidate.mfOffset;
            rfHalfWidth = rCandidate.mfHalfWidth;
            rColor = rCandidate.maColor;
        }
    };

    class StyleVectorTable
    {
    private:
        std::vector< StyleVectorCombination >       maEntries;

    public:
        StyleVectorTable()
        :   maEntries()
        {
        }

        void add(
            const svx::frame::Style& rStyle,
            const basegfx::B2DVector& rMyVector,
            const basegfx::B2DVector& rOtherVector,
            bool bMirrored,
            double fMinimalDiscreteUnit)
        {
            if(rStyle.IsUsed() && !basegfx::areParallel(rMyVector, rOtherVector))
            {
                // create angle between both. angle() needs vectors pointing away from the same point,
                // so take the mirrored one. Add F_PI to get from -pi..+pi to [0..F_PI2] for sorting
                const double fAngle(basegfx::B2DVector(-rMyVector.getX(), -rMyVector.getY()).angle(rOtherVector) + F_PI);
                maEntries.emplace_back(
                    rStyle,
                    rOtherVector,
                    fAngle,
                    bMirrored,
                    nullptr,
                    fMinimalDiscreteUnit);
            }
        }

        void sort()
        {
            // sort inverse fom highest to lowest
            std::sort(
                maEntries.begin(),
                maEntries.end(),
                [](const StyleVectorCombination& a, const StyleVectorCombination& b)
                    { return a.getAngle() > b.getAngle(); });
        }

        bool empty() const { return maEntries.empty(); }
        const std::vector< StyleVectorCombination >& getEntries() const{ return maEntries; }
    };

    struct CutSet
    {
        double          mfOLML;
        double          mfORML;
        double          mfOLMR;
        double          mfORMR;

        CutSet() : mfOLML(0.0), mfORML(0.0), mfOLMR(0.0), mfORMR(0.0)
        {
        }

        bool operator<( const CutSet& rOther) const
        {
            const double fA(mfOLML + mfORML + mfOLMR + mfORMR);
            const double fB(rOther.mfOLML + rOther.mfORML + rOther.mfOLMR + rOther.mfORMR);

            return fA < fB;
        }

        double getSum() const { return mfOLML + mfORML + mfOLMR + mfORMR; }
    };

    void getCutSet(
        CutSet& rCutSet,
        const basegfx::B2DPoint& rLeft,
        const basegfx::B2DPoint& rRight,
        const basegfx::B2DVector& rX,
        const basegfx::B2DPoint& rOtherLeft,
        const basegfx::B2DPoint& rOtherRight,
        const basegfx::B2DVector& rOtherX)
    {
        basegfx::utils::findCut(
            rLeft,
            rX,
            rOtherLeft,
            rOtherX,
            CutFlagValue::LINE,
            &rCutSet.mfOLML);

        basegfx::utils::findCut(
            rRight,
            rX,
            rOtherLeft,
            rOtherX,
            CutFlagValue::LINE,
            &rCutSet.mfOLMR);

        basegfx::utils::findCut(
            rLeft,
            rX,
            rOtherRight,
            rOtherX,
            CutFlagValue::LINE,
            &rCutSet.mfORML);

        basegfx::utils::findCut(
            rRight,
            rX,
            rOtherRight,
            rOtherX,
            CutFlagValue::LINE,
            &rCutSet.mfORMR);
    }

    struct ExtendSet
    {
        double          mfExtLeft;
        double          mfExtRight;

        ExtendSet() : mfExtLeft(0.0), mfExtRight(0.0) {}
    };

    void getExtends(
        std::vector<ExtendSet>& rExtendSet,                         // target Left/Right values to fill
        const basegfx::B2DPoint& rOrigin,                           // own vector start
        const StyleVectorCombination& rCombination,                 // own vector and offsets for lines
        const basegfx::B2DVector& rPerpendX,                        // normalized perpendicular to own vector
        const std::vector< StyleVectorCombination >& rStyleVector)  // other vectors emerging in this point
    {
        if(!rCombination.empty() && !rStyleVector.empty() && rCombination.size() == rExtendSet.size())
        {
            const size_t nOffsetA(rCombination.size());

            if(1 == nOffsetA)
            {
                Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
                rCombination.getColorAndOffsetAndHalfWidth(0, aMyColor, fMyOffset, fMyHalfWidth);

                if(0xff != aMyColor.GetTransparency())
                {
                    const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
                    const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
                    std::vector< CutSet > aCutSets;

                    for(const auto& rStyleCandidate : rStyleVector)
                    {
                        const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
                        const size_t nOffsetB(rStyleCandidate.size());

                        for(size_t other(0); other < nOffsetB; other++)
                        {
                            Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
                            rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);

                            if(0xff != aOtherColor.GetTransparency())
                            {
                                const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
                                const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));

                                CutSet aNewCutSet;
                                getCutSet(aNewCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
                                aCutSets.push_back(aNewCutSet);
                            }
                        }
                    }

                    if(!aCutSets.empty())
                    {
                        CutSet aCutSet(aCutSets[0]);
                        const size_t nNumCutSets(aCutSets.size());

                        if(1 != nNumCutSets)
                        {
                            double fCutSet(aCutSet.getSum());

                            for(size_t a(1); a < nNumCutSets; a++)
                            {
                                const CutSet& rCandidate(aCutSets[a]);
                                const double fCandidate(rCandidate.getSum());

                                if(basegfx::fTools::equalZero(fCandidate - fCutSet))
                                {
                                    // both have equal center point, use medium cut
                                    const double fNewOLML(std::max(std::min(rCandidate.mfOLML, rCandidate.mfORML), std::min(aCutSet.mfOLML, aCutSet.mfORML)));
                                    const double fNewORML(std::min(std::max(rCandidate.mfOLML, rCandidate.mfORML), std::max(aCutSet.mfOLML, aCutSet.mfORML)));
                                    const double fNewOLMR(std::max(std::min(rCandidate.mfOLMR, rCandidate.mfORMR), std::min(aCutSet.mfOLMR, aCutSet.mfORMR)));
                                    const double fNewORMR(std::min(std::max(rCandidate.mfOLMR, rCandidate.mfORMR), std::max(aCutSet.mfOLMR, aCutSet.mfORMR)));
                                    aCutSet.mfOLML = fNewOLML;
                                    aCutSet.mfORML = fNewORML;
                                    aCutSet.mfOLMR = fNewOLMR;
                                    aCutSet.mfORMR = fNewORMR;
                                    fCutSet = aCutSet.getSum();
                                }
                                else if(fCandidate < fCutSet)
                                {
                                    // get minimum
                                    fCutSet = fCandidate;
                                    aCutSet = rCandidate;
                                }
                            }
                        }

                        ExtendSet& rExt(rExtendSet[0]);

                        rExt.mfExtLeft = std::min(aCutSet.mfOLML, aCutSet.mfORML);
                        rExt.mfExtRight = std::min(aCutSet.mfOLMR, aCutSet.mfORMR);
                    }
                }
            }
            else
            {
                size_t nVisEdgeUp(0);
                size_t nVisEdgeDn(0);

                for(size_t my(0); my < nOffsetA; my++)
                {
                    Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
                    rCombination.getColorAndOffsetAndHalfWidth(my, aMyColor, fMyOffset, fMyHalfWidth);

                    if(0xff != aMyColor.GetTransparency())
                    {
                        const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
                        const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
                        const bool bUpper(my <= (nOffsetA >> 1));
                        const StyleVectorCombination& rStyleCandidate(bUpper ? rStyleVector.front() : rStyleVector.back());
                        const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
                        const size_t nOffsetB(rStyleCandidate.size());
                        std::vector< CutSet > aCutSets;

                        for(size_t other(0); other < nOffsetB; other++)
                        {
                            Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
                            rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);

                            if(0xff != aOtherColor.GetTransparency())
                            {
                                const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
                                const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
                                CutSet aCutSet;
                                getCutSet(aCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
                                aCutSets.push_back(aCutSet);
                            }
                        }

                        if(!aCutSets.empty())
                        {
                            // sort: min to start, max to end
                            std::sort(aCutSets.begin(), aCutSets.end());
                            const bool bOtherUpper(rStyleCandidate.getAngle() > F_PI);

                            // check if we need min or max
                            //  bUpper      bOtherUpper        MinMax
                            //    t             t               max
                            //    t             f               min
                            //    f             f               max
                            //    f             t               min
                            const bool bMax(bUpper == bOtherUpper);
                            size_t nBaseIndex(0);
                            const size_t nNumCutSets(aCutSets.size());

                            if(bMax)
                            {
                                // access at end
                                nBaseIndex = nNumCutSets - 1 - (bUpper ? nVisEdgeUp : nVisEdgeDn);
                            }
                            else
                            {
                                // access at start
                                nBaseIndex = bUpper ? nVisEdgeUp : nVisEdgeDn;
                            }

                            const size_t nSecuredIndex(std::min(nNumCutSets - 1, std::max(nBaseIndex, static_cast< size_t >(0))));
                            const CutSet& rCutSet(aCutSets[nSecuredIndex]);
                            ExtendSet& rExt(rExtendSet[my]);

                            rExt.mfExtLeft = std::min(rCutSet.mfOLML, rCutSet.mfORML);
                            rExt.mfExtRight = std::min(rCutSet.mfOLMR, rCutSet.mfORMR);
                        }

                        if(bUpper)
                        {
                            nVisEdgeUp++;
                        }
                        else
                        {
                            nVisEdgeDn++;
                        }
                    }
                }
            }
        }
    }

    /**
     *  Helper method to create the correct drawinglayer::primitive2d::BorderLinePrimitive2D
     *  for the given data, especially the correct drawinglayer::primitive2d::BorderLine entries
     *  including the correctly solved/created LineStartEnd extends
     *
     *  rTarget : Here the evtl. created BorderLinePrimitive2D will be appended
     *  rOrigin : StartPoint of the Borderline
     *  rX      : Vector of the Borderline
     *  rBorder : svx::frame::Style of the of the Borderline
     *  rStartStyleVectorTable : All other Borderlines which have to be taken into account because
     *      they have the same StartPoint as the current Borderline. These will be used to calculate
     *      the correct LineStartEnd extends tor the BorderLinePrimitive2D. The definition should be
     *      built up using svx::frame::StyleVectorTable and StyleVectorTable::add and includes:
     *          rStyle      : the svx::frame::Style of one other BorderLine
     *          rMyVector   : the Vector of the *new* to-be-defined BorderLine, identical to rX
     *          rOtherVector: the Vector of one other BorderLine (may be, but does not need to be normalized),
     *                        always *pointing away* from the common StartPoint rOrigin
     *          bMirrored   : define if rStyle of one other BorderLine shall be mirrored (e.g. bottom-right edges)
     *      With multiple BorderLines the definitions have to be CounterClockWise. This will be
     *      ensured by StyleVectorTable sorting the entries, but knowing this may allow more efficient
     *      data creation.
     *  rEndStyleVectorTable: All other BorderLines that have the same EndPoint. There are differences to
     *      the Start definitions:
     *          - do not forget to consequently use -rX for rMyVector
     *          - definitions have to be ClockWise for the EndBorderLines, will be ensured by sorting
     *
     *  If you take all this into account, you will gett correctly extended BorderLinePrimitive2D
     *  representations for the new to be defined BorderLine. That extensions will overlap nicely
     *  with the corresponding BorderLines and take all multiple line definitions in the ::Style into
     *  account.
     *  The internal solver is *not limited* to ::Style(s) with three parts (Left/Gap/Right), this is
     *  just due to svx::frame::Style's definitions. A new solver based on this one can be created
     *  anytime using more mulötiple borders based on the more flexible
     *  std::vector< drawinglayer::primitive2d::BorderLine > if needed.
     */
    void CreateBorderPrimitives(
        drawinglayer::primitive2d::Primitive2DContainer& rTarget,   /// target for created primitives
        const basegfx::B2DPoint& rOrigin,                           /// start point of borderline
        const basegfx::B2DVector& rX,                               /// X-Axis of borderline with length
        const svx::frame::Style& rBorder,                           /// Style of borderline
        const StyleVectorTable& rStartStyleVectorTable,             /// Styles and vectors (pointing away) at borderline start, ccw
        const StyleVectorTable& rEndStyleVectorTable,               /// Styles and vectors (pointing away) at borderline end, cw
        const Color* pForceColor,                                   /// If specified, overrides frame border color.
        double fMinimalDiscreteUnit)                                /// minimal discrete unit to use for svx::frame::Style width values
    {
        // get offset color pairs for  style, one per visible line
        const StyleVectorCombination aCombination(
            rBorder,
            rX,
            0.0,
            false,
            pForceColor,
            fMinimalDiscreteUnit);

        if(aCombination.empty())
            return;

        const basegfx::B2DVector aPerpendX(basegfx::getNormalizedPerpendicular(rX));
        const bool bHasStartStyles(!rStartStyleVectorTable.empty());
        const bool bHasEndStyles(!rEndStyleVectorTable.empty());
        const size_t nOffsets(aCombination.size());
        std::vector<ExtendSet> aExtendSetStart(nOffsets);
        std::vector<ExtendSet> aExtendSetEnd(nOffsets);

        if(bHasStartStyles)
        {
            // create extends for line starts, use given point/vector and offsets
            getExtends(aExtendSetStart, rOrigin, aCombination, aPerpendX, rStartStyleVectorTable.getEntries());
        }

        if(bHasEndStyles)
        {
            // Create extends for line ends, create inverse point/vector and inverse offsets.
            const StyleVectorCombination aMirroredCombination(
                rBorder,
                -rX,
                0.0,
                true,
                pForceColor,
                fMinimalDiscreteUnit);

            getExtends(aExtendSetEnd, rOrigin + rX, aMirroredCombination, -aPerpendX, rEndStyleVectorTable.getEntries());

            // also need to inverse the result to apply to the correct lines
            std::reverse(aExtendSetEnd.begin(), aExtendSetEnd.end());
        }

        std::vector< drawinglayer::primitive2d::BorderLine > aBorderlines;
        const double fNegLength(-rX.getLength());

        for(size_t a(0); a < nOffsets; a++)
        {
            Color aMyColor;
            double fMyOffset(0.0);
            double fMyHalfWidth(0.0);
            aCombination.getColorAndOffsetAndHalfWidth(a, aMyColor, fMyOffset, fMyHalfWidth);
            const ExtendSet& rExtStart(aExtendSetStart[a]);
            const ExtendSet& rExtEnd(aExtendSetEnd[a]);

            if(0xff == aMyColor.GetTransparency())
            {
                aBorderlines.push_back(
                    drawinglayer::primitive2d::BorderLine(
                        fMyHalfWidth * 2.0));
            }
            else
            {
                aBorderlines.push_back(
                    drawinglayer::primitive2d::BorderLine(
                        drawinglayer::attribute::LineAttribute(
                            aMyColor.getBColor(),
                            fMyHalfWidth * 2.0),
                        fNegLength * rExtStart.mfExtLeft,
                        fNegLength * rExtStart.mfExtRight,
                        fNegLength * rExtEnd.mfExtRight,
                        fNegLength * rExtEnd.mfExtLeft));
            }
        }

        static const double fPatScFact(10.0); // 10.0 multiply, see old code
        const std::vector<double> aDashing(svtools::GetLineDashing(rBorder.Type(), rBorder.PatternScale() * fPatScFact));
        const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(aDashing);
        const basegfx::B2DPoint aStart(rOrigin + (aPerpendX * aCombination.getRefModeOffset()));

        rTarget.append(
            drawinglayer::primitive2d::Primitive2DReference(
                new drawinglayer::primitive2d::BorderLinePrimitive2D(
                    aStart,
                    aStart + rX,
                    aBorderlines,
                    aStrokeAttribute)));
    }

    double getMinimalNonZeroValue(double fCurrent, double fNew)
    {
        if(0.0 != fNew)
        {
            if(0.0 != fCurrent)
            {
                fCurrent = std::min(fNew, fCurrent);
            }
            else
            {
                fCurrent = fNew;
            }
        }

        return fCurrent;
    }

    double getMinimalNonZeroBorderWidthFromStyle(double fCurrent, const svx::frame::Style& rStyle)
    {
        if(rStyle.IsUsed())
        {
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Prim());
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Dist());
            fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Secn());
        }

        return fCurrent;
    }
}

namespace drawinglayer
{
    namespace primitive2d
    {
        SdrFrameBorderData::SdrConnectStyleData::SdrConnectStyleData(
            const svx::frame::Style& rStyle,
            const basegfx::B2DVector& rNormalizedPerpendicular,
            bool bStyleMirrored)
        :   maStyle(rStyle),
            maNormalizedPerpendicular(rNormalizedPerpendicular),
            mbStyleMirrored(bStyleMirrored)
        {
        }

        SdrFrameBorderData::SdrFrameBorderData(
            const basegfx::B2DPoint& rOrigin,
            const basegfx::B2DVector& rX,
            const svx::frame::Style& rStyle,
            const Color* pForceColor)
        :   maOrigin(rOrigin),
            maX(rX),
            maStyle(rStyle),
            maColor(nullptr != pForceColor ? *pForceColor : Color()),
            mbForceColor(nullptr != pForceColor),
            maStart(),
            maEnd()
        {
        }

        void SdrFrameBorderData::addSdrConnectStyleData(
            bool bStart,
            const svx::frame::Style& rStyle,
            const basegfx::B2DVector& rNormalizedPerpendicular,
            bool bStyleMirrored)
        {
            if(rStyle.IsUsed())
            {
                if(bStart)
                {
                    maStart.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
                }
                else
                {
                    maEnd.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
                }
            }
        }

        void SdrFrameBorderData::create2DDecomposition(
            Primitive2DContainer& rContainer,
            double fMinimalDiscreteUnit) const
        {
            StyleVectorTable aStartVector;
            StyleVectorTable aEndVector;
            const basegfx::B2DVector aAxis(-maX);

            for(const auto& rStart : maStart)
            {
                aStartVector.add(
                    rStart.getStyle(),
                    maX,
                    rStart.getNormalizedPerpendicular(),
                    rStart.getStyleMirrored(),
                    fMinimalDiscreteUnit);
            }

            for(const auto& rEnd : maEnd)
            {
                aEndVector.add(
                    rEnd.getStyle(),
                    aAxis,
                    rEnd.getNormalizedPerpendicular(),
                    rEnd.getStyleMirrored(),
                    fMinimalDiscreteUnit);
            }

            aStartVector.sort();
            aEndVector.sort();

            CreateBorderPrimitives(
                rContainer,
                maOrigin,
                maX,
                maStyle,
                aStartVector,
                aEndVector,
                mbForceColor ? &maColor : nullptr,
                fMinimalDiscreteUnit);
        }

        double SdrFrameBorderData::getMinimalNonZeroBorderWidth() const
        {
            double fRetval(getMinimalNonZeroBorderWidthFromStyle(0.0, maStyle));

            for(const auto& rStart : maStart)
            {
                fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rStart.getStyle());
            }

            for(const auto& rEnd : maEnd)
            {
                fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rEnd.getStyle());
            }

            return fRetval;
        }
    } // end of namespace primitive2d
} // end of namespace drawinglayer

namespace drawinglayer
{
    namespace primitive2d
    {
        void SdrFrameBorderPrimitive2D::create2DDecomposition(
            Primitive2DContainer& rContainer,
            const geometry::ViewInformation2D& /*aViewInformation*/) const
        {
            if(!getFrameBorders())
            {
                return;
            }

            Primitive2DContainer aRetval;

            // Check and use the minimal non-zero BorderWidth for decompose
            // if that is set and wanted
            const double fMinimalDiscreteUnit(doForceToSingleDiscreteUnit()
                ? mfMinimalNonZeroBorderWidthUsedForDecompose
                : 0.0);

            if(doMergeResult())
            {
                // decompose all buffered SdrFrameBorderData entries and try to merge them
                // to reduce existing number of BorderLinePrimitive2D(s)
                for(const auto& rCandidate : *getFrameBorders().get())
                {
                    // get decomposition on one SdrFrameBorderData entry
                    Primitive2DContainer aPartial;
                    rCandidate.create2DDecomposition(
                        aPartial,
                        fMinimalDiscreteUnit);

                    for(const auto& aCandidatePartial : aPartial)
                    {
                        if(aRetval.empty())
                        {
                            // no local data yet, just add as 1st entry, done
                            aRetval.append(aCandidatePartial);
                        }
                        else
                        {
                            bool bDidMerge(false);

                            for(auto& aCandidateRetval : aRetval)
                            {
                                // try to merge by appending new data to existing data
                                const drawinglayer::primitive2d::Primitive2DReference aMergeRetvalPartial(
                                    drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
                                        aCandidateRetval,
                                        aCandidatePartial));

                                if(aMergeRetvalPartial.is())
                                {
                                    // could append, replace existing data with merged data, done
                                    aCandidateRetval = aMergeRetvalPartial;
                                    bDidMerge = true;
                                    break;
                                }

                                // try to merge by appending existing data to new data
                                const drawinglayer::primitive2d::Primitive2DReference aMergePartialRetval(
                                    drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
                                        aCandidatePartial,
                                        aCandidateRetval));

                                if(aMergePartialRetval.is())
                                {
                                    // could append, replace existing data with merged data, done
                                    aCandidateRetval = aMergePartialRetval;
                                    bDidMerge = true;
                                    break;
                                }
                            }

                            if(!bDidMerge)
                            {
                                // no merge after checking all existing data, append as new segment
                                aRetval.append(aCandidatePartial);
                            }
                        }
                    }
                }
            }
            else
            {
                // just decompose all buffered SdrFrameBorderData entries, do not try to merge
                for(const auto& rCandidate : *getFrameBorders().get())
                {
                    rCandidate.create2DDecomposition(
                        aRetval,
                        fMinimalDiscreteUnit);
                }
            }

            rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end());
        }

        SdrFrameBorderPrimitive2D::SdrFrameBorderPrimitive2D(
            std::shared_ptr<SdrFrameBorderDataVector>& rFrameBorders,
            bool bMergeResult,
            bool bForceToSingleDiscreteUnit)
        :   BufferedDecompositionPrimitive2D(),
            maFrameBorders(std::move(rFrameBorders)),
            mfMinimalNonZeroBorderWidth(0.0),
            mfMinimalNonZeroBorderWidthUsedForDecompose(0.0),
            mbMergeResult(bMergeResult),
            mbForceToSingleDiscreteUnit(bForceToSingleDiscreteUnit)
        {
            if(getFrameBorders() && doForceToSingleDiscreteUnit())
            {
                // detect used minimal non-zero partial border width
                for(const auto& rCandidate : *getFrameBorders().get())
                {
                    mfMinimalNonZeroBorderWidth = getMinimalNonZeroValue(
                        mfMinimalNonZeroBorderWidth,
                        rCandidate.getMinimalNonZeroBorderWidth());
                }
            }
        }

        bool SdrFrameBorderPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
        {
            if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
            {
                const SdrFrameBorderPrimitive2D& rCompare = static_cast<const SdrFrameBorderPrimitive2D&>(rPrimitive);

                return getFrameBorders() == rCompare.getFrameBorders()
                    && doMergeResult() == rCompare.doMergeResult()
                    && doForceToSingleDiscreteUnit() == rCompare.doForceToSingleDiscreteUnit();
            }

            return false;
        }

        void SdrFrameBorderPrimitive2D::get2DDecomposition(
            Primitive2DDecompositionVisitor& rVisitor,
            const geometry::ViewInformation2D& rViewInformation) const
        {
            if(doForceToSingleDiscreteUnit())
            {
                // Get the current DiscreteUnit, look at X and Y and use the maximum
                const basegfx::B2DVector aDiscreteVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0));
                double fDiscreteUnit(std::min(fabs(aDiscreteVector.getX()), fabs(aDiscreteVector.getY())));

                if(fDiscreteUnit <= mfMinimalNonZeroBorderWidth)
                {
                    // no need to use it, reset
                    fDiscreteUnit = 0.0;
                }

                if(fDiscreteUnit != mfMinimalNonZeroBorderWidthUsedForDecompose)
                {
                    // conditions of last local decomposition have changed, delete
                    // possible content
                    if(!getBuffered2DDecomposition().empty())
                    {
                        const_cast< SdrFrameBorderPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer());
                    }

                    // remember new conditions
                    const_cast< SdrFrameBorderPrimitive2D* >(this)->mfMinimalNonZeroBorderWidthUsedForDecompose = fDiscreteUnit;
                }
            }

            // call parent. This will call back ::create2DDecomposition above
            // where mfMinimalNonZeroBorderWidthUsedForDecompose will be used
            // when doForceToSingleDiscreteUnit() is true
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
        }

        // provide unique ID
        ImplPrimitive2DIDBlock(SdrFrameBorderPrimitive2D, PRIMITIVE2D_ID_SDRFRAMEBORDERTPRIMITIVE2D)

    } // end of namespace primitive2d
} // end of namespace drawinglayer

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