/**************************************************************
 * 
 * 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_drawinglayer.hxx"

#include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
#include <basegfx/tools/canvastools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <basegfx/polygon/b2dlinegeometry.hxx>
#include <com/sun/star/drawing/LineCap.hpp>

//////////////////////////////////////////////////////////////////////////////

using namespace com::sun::star;

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace primitive2d
	{
		PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
			const basegfx::BColor& rBColor)
		:	BasePrimitive2D(),
			maPolygon(rPolygon),
			maBColor(rBColor)
		{
		}

		bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
		{
			if(BasePrimitive2D::operator==(rPrimitive))
			{
				const PolygonHairlinePrimitive2D& rCompare = (PolygonHairlinePrimitive2D&)rPrimitive;

				return (getB2DPolygon() == rCompare.getB2DPolygon() 
					&& getBColor() == rCompare.getBColor());
			}

			return false;
		}

		basegfx::B2DRange PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
		{
            // this is a hairline, thus the line width is view-dependent. Get range of polygon
            // as base size
	        basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());

            if(!aRetval.isEmpty())
            {
                // Calculate view-dependent hairline width
                const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
                const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
                
                if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
                {
		            aRetval.grow(fDiscreteHalfLineWidth);
                }
            }

            // return range
			return aRetval;
		}

		// provide unique ID
		ImplPrimitrive2DIDBlock(PolygonHairlinePrimitive2D, PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D)

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

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace primitive2d
	{
		Primitive2DSequence PolygonMarkerPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
		{
			// calculate logic DashLength
			const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(getDiscreteDashLength(), 0.0));
			const double fLogicDashLength(aDashVector.getX());

			if(fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB()))
			{
				// apply dashing; get line and gap snippets
				::std::vector< double > aDash;
				basegfx::B2DPolyPolygon aDashedPolyPolyA;
				basegfx::B2DPolyPolygon aDashedPolyPolyB;

				aDash.push_back(fLogicDashLength);
				aDash.push_back(fLogicDashLength);
				basegfx::tools::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA, &aDashedPolyPolyB, 2.0 * fLogicDashLength);

				// prepare return value
				Primitive2DSequence aRetval(2);

				aRetval[0] = Primitive2DReference(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyA, getRGBColorA()));
				aRetval[1] = Primitive2DReference(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyB, getRGBColorB()));

				return aRetval;
			}
			else
			{
				const Primitive2DReference xRef(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA()));
				return Primitive2DSequence(&xRef, 1L);
			}
		}

		PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
			const basegfx::BColor& rRGBColorA,
			const basegfx::BColor& rRGBColorB,
			double fDiscreteDashLength)
		:	BufferedDecompositionPrimitive2D(),
			maPolygon(rPolygon),
			maRGBColorA(rRGBColorA),
			maRGBColorB(rRGBColorB),
			mfDiscreteDashLength(fDiscreteDashLength),
			maLastInverseObjectToViewTransformation()
		{
		}

		bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
		{
			if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
			{
				const PolygonMarkerPrimitive2D& rCompare = (PolygonMarkerPrimitive2D&)rPrimitive;

				return (getB2DPolygon() == rCompare.getB2DPolygon()
					&& getRGBColorA() == rCompare.getRGBColorA()
					&& getRGBColorB() == rCompare.getRGBColorB()
					&& getDiscreteDashLength() == rCompare.getDiscreteDashLength());
			}

			return false;
		}

		basegfx::B2DRange PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
		{
            // this is a hairline, thus the line width is view-dependent. Get range of polygon
            // as base size
	        basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());

            if(!aRetval.isEmpty())
            {
                // Calculate view-dependent hairline width
                const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
                const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
                
                if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
                {
		            aRetval.grow(fDiscreteHalfLineWidth);
                }
            }

            // return range
			return aRetval;
		}

		Primitive2DSequence PolygonMarkerPrimitive2D::get2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
		{ 
			::osl::MutexGuard aGuard( m_aMutex );
			bool bNeedNewDecomposition(false);

			if(getBuffered2DDecomposition().hasElements())
			{
				if(rViewInformation.getInverseObjectToViewTransformation() != maLastInverseObjectToViewTransformation)
				{
					bNeedNewDecomposition = true;
				}
			}

			if(bNeedNewDecomposition)
			{
				// conditions of last local decomposition have changed, delete
				const_cast< PolygonMarkerPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DSequence());
			}

			if(!getBuffered2DDecomposition().hasElements())
			{
				// remember last used InverseObjectToViewTransformation
				PolygonMarkerPrimitive2D* pThat = const_cast< PolygonMarkerPrimitive2D* >(this);
				pThat->maLastInverseObjectToViewTransformation = rViewInformation.getInverseObjectToViewTransformation();
			}

			// use parent implementation
			return BufferedDecompositionPrimitive2D::get2DDecomposition(rViewInformation);
		}

		// provide unique ID
		ImplPrimitrive2DIDBlock(PolygonMarkerPrimitive2D, PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D)

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

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace primitive2d
	{
		Primitive2DSequence PolygonStrokePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
		{
			if(getB2DPolygon().count())
			{
                // #i102241# try to simplify before usage
                const basegfx::B2DPolygon aB2DPolygon(basegfx::tools::simplifyCurveSegments(getB2DPolygon()));
				basegfx::B2DPolyPolygon aHairLinePolyPolygon;

				if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
				{
                    // no line dashing, just copy
					aHairLinePolyPolygon.append(aB2DPolygon);
				}
				else
				{
				    // apply LineStyle
				    basegfx::tools::applyLineDashing(
                        aB2DPolygon, getStrokeAttribute().getDotDashArray(), 
                        &aHairLinePolyPolygon, 0, getStrokeAttribute().getFullDotDashLen());
				}

                const sal_uInt32 nCount(aHairLinePolyPolygon.count());

                if(!getLineAttribute().isDefault() && getLineAttribute().getWidth())
				{
                    // create fat line data
					const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0);
					const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin());
                    const com::sun::star::drawing::LineCap aLineCap(getLineAttribute().getLineCap());
					basegfx::B2DPolyPolygon aAreaPolyPolygon;

					for(sal_uInt32 a(0L); a < nCount; a++)
					{
                        // New version of createAreaGeometry; now creates bezier polygons
                        aAreaPolyPolygon.append(basegfx::tools::createAreaGeometry(
							aHairLinePolyPolygon.getB2DPolygon(a), 
                            fHalfLineWidth, 
                            aLineJoin,
                            aLineCap));
					}

					// prepare return value
					Primitive2DSequence aRetval(aAreaPolyPolygon.count());

					// create primitive
					for(sal_uInt32 b(0L); b < aAreaPolyPolygon.count(); b++)
					{
						// put into single polyPolygon primitives to make clear that this is NOT meant
						// to be painted as a single PolyPolygon (XORed as fill rule). Alternatively, a 
						// melting process may be used here one day.
						const basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b));
    					static bool bTestByUsingRandomColor(false);
                        const basegfx::BColor aColor(bTestByUsingRandomColor
                            ? basegfx::BColor(rand() / 32767.0, rand() / 32767.0, rand() / 32767.0) 
                            : getLineAttribute().getColor());
						const Primitive2DReference xRef(new PolyPolygonColorPrimitive2D(aNewPolyPolygon, aColor));
						aRetval[b] = xRef;
					}

					return aRetval;
				}
				else
				{
					// prepare return value
					const Primitive2DReference xRef(
                        new PolyPolygonHairlinePrimitive2D(
                            aHairLinePolyPolygon, 
                            getLineAttribute().getColor()));

                    return Primitive2DSequence(&xRef, 1);
				}
			}
			else
			{
				return Primitive2DSequence();
			}
		}

		PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
            const attribute::LineAttribute& rLineAttribute,
			const attribute::StrokeAttribute& rStrokeAttribute)
		:	BufferedDecompositionPrimitive2D(),
			maPolygon(rPolygon),
            maLineAttribute(rLineAttribute),
			maStrokeAttribute(rStrokeAttribute)
		{
		}

		PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
            const attribute::LineAttribute& rLineAttribute)
		:	BufferedDecompositionPrimitive2D(),
			maPolygon(rPolygon),
            maLineAttribute(rLineAttribute),
			maStrokeAttribute()
		{
		}

		bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
		{
			if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
			{
				const PolygonStrokePrimitive2D& rCompare = (PolygonStrokePrimitive2D&)rPrimitive;

				return (getB2DPolygon() == rCompare.getB2DPolygon() 
					&& getLineAttribute() == rCompare.getLineAttribute()
					&& getStrokeAttribute() == rCompare.getStrokeAttribute());
			}

			return false;
		}

		basegfx::B2DRange PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
		{
            basegfx::B2DRange aRetval;

            if(getLineAttribute().getWidth())
            {
                bool bUseDecomposition(false);

                if(basegfx::B2DLINEJOIN_MITER == getLineAttribute().getLineJoin())
                {
                    // if line is mitered, use parent call since mitered line
                    // geometry may use more space than the geometry grown by half line width
                    bUseDecomposition = true;
                }

                if(!bUseDecomposition && com::sun::star::drawing::LineCap_SQUARE == getLineAttribute().getLineCap())
                {
                    // when drawing::LineCap_SQUARE is used the below method to grow the polygon
                    // range by half line width will not work, so use decomposition. Interestingly,
                    // the grow method below works perfectly for LineCap_ROUND since the grow is in
                    // all directions and the rounded cap needs the same grow in all directions independent
                    // from it's orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE
                    bUseDecomposition = true;
                }

                if(bUseDecomposition)
                {
                    // get correct range by using the decomposition fallback, reasons see above cases
                    aRetval = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
                }
                else
                {
                    // for all other B2DLINEJOIN_* get the range from the base geometry
                    // and expand by half the line width
			        aRetval = getB2DPolygon().getB2DRange();
			        aRetval.grow(getLineAttribute().getWidth() * 0.5);
                }
            }
            else
            {
                // this is a hairline, thus the line width is view-dependent. Get range of polygon
                // as base size
    	        aRetval = getB2DPolygon().getB2DRange();

                if(!aRetval.isEmpty())
                {
                    // Calculate view-dependent hairline width
                    const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
                    const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
                    
                    if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
                    {
			            aRetval.grow(fDiscreteHalfLineWidth);
                    }
                }
            }

            return aRetval;
		}

		// provide unique ID
		ImplPrimitrive2DIDBlock(PolygonStrokePrimitive2D, PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D)

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

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace primitive2d
	{
		Primitive2DSequence PolygonWavePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
		{
			Primitive2DSequence aRetval;

			if(getB2DPolygon().count())
			{
				const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth()));
				const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight()));

				if(bHasWidth && bHasHeight)
				{
					// create waveline curve
					const basegfx::B2DPolygon aWaveline(basegfx::tools::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight()));
					const Primitive2DReference xRef(new PolygonStrokePrimitive2D(aWaveline, getLineAttribute(), getStrokeAttribute()));
					aRetval = Primitive2DSequence(&xRef, 1);
				}
				else
				{
					// flat waveline, decompose to simple line primitive
					const Primitive2DReference xRef(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), getStrokeAttribute()));
					aRetval = Primitive2DSequence(&xRef, 1);
				}
			}

			return aRetval;
		}

		PolygonWavePrimitive2D::PolygonWavePrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
            const attribute::LineAttribute& rLineAttribute,
			const attribute::StrokeAttribute& rStrokeAttribute,
			double fWaveWidth,
			double fWaveHeight)
		:	PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute),
			mfWaveWidth(fWaveWidth),
			mfWaveHeight(fWaveHeight)
		{
			if(mfWaveWidth < 0.0)
			{
				mfWaveWidth = 0.0;
			}

			if(mfWaveHeight < 0.0)
			{
				mfWaveHeight = 0.0;
			}
		}

		PolygonWavePrimitive2D::PolygonWavePrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
            const attribute::LineAttribute& rLineAttribute,
			double fWaveWidth,
			double fWaveHeight)
		:	PolygonStrokePrimitive2D(rPolygon, rLineAttribute),
			mfWaveWidth(fWaveWidth),
			mfWaveHeight(fWaveHeight)
		{
			if(mfWaveWidth < 0.0)
			{
				mfWaveWidth = 0.0;
			}

			if(mfWaveHeight < 0.0)
			{
				mfWaveHeight = 0.0;
			}
		}

		bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
		{
			if(PolygonStrokePrimitive2D::operator==(rPrimitive))
			{
				const PolygonWavePrimitive2D& rCompare = (PolygonWavePrimitive2D&)rPrimitive;

				return (getWaveWidth() == rCompare.getWaveWidth()
					&& getWaveHeight() == rCompare.getWaveHeight());
			}

			return false;
		}

		basegfx::B2DRange PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
		{
			// get range of parent
    		basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation));

			// if WaveHeight, grow by it
			if(basegfx::fTools::more(getWaveHeight(), 0.0))
			{
				aRetval.grow(getWaveHeight());
			}

			// if line width, grow by it
			if(basegfx::fTools::more(getLineAttribute().getWidth(), 0.0))
			{
				aRetval.grow(getLineAttribute().getWidth() * 0.5);
			}

			return aRetval;
		}

		// provide unique ID
		ImplPrimitrive2DIDBlock(PolygonWavePrimitive2D, PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D)

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

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace primitive2d
	{
		Primitive2DSequence PolygonStrokeArrowPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
		{
			// copy local polygon, it may be changed
			basegfx::B2DPolygon aLocalPolygon(getB2DPolygon());
			basegfx::B2DPolyPolygon aArrowA;
			basegfx::B2DPolyPolygon aArrowB;

			if(!aLocalPolygon.isClosed())
			{
				// apply arrows
				const double fPolyLength(basegfx::tools::getLength(aLocalPolygon));
				double fStart(0.0);
				double fEnd(0.0);

				if(!getStart().isDefault() && getStart().isActive())
				{
					// create start arrow primitive and consume
					aArrowA = basegfx::tools::createAreaGeometryForLineStartEnd(
						aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(), 
						fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart);

					// create some overlapping
					fStart *= 0.8;
				}

				if(!getEnd().isDefault() && getEnd().isActive())
				{
					// create end arrow primitive and consume
					aArrowB = basegfx::tools::createAreaGeometryForLineStartEnd(
						aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(), 
						fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd);

					// create some overlapping
					fEnd *= 0.8;
				}

				if(0.0 != fStart || 0.0 != fEnd)
				{
					// build new poly, consume something from old poly
					aLocalPolygon = basegfx::tools::getSnippetAbsolute(aLocalPolygon, fStart, fPolyLength - fEnd, fPolyLength);
				}
			}

			// prepare return value
			Primitive2DSequence aRetval(1L + (aArrowA.count() ? 1L : 0L) + (aArrowB.count() ? 1L : 0L));
			sal_uInt32 nInd(0L);

			// add shaft
			const Primitive2DReference xRefShaft(new 
                PolygonStrokePrimitive2D(
                    aLocalPolygon, getLineAttribute(), getStrokeAttribute()));
			aRetval[nInd++] = xRefShaft;

			if(aArrowA.count())
			{
				const Primitive2DReference xRefA(
                    new PolyPolygonColorPrimitive2D(
                        aArrowA, getLineAttribute().getColor()));
				aRetval[nInd++] = xRefA;
			}

			if(aArrowB.count())
			{
				const Primitive2DReference xRefB(
                    new PolyPolygonColorPrimitive2D(
                        aArrowB, getLineAttribute().getColor()));
				aRetval[nInd++] = xRefB;
			}

			return aRetval;
		}

		PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
            const attribute::LineAttribute& rLineAttribute,
			const attribute::StrokeAttribute& rStrokeAttribute, 
			const attribute::LineStartEndAttribute& rStart, 
			const attribute::LineStartEndAttribute& rEnd)
		:	PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute),
			maStart(rStart),
			maEnd(rEnd)
		{
		}

		PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
			const basegfx::B2DPolygon& rPolygon, 
            const attribute::LineAttribute& rLineAttribute,
			const attribute::LineStartEndAttribute& rStart, 
			const attribute::LineStartEndAttribute& rEnd)
		:	PolygonStrokePrimitive2D(rPolygon, rLineAttribute),
			maStart(rStart),
			maEnd(rEnd)
		{
		}

		bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
		{
			if(PolygonStrokePrimitive2D::operator==(rPrimitive))
			{
				const PolygonStrokeArrowPrimitive2D& rCompare = (PolygonStrokeArrowPrimitive2D&)rPrimitive;

				return (getStart() == rCompare.getStart() 
					&& getEnd() == rCompare.getEnd());
			}

			return false;
		}

		basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
		{
			basegfx::B2DRange aRetval;

			if(getStart().isActive() || getEnd().isActive())
			{
				// use decomposition when line start/end is used
				return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
			}
			else
			{
				// get range from parent
				return PolygonStrokePrimitive2D::getB2DRange(rViewInformation);
			}
		}

		// provide unique ID
		ImplPrimitrive2DIDBlock(PolygonStrokeArrowPrimitive2D, PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D)

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

//////////////////////////////////////////////////////////////////////////////
// eof