/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: viewobjectcontact.cxx,v $
 * $Revision: 1.17.18.1 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svx.hxx"
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/sdr/contact/displayinfo.hxx>
#include <vcl/region.hxx>
#include <svx/sdr/animation/objectanimator.hxx>
#include <svx/sdr/animation/animationstate.hxx>
#include <svx/sdr/contact/viewobjectcontactredirector.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/color/bcolor.hxx>
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
#include <basegfx/tools/canvastools.hxx>
#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
#include <drawinglayer/processor2d/baseprocessor2d.hxx>
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
#include <svx/sdr/contact/viewobjectcontactredirector.hxx>

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

using namespace com::sun::star;

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

namespace 
{
	// animated extractor

	// Necessary to filter a sequence of animated primitives from
	// a sequence of primitives to find out if animated or not. The decision for
	// what to decompose is hard-coded and only done for knowingly animated primitives
	// to not decompose too deeply and unnecessarily. This implies that the list
	// which is view-specific needs to be expanded by hand when new animated objects
	// are added. This may eventually be changed to a dynamically configurable approach
	// if necessary.
	class AnimatedExtractingProcessor2D : public drawinglayer::processor2d::BaseProcessor2D
	{
	protected:
        // the found animated primitives
        drawinglayer::primitive2d::Primitive2DSequence	maPrimitive2DSequence;

        // bitfield
		// text animation allowed?
		unsigned										mbTextAnimationAllowed : 1;

		// graphic animation allowed?
		unsigned										mbGraphicAnimationAllowed : 1;

		// as tooling, the process() implementation takes over API handling and calls this
		// virtual render method when the primitive implementation is BasePrimitive2D-based.
		virtual void processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate);

	public:
		AnimatedExtractingProcessor2D(
			const drawinglayer::geometry::ViewInformation2D& rViewInformation,
			bool bTextAnimationAllowed, 
			bool bGraphicAnimationAllowed);
        virtual ~AnimatedExtractingProcessor2D();

		// data access
        const drawinglayer::primitive2d::Primitive2DSequence& getPrimitive2DSequence() const { return maPrimitive2DSequence; }
		bool isTextAnimationAllowed() const { return mbTextAnimationAllowed; }
		bool isGraphicAnimationAllowed() const { return mbGraphicAnimationAllowed; }
	};

    AnimatedExtractingProcessor2D::AnimatedExtractingProcessor2D(
		const drawinglayer::geometry::ViewInformation2D& rViewInformation,
		bool bTextAnimationAllowed, 
		bool bGraphicAnimationAllowed)
	:	drawinglayer::processor2d::BaseProcessor2D(rViewInformation),
        maPrimitive2DSequence(),
		mbTextAnimationAllowed(bTextAnimationAllowed),
		mbGraphicAnimationAllowed(bGraphicAnimationAllowed)
	{
    }

    AnimatedExtractingProcessor2D::~AnimatedExtractingProcessor2D()
    {
    }

    void AnimatedExtractingProcessor2D::processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate)
	{
		// known implementation, access directly
		switch(rCandidate.getPrimitiveID())
		{
			// add and accept animated primitives directly, no need to decompose
			case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D :
			case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D :
			case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D :
			{
				const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& rSwitchPrimitive = static_cast< const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& >(rCandidate);

				if((rSwitchPrimitive.isTextAnimation() && isTextAnimationAllowed())
					|| (rSwitchPrimitive.isGraphicAnimation() && isGraphicAnimationAllowed()))
				{
					const drawinglayer::primitive2d::Primitive2DReference xReference(const_cast< drawinglayer::primitive2d::BasePrimitive2D* >(&rCandidate));
                    drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(maPrimitive2DSequence, xReference);
				}
				break;
			}
			
			// decompose animated gifs where SdrGrafPrimitive2D produces a GraphicPrimitive2D
            // which then produces the animation infos (all when used/needed)
			case PRIMITIVE2D_ID_SDRGRAFPRIMITIVE2D :
            case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D :

			// decompose SdrObjects with evtl. animated text
			case PRIMITIVE2D_ID_SDRCAPTIONPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRCONNECTORPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRCUSTOMSHAPEPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRELLIPSEPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRELLIPSESEGMENTPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRPATHPRIMITIVE2D :
			case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D :

			// decompose evtl. animated text contained in MaskPrimitive2D
			// or group rimitives
			case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
			case PRIMITIVE2D_ID_GROUPPRIMITIVE2D :
			{
				process(rCandidate.get2DDecomposition(getViewInformation2D()));
				break;
			}
			
			default :
			{
				// nothing to do for the rest
				break;
			}
		}
	}
} // end of anonymous namespace

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

namespace sdr
{
	namespace contact
	{
		ViewObjectContact::ViewObjectContact(ObjectContact& rObjectContact, ViewContact& rViewContact)
		:	mrObjectContact(rObjectContact),
			mrViewContact(rViewContact),
			maObjectRange(),
			mxPrimitive2DSequence(),
			mpPrimitiveAnimation(0),
			mbLazyInvalidate(false)
		{
			// make the ViewContact remember me
			mrViewContact.AddViewObjectContact(*this);

			// make the ObjectContact remember me
			mrObjectContact.AddViewObjectContact(*this);
		}

		ViewObjectContact::~ViewObjectContact()
		{
			// invalidate in view
            if(!maObjectRange.isEmpty())
            {
    			GetObjectContact().InvalidatePartOfView(maObjectRange);
            }

			// delete PrimitiveAnimation
			if(mpPrimitiveAnimation)
			{
				delete mpPrimitiveAnimation;
				mpPrimitiveAnimation = 0;
			}

			// take care of remebered ObjectContact. Remove from
            // OC first. The VC removal (below) CAN trigger a StopGettingViewed()
            // which (depending of it's implementation) may destroy other OCs. This
            // can trigger the deletion of the helper OC of a page visualising object
            // which IS the OC of this object. Eventually StopGettingViewed() needs
            // to get asynchron later
			GetObjectContact().RemoveViewObjectContact(*this);
			
			// take care of remebered ViewContact
			GetViewContact().RemoveViewObjectContact(*this);
		}

		const basegfx::B2DRange& ViewObjectContact::getObjectRange() const 
		{ 
			if(maObjectRange.isEmpty())
			{
				// if range is not computed (new or LazyInvalidate objects), force it
				const DisplayInfo aDisplayInfo;
				const drawinglayer::primitive2d::Primitive2DSequence xSequence(getPrimitive2DSequence(aDisplayInfo));

                if(xSequence.hasElements())
                {
					const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
                    const_cast< ViewObjectContact* >(this)->maObjectRange = 
                        drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(xSequence, rViewInformation2D);
				}
			}

			return maObjectRange; 
		}

		void ViewObjectContact::ActionChanged()
		{
			if(!mbLazyInvalidate)
			{
				// set local flag
				mbLazyInvalidate = true;

				// force ObjectRange
				getObjectRange();

				if(!maObjectRange.isEmpty())
				{
					// invalidate current valid range
					GetObjectContact().InvalidatePartOfView(maObjectRange);

					// reset ObjectRange, it needs to be recalculated
					maObjectRange.reset();
				}

				// register at OC for lazy invalidate
				GetObjectContact().setLazyInvalidate(*this);
			}
		}

		void ViewObjectContact::triggerLazyInvalidate()
		{
			if(mbLazyInvalidate)
			{
				// reset flag
				mbLazyInvalidate = false;
				
				// force ObjectRange
				getObjectRange();

				if(!maObjectRange.isEmpty())
				{
					// invalidate current valid range
					GetObjectContact().InvalidatePartOfView(maObjectRange);
				}
			}
		}

		// Take some action when new objects are inserted
		void ViewObjectContact::ActionChildInserted(ViewContact& rChild)
		{
			// force creation of the new VOC and trigger it's refresh, so it
			// will take part in LazyInvalidate immediately
			rChild.GetViewObjectContact(GetObjectContact()).ActionChanged();

			// forward action to ObjectContact
			// const ViewObjectContact& rChildVOC = rChild.GetViewObjectContact(GetObjectContact());
			// GetObjectContact().InvalidatePartOfView(rChildVOC.getObjectRange());
		}

		void ViewObjectContact::checkForPrimitive2DAnimations()
		{
			// remove old one
			if(mpPrimitiveAnimation)
			{
				delete mpPrimitiveAnimation;
				mpPrimitiveAnimation = 0;
			}

			// check for animated primitives
			if(mxPrimitive2DSequence.hasElements())
			{
                const bool bTextAnimationAllowed(GetObjectContact().IsTextAnimationAllowed());
                const bool bGraphicAnimationAllowed(GetObjectContact().IsGraphicAnimationAllowed());

                if(bTextAnimationAllowed || bGraphicAnimationAllowed)
                {
				    AnimatedExtractingProcessor2D aAnimatedExtractor(GetObjectContact().getViewInformation2D(), 
					    bTextAnimationAllowed, bGraphicAnimationAllowed);
				    aAnimatedExtractor.process(mxPrimitive2DSequence);

				    if(aAnimatedExtractor.getPrimitive2DSequence().hasElements())
				    {
					    // dervied primitiveList is animated, setup new PrimitiveAnimation
					    mpPrimitiveAnimation =  new sdr::animation::PrimitiveAnimation(*this, aAnimatedExtractor.getPrimitive2DSequence());
				    }
                }
			}
		}

		drawinglayer::primitive2d::Primitive2DSequence ViewObjectContact::createPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const
		{
			// get the view-independent Primitive from the viewContact
			drawinglayer::primitive2d::Primitive2DSequence xRetval(GetViewContact().getViewIndependentPrimitive2DSequence());

			if(xRetval.hasElements())
			{
				// handle GluePoint
				if(!GetObjectContact().isOutputToPrinter() && GetObjectContact().AreGluePointsVisible())
				{
					const drawinglayer::primitive2d::Primitive2DSequence xGlue(GetViewContact().createGluePointPrimitive2DSequence());

					if(xGlue.hasElements())
					{
                        drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(xRetval, xGlue);
					}
				}

				// handle ghosted
				if(isPrimitiveGhosted(rDisplayInfo))
				{
					const basegfx::BColor aRGBWhite(1.0, 1.0, 1.0);
					const basegfx::BColorModifier aBColorModifier(aRGBWhite, 0.5, basegfx::BCOLORMODIFYMODE_INTERPOLATE);
                    const drawinglayer::primitive2d::Primitive2DReference xReference(new drawinglayer::primitive2d::ModifiedColorPrimitive2D(xRetval, aBColorModifier));
                    xRetval = drawinglayer::primitive2d::Primitive2DSequence(&xReference, 1);
				}
			}

			return xRetval;
		}

		drawinglayer::primitive2d::Primitive2DSequence ViewObjectContact::getPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const
		{
			drawinglayer::primitive2d::Primitive2DSequence xNewPrimitiveSequence;

			// take care of redirectors and create new list
			ViewObjectContactRedirector* pRedirector = GetObjectContact().GetViewObjectContactRedirector();

			if(pRedirector)
			{
				xNewPrimitiveSequence = pRedirector->createRedirectedPrimitive2DSequence(*this, rDisplayInfo);
			}
			else
			{
				xNewPrimitiveSequence = createPrimitive2DSequence(rDisplayInfo);
			}

			// local up-to-date checks. New list different from local one?
			if(!drawinglayer::primitive2d::arePrimitive2DSequencesEqual(mxPrimitive2DSequence, xNewPrimitiveSequence))
			{
				// has changed, copy content
				const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = xNewPrimitiveSequence;
				
                // check for animated stuff
				const_cast< ViewObjectContact* >(this)->checkForPrimitive2DAnimations();

				// always update object range when PrimitiveSequence changes
				const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
                const_cast< ViewObjectContact* >(this)->maObjectRange = 
                    drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(mxPrimitive2DSequence, rViewInformation2D);
			}

			// return current Primitive2DSequence
			return mxPrimitive2DSequence;
		}

		bool ViewObjectContact::isPrimitiveVisible(const DisplayInfo& /*rDisplayInfo*/) const
		{
			// default: always visible
			return true;
		}

		bool ViewObjectContact::isPrimitiveGhosted(const DisplayInfo& rDisplayInfo) const
		{
			// default: standard check
			return (GetObjectContact().DoVisualizeEnteredGroup() && !GetObjectContact().isOutputToPrinter() && rDisplayInfo.IsGhostedDrawModeActive());
		}

		drawinglayer::primitive2d::Primitive2DSequence ViewObjectContact::getPrimitive2DSequenceHierarchy(DisplayInfo& rDisplayInfo) const
		{
			drawinglayer::primitive2d::Primitive2DSequence xRetval;

			// check model-view visibility
			if(isPrimitiveVisible(rDisplayInfo))
			{
				xRetval = getPrimitive2DSequence(rDisplayInfo);

				if(xRetval.hasElements())
				{
					// get ranges
					const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
					const basegfx::B2DRange aObjectRange(drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(xRetval, rViewInformation2D));
					const basegfx::B2DRange aViewRange(rViewInformation2D.getViewport());

					// check geometrical visibility
					if(!aViewRange.isEmpty() && !aViewRange.overlaps(aObjectRange))
					{
						// not visible, release
						xRetval.realloc(0);
					}
				}
			}

			return xRetval;
		}

		drawinglayer::primitive2d::Primitive2DSequence ViewObjectContact::getPrimitive2DSequenceSubHierarchy(DisplayInfo& rDisplayInfo) const
		{
			const sal_uInt32 nSubHierarchyCount(GetViewContact().GetObjectCount());
			drawinglayer::primitive2d::Primitive2DSequence xSeqRetval;

			for(sal_uInt32 a(0); a < nSubHierarchyCount; a++)
			{
				const ViewObjectContact& rCandidate(GetViewContact().GetViewContact(a).GetViewObjectContact(GetObjectContact()));

                drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(xSeqRetval, rCandidate.getPrimitive2DSequenceHierarchy(rDisplayInfo));
			}

			return xSeqRetval;
		}
	} // end of namespace contact
} // end of namespace sdr

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