/**************************************************************
 * 
 * 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_svx.hxx"
#include <svx/sdr/contact/viewcontactofe3dscene.hxx>
#include <svx/polysc3d.hxx>
#include <svx/sdr/contact/displayinfo.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/color/bcolor.hxx>
#include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
#include <svx/sdr/primitive2d/sdrattributecreator.hxx>
#include <svx/sdr/contact/viewobjectcontactofe3dscene.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/range/b3drange.hxx>
#include <drawinglayer/primitive3d/baseprimitive3d.hxx>
#include <svx/sdr/contact/viewcontactofe3d.hxx>
#include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
#include <drawinglayer/primitive3d/transformprimitive3d.hxx>
#include <drawinglayer/primitive2d/sdrdecompositiontools2d.hxx>

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

using namespace com::sun::star;

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

namespace
{
	// pActiveVC is only true if ghosted is still activated and maybe needs to be switched off in this path
	void createSubPrimitive3DVector(
		const sdr::contact::ViewContact& rCandidate,
		drawinglayer::primitive3d::Primitive3DSequence& o_rAllTarget,
		drawinglayer::primitive3d::Primitive3DSequence* o_pVisibleTarget,
		const SetOfByte* pVisibleLayerSet,
		const bool bTestSelectedVisibility)
	{
		const sdr::contact::ViewContactOfE3dScene* pViewContactOfE3dScene = dynamic_cast< const sdr::contact::ViewContactOfE3dScene* >(&rCandidate);

		if(pViewContactOfE3dScene)
		{
			const sal_uInt32 nChildrenCount(rCandidate.GetObjectCount());

			if(nChildrenCount)
			{
				// provide new collection sequences
				drawinglayer::primitive3d::Primitive3DSequence aNewAllTarget;
				drawinglayer::primitive3d::Primitive3DSequence aNewVisibleTarget;
				
				// add children recursively
				for(sal_uInt32 a(0L); a < nChildrenCount; a++)
				{
					createSubPrimitive3DVector(
						rCandidate.GetViewContact(a), 
						aNewAllTarget, 
						o_pVisibleTarget ? &aNewVisibleTarget : 0,
						pVisibleLayerSet, 
						bTestSelectedVisibility);
				}

				// create transform primitive for the created content combining content and transformtion
				const drawinglayer::primitive3d::Primitive3DReference xReference(new drawinglayer::primitive3d::TransformPrimitive3D(
					pViewContactOfE3dScene->GetE3dScene().GetTransform(),
					aNewAllTarget));

				// add created content to all target
				drawinglayer::primitive3d::appendPrimitive3DReferenceToPrimitive3DSequence(o_rAllTarget, xReference);
				
				// add created content to visibiel target if exists
				if(o_pVisibleTarget)
				{
					drawinglayer::primitive3d::appendPrimitive3DReferenceToPrimitive3DSequence(*o_pVisibleTarget, xReference);
				}
			}
		}
		else
		{
			// access view independent representation of rCandidate
			const sdr::contact::ViewContactOfE3d* pViewContactOfE3d = dynamic_cast< const sdr::contact::ViewContactOfE3d* >(&rCandidate);

			if(pViewContactOfE3d)
			{
				drawinglayer::primitive3d::Primitive3DSequence xPrimitive3DSeq(pViewContactOfE3d->getViewIndependentPrimitive3DSequence());
				
				if(xPrimitive3DSeq.hasElements())
				{
					// add to all target vector
					drawinglayer::primitive3d::appendPrimitive3DSequenceToPrimitive3DSequence(o_rAllTarget, xPrimitive3DSeq);

					if(o_pVisibleTarget)
					{
						// test visibility. Primitive is visible when both tests are true (AND)
						bool bVisible(true);

						if(pVisibleLayerSet)
						{
							// test layer visibility
							const E3dObject& rE3dObject = pViewContactOfE3d->GetE3dObject();
							const SdrLayerID aLayerID(rE3dObject.GetLayer());
							
							bVisible = pVisibleLayerSet->IsSet(aLayerID);
						}
						
						if(bVisible && bTestSelectedVisibility)
						{
							// test selected visibility (see 3D View's DrawMarkedObj implementation)
							const E3dObject& rE3dObject = pViewContactOfE3d->GetE3dObject();

							bVisible = rE3dObject.GetSelected();
						}

						if(bVisible && o_pVisibleTarget)
						{
							// add to visible target vector
							drawinglayer::primitive3d::appendPrimitive3DSequenceToPrimitive3DSequence(*o_pVisibleTarget, xPrimitive3DSeq);
						}
					}
				}
			}
		}
	}
} // end of anonymous namespace

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

namespace sdr
{
	namespace contact
	{
		// Create a Object-Specific ViewObjectContact, set ViewContact and
		// ObjectContact. Always needs to return something.
		ViewObjectContact& ViewContactOfE3dScene::CreateObjectSpecificViewObjectContact(ObjectContact& rObjectContact)
		{
			ViewObjectContact* pRetval = new ViewObjectContactOfE3dScene(rObjectContact, *this);
			DBG_ASSERT(pRetval, "ViewContactOfE3dScene::CreateObjectSpecificViewObjectContact() failed (!)");

			return *pRetval;
		}

		ViewContactOfE3dScene::ViewContactOfE3dScene(E3dScene& rScene)
		:	ViewContactOfSdrObj(rScene),
			maViewInformation3D(),
			maObjectTransformation(),
			maSdrSceneAttribute(),
			maSdrLightingAttribute()
		{
		}

		void ViewContactOfE3dScene::createViewInformation3D(const basegfx::B3DRange& rContentRange)
		{
			basegfx::B3DHomMatrix aTransformation;
			basegfx::B3DHomMatrix aOrientation;
			basegfx::B3DHomMatrix aProjection;
			basegfx::B3DHomMatrix aDeviceToView;

			// create transformation (scene as group's transformation)
			// For historical reasons, the outmost scene's transformation is handles as part of the
			// view transformation. This means that the BoundRect of the contained 3D Objects is
			// without that transformation and makes it necessary to NOT add the first scene to the
			// Primitive3DSequence of contained objects.
			{
				aTransformation = GetE3dScene().GetTransform();
			}

			// create orientation (world to camera coordinate system)
			{
				// calculate orientation from VRP, VPN and VUV
				const B3dCamera& rSceneCamera = GetE3dScene().GetCameraSet();
				const basegfx::B3DPoint aVRP(rSceneCamera.GetVRP());
				const basegfx::B3DVector aVPN(rSceneCamera.GetVRP());
				const basegfx::B3DVector aVUV(rSceneCamera.GetVUV());

                aOrientation.orientation(aVRP, aVPN, aVUV);
			}

			// create projection (camera coordinate system to relative 2d where X,Y and Z are [0.0 .. 1.0])
			{
				const basegfx::B3DHomMatrix aWorldToCamera(aOrientation * aTransformation);
				basegfx::B3DRange aCameraRange(rContentRange);
				aCameraRange.transform(aWorldToCamera);

				// remember Z-Values, but change orientation
				const double fMinZ(-aCameraRange.getMaxZ());
				const double fMaxZ(-aCameraRange.getMinZ());

				// construct temorary matrix from world to device. Use unit values here to measure expansion
				basegfx::B3DHomMatrix aWorldToDevice(aWorldToCamera);
				const drawinglayer::attribute::SdrSceneAttribute& rSdrSceneAttribute = getSdrSceneAttribute();

				if(::com::sun::star::drawing::ProjectionMode_PERSPECTIVE == rSdrSceneAttribute.getProjectionMode())
				{
					aWorldToDevice.frustum(-1.0, 1.0, -1.0, 1.0, fMinZ, fMaxZ);
				}
				else
				{
					aWorldToDevice.ortho(-1.0, 1.0, -1.0, 1.0, fMinZ, fMaxZ);
				}

				// create B3DRange in device. This will create the real used ranges
				// in camera space. Do not use the Z-Values, though.
				basegfx::B3DRange aDeviceRange(rContentRange);
				aDeviceRange.transform(aWorldToDevice);

				// set projection
				if(::com::sun::star::drawing::ProjectionMode_PERSPECTIVE == rSdrSceneAttribute.getProjectionMode())
				{
					aProjection.frustum(
						aDeviceRange.getMinX(), aDeviceRange.getMaxX(), 
						aDeviceRange.getMinY(), aDeviceRange.getMaxY(), 
						fMinZ, fMaxZ);
				}
				else
				{
					aProjection.ortho(
						aDeviceRange.getMinX(), aDeviceRange.getMaxX(), 
						aDeviceRange.getMinY(), aDeviceRange.getMaxY(), 
						fMinZ, fMaxZ);
				}
			}

			// create device to view transform
			{
				// create standard deviceToView projection for geometry
				// input is [-1.0 .. 1.0] in X,Y and Z. bring to [0.0 .. 1.0]. Also
				// necessary to flip Y due to screen orientation
				// Z is not needed, but will also be brought to [0.0 .. 1.0]
				aDeviceToView.scale(0.5, -0.5, 0.5);
				aDeviceToView.translate(0.5, 0.5, 0.5);
			}

			const uno::Sequence< beans::PropertyValue > aEmptyProperties;
			maViewInformation3D = drawinglayer::geometry::ViewInformation3D(
				aTransformation, aOrientation, aProjection, 
				aDeviceToView, 0.0, aEmptyProperties);
		}

		void ViewContactOfE3dScene::createObjectTransformation()
		{
			// create 2d Object Transformation from relative point in 2d scene to world
			const Rectangle& rRectangle = GetE3dScene().GetSnapRect();

			maObjectTransformation.set(0, 0, rRectangle.getWidth());
			maObjectTransformation.set(1, 1, rRectangle.getHeight());
			maObjectTransformation.set(0, 2, rRectangle.Left());
			maObjectTransformation.set(1, 2, rRectangle.Top());
		}

		void ViewContactOfE3dScene::createSdrSceneAttribute()
		{
			const SfxItemSet& rItemSet = GetE3dScene().GetMergedItemSet();
			maSdrSceneAttribute = drawinglayer::primitive2d::createNewSdrSceneAttribute(rItemSet);
		}

		void ViewContactOfE3dScene::createSdrLightingAttribute()
		{
			const SfxItemSet& rItemSet = GetE3dScene().GetMergedItemSet();
			maSdrLightingAttribute = drawinglayer::primitive2d::createNewSdrLightingAttribute(rItemSet);
		}

		drawinglayer::primitive2d::Primitive2DSequence ViewContactOfE3dScene::createScenePrimitive2DSequence(
            const SetOfByte* pLayerVisibility) const
		{
			drawinglayer::primitive2d::Primitive2DSequence xRetval;
			const sal_uInt32 nChildrenCount(GetObjectCount());

			if(nChildrenCount)
			{
				// create 3d scene primitive with visible content tested against rLayerVisibility
				drawinglayer::primitive3d::Primitive3DSequence aAllSequence;
				drawinglayer::primitive3d::Primitive3DSequence aVisibleSequence;
				const bool bTestLayerVisibility(0 != pLayerVisibility);
				const bool bTestSelectedVisibility(GetE3dScene().GetDrawOnlySelected());
				const bool bTestVisibility(bTestLayerVisibility || bTestSelectedVisibility);

				// add children recursively. Do NOT start with (*this), this would create
				// a 3D transformPrimitive for the start scene. While this is theoretically not
				// a bad thing, for historical reasons the transformation of the outmost scene
				// is seen as part of the ViewTransformation (see text in createViewInformation3D)
				for(sal_uInt32 a(0L); a < nChildrenCount; a++)
				{
					createSubPrimitive3DVector(
						GetViewContact(a), 
						aAllSequence, 
						bTestLayerVisibility ? &aVisibleSequence : 0, 
						bTestLayerVisibility ? pLayerVisibility : 0,
						bTestSelectedVisibility);
				}

				const sal_uInt32 nAllSize(aAllSequence.hasElements() ? aAllSequence.getLength() : 0);
				const sal_uInt32 nVisibleSize(aVisibleSequence.hasElements() ? aVisibleSequence.getLength() : 0);

				if((bTestVisibility && nVisibleSize) || nAllSize)
				{
					// for getting the 3D range using getB3DRangeFromPrimitive3DSequence a ViewInformation3D
					// needs to be given for evtl. decompositions. At the same time createViewInformation3D
					// currently is based on creating the target-ViewInformation3D using a given range. To
					// get the true range, use a neutral ViewInformation3D here. This leaves all matrices
					// on identity and the time on 0.0.
					const uno::Sequence< beans::PropertyValue > aEmptyProperties;
					const drawinglayer::geometry::ViewInformation3D aNeutralViewInformation3D(aEmptyProperties);
					const basegfx::B3DRange aContentRange(
						drawinglayer::primitive3d::getB3DRangeFromPrimitive3DSequence(aAllSequence, aNeutralViewInformation3D));

					// create 2d primitive 3dscene with generated sub-list from collector
					const drawinglayer::primitive2d::Primitive2DReference xReference(
						new drawinglayer::primitive2d::ScenePrimitive2D(
							bTestVisibility ? aVisibleSequence : aAllSequence,
							getSdrSceneAttribute(), 
							getSdrLightingAttribute(), 
							getObjectTransformation(), 
							getViewInformation3D(aContentRange)));

					xRetval = drawinglayer::primitive2d::Primitive2DSequence(&xReference, 1);
				}
			}

			// always append an invisible outline for the cases where no visible content exists
			drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(xRetval, 
				drawinglayer::primitive2d::createHiddenGeometryPrimitives2D(
					false, getObjectTransformation()));

			return xRetval;
		}

		drawinglayer::primitive2d::Primitive2DSequence ViewContactOfE3dScene::createViewIndependentPrimitive2DSequence() const
		{
			drawinglayer::primitive2d::Primitive2DSequence xRetval;

			if(GetObjectCount())
			{
				// create a default ScenePrimitive2D (without visibility test of members)
				xRetval = createScenePrimitive2DSequence(0);
			}

            return xRetval;
		}

		void ViewContactOfE3dScene::ActionChanged()
		{
			// call parent
			ViewContactOfSdrObj::ActionChanged();

			// mark locally cached values as invalid
    		maViewInformation3D = drawinglayer::geometry::ViewInformation3D();
			maObjectTransformation.identity();
			maSdrSceneAttribute = drawinglayer::attribute::SdrSceneAttribute();
			maSdrLightingAttribute = drawinglayer::attribute::SdrLightingAttribute();
		}

		const drawinglayer::geometry::ViewInformation3D& ViewContactOfE3dScene::getViewInformation3D() const
		{
			if(maViewInformation3D.isDefault())
			{
				// this version will create the content range on demand locally and thus is less
				// performant than the other one. Since the information is buffered the planned
				// behaviour is that the version with the given range is used initially.
                basegfx::B3DRange aContentRange(getAllContentRange3D());

				if(aContentRange.isEmpty())
				{
					// empty scene, no 3d action should be necessary. Prepare some
                    // fallback size
					OSL_ENSURE(false, "No need to get ViewInformation3D from an empty scene (!)");
					aContentRange.expand(basegfx::B3DPoint(-100.0, -100.0, -100.0));
					aContentRange.expand(basegfx::B3DPoint( 100.0,  100.0,  100.0));
				}

				const_cast < ViewContactOfE3dScene* >(this)->createViewInformation3D(aContentRange);
			}

			return maViewInformation3D;
		}

		const drawinglayer::geometry::ViewInformation3D& ViewContactOfE3dScene::getViewInformation3D(const basegfx::B3DRange& rContentRange) const
		{
            if(maViewInformation3D.isDefault())
			{
				const_cast < ViewContactOfE3dScene* >(this)->createViewInformation3D(rContentRange);
			}

			return maViewInformation3D;
		}

		const basegfx::B2DHomMatrix& ViewContactOfE3dScene::getObjectTransformation() const
		{
            if(maObjectTransformation.isIdentity())
			{
				const_cast < ViewContactOfE3dScene* >(this)->createObjectTransformation();
			}

			return maObjectTransformation;
		}

		const drawinglayer::attribute::SdrSceneAttribute& ViewContactOfE3dScene::getSdrSceneAttribute() const
		{
            if(maSdrSceneAttribute.isDefault())
			{
				const_cast < ViewContactOfE3dScene* >(this)->createSdrSceneAttribute();
			}

			return maSdrSceneAttribute;
		}

		const drawinglayer::attribute::SdrLightingAttribute& ViewContactOfE3dScene::getSdrLightingAttribute() const
		{
            if(maSdrLightingAttribute.isDefault())
			{
				const_cast < ViewContactOfE3dScene* >(this)->createSdrLightingAttribute();
			}

			return maSdrLightingAttribute;
		}

        drawinglayer::primitive3d::Primitive3DSequence ViewContactOfE3dScene::getAllPrimitive3DSequence() const
        {
            drawinglayer::primitive3d::Primitive3DSequence aAllPrimitive3DSequence;
			const sal_uInt32 nChildrenCount(GetObjectCount());

		    // add children recursively. Do NOT start with (*this), this would create
		    // a 3D transformPrimitive for the start scene. While this is theoretically not
		    // a bad thing, for historical reasons the transformation of the outmost scene
		    // is seen as part of the ViewTransformation (see text in createViewInformation3D)
            for(sal_uInt32 a(0L); a < nChildrenCount; a++)
		    {
			    createSubPrimitive3DVector(GetViewContact(a), aAllPrimitive3DSequence, 0, 0, false);
		    }

            return aAllPrimitive3DSequence;
        }

        basegfx::B3DRange ViewContactOfE3dScene::getAllContentRange3D() const
        {
            const drawinglayer::primitive3d::Primitive3DSequence xAllSequence(getAllPrimitive3DSequence());
            basegfx::B3DRange aAllContentRange3D;

            if(xAllSequence.hasElements())
			{
				// for getting the 3D range using getB3DRangeFromPrimitive3DSequence a ViewInformation3D
				// needs to be given for evtl. decompositions. Use a neutral ViewInformation3D here. This 
                // leaves all matrices on identity and the time on 0.0.
				const uno::Sequence< beans::PropertyValue > aEmptyProperties;
				const drawinglayer::geometry::ViewInformation3D aNeutralViewInformation3D(aEmptyProperties);

				aAllContentRange3D = drawinglayer::primitive3d::getB3DRangeFromPrimitive3DSequence(xAllSequence, aNeutralViewInformation3D);
            }

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

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