/**************************************************************
 * 
 * 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/viewcontactofe3d.hxx>
#include <svx/sdr/contact/viewobjectcontactofe3d.hxx>
#include <svx/obj3d.hxx>
#include <drawinglayer/primitive2d/embedded3dprimitive2d.hxx>
#include <svx/sdr/contact/viewcontactofe3dscene.hxx>
#include <svx/scene3d.hxx>
#include <drawinglayer/primitive3d/transformprimitive3d.hxx>
#include <drawinglayer/attribute/sdrsceneattribute3d.hxx>
#include <drawinglayer/attribute/sdrlightingattribute3d.hxx>
#include <drawinglayer/attribute/sdrlightattribute3d.hxx>
#include <drawinglayer/attribute/sdrlineattribute.hxx>

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

namespace  
{
    const sdr::contact::ViewContactOfE3dScene* tryToFindVCOfE3DScene(
        const sdr::contact::ViewContact& rCandidate, 
        basegfx::B3DHomMatrix& o_rInBetweenObjectTransform)
    {
	    const sdr::contact::ViewContactOfE3dScene* pSceneParent = 
            dynamic_cast< const sdr::contact::ViewContactOfE3dScene* >(rCandidate.GetParentContact());

        if(pSceneParent)
        {
            // each 3d object (including in-between scenes) should have a scene as parent
		    const sdr::contact::ViewContactOfE3dScene* pSceneParentParent = 
                dynamic_cast< const sdr::contact::ViewContactOfE3dScene* >(pSceneParent->GetParentContact());

            if(pSceneParentParent)
            {
                // the parent scene of rCandidate is a in-between scene, call recursively and collect
                // the in-between scene's object transformation part in o_rInBetweenObjectTransform
                const basegfx::B3DHomMatrix& rSceneParentTransform = pSceneParent->GetE3dScene().GetTransform();
                o_rInBetweenObjectTransform = rSceneParentTransform * o_rInBetweenObjectTransform;
			    return tryToFindVCOfE3DScene(*pSceneParent, o_rInBetweenObjectTransform);
            }
            else
            {
                // the parent scene is the outmost scene
                return pSceneParent;
            }
        }
	
        // object hierarchy structure is incorrect; no result
	    return 0;
    }
} // end of anonymous namespace

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

namespace sdr
{
	namespace contact
	{
		drawinglayer::primitive2d::Primitive2DSequence ViewContactOfE3d::impCreateWithGivenPrimitive3DSequence(
            const drawinglayer::primitive3d::Primitive3DSequence& rxContent3D) const
        {
			drawinglayer::primitive2d::Primitive2DSequence xRetval;
			
            if(rxContent3D.hasElements())
			{
				// try to get the outmost ViewObjectContactOfE3dScene for this single 3d object,
                // the ones on the way there are grouping scenes. Collect the in-between scene's
                // transformations to build a correct object transformation for the embedded
                // object
                basegfx::B3DHomMatrix aInBetweenObjectTransform;
				const ViewContactOfE3dScene* pVCOfE3DScene = tryToFindVCOfE3DScene(*this, aInBetweenObjectTransform);

				if(pVCOfE3DScene)
				{
					basegfx::B3DVector aLightNormal;
                    const double fShadowSlant(pVCOfE3DScene->getSdrSceneAttribute().getShadowSlant());
                    const basegfx::B3DRange& rAllContentRange = pVCOfE3DScene->getAllContentRange3D();
            		drawinglayer::geometry::ViewInformation3D aViewInformation3D(pVCOfE3DScene->getViewInformation3D());

					if(pVCOfE3DScene->getSdrLightingAttribute().getLightVector().size())
					{
						// get light normal from first light and normalize
						aLightNormal = pVCOfE3DScene->getSdrLightingAttribute().getLightVector()[0].getDirection();
						aLightNormal.normalize();
					}

                    if(!aInBetweenObjectTransform.isIdentity())
                    {
                        // if aInBetweenObjectTransform is used, create combined ViewInformation3D which
                        // contains the correct object transformation for the embedded 3d object
                        aViewInformation3D = drawinglayer::geometry::ViewInformation3D(
                            aViewInformation3D.getObjectTransformation() * aInBetweenObjectTransform, 
                            aViewInformation3D.getOrientation(),
                            aViewInformation3D.getProjection(),
                            aViewInformation3D.getDeviceToView(),
                            aViewInformation3D.getViewTime(),
                            aViewInformation3D.getExtendedInformationSequence());
                    }

					// create embedded 2d primitive and add. LightNormal and ShadowSlant are needed for evtl.
					// 3D shadow extraction for correct B2DRange calculation (shadow is part of the object)
                    const drawinglayer::primitive2d::Primitive2DReference xReference(
						new drawinglayer::primitive2d::Embedded3DPrimitive2D(
							rxContent3D, 
							pVCOfE3DScene->getObjectTransformation(), 
							aViewInformation3D,
							aLightNormal,
							fShadowSlant,
							rAllContentRange));
                    
					xRetval = drawinglayer::primitive2d::Primitive2DSequence(&xReference, 1);
				}
			}

			return xRetval;
        }

		ViewContactOfE3d::ViewContactOfE3d(E3dObject& rSdrObject)
		:	ViewContactOfSdrObj(rSdrObject)
		{
		}

		ViewContactOfE3d::~ViewContactOfE3d()
		{
		}

		drawinglayer::primitive3d::Primitive3DSequence ViewContactOfE3d::getVIP3DSWithoutObjectTransform() const
		{
			// local up-to-date checks. Create new list and compare.
			drawinglayer::primitive3d::Primitive3DSequence xNew(createViewIndependentPrimitive3DSequence());

			if(!drawinglayer::primitive3d::arePrimitive3DSequencesEqual(mxViewIndependentPrimitive3DSequence, xNew))
			{
				// has changed, copy content
				const_cast< ViewContactOfE3d* >(this)->mxViewIndependentPrimitive3DSequence = xNew;
			}

			// return current Primitive2DSequence
			return mxViewIndependentPrimitive3DSequence;
		}

		drawinglayer::primitive3d::Primitive3DSequence ViewContactOfE3d::getViewIndependentPrimitive3DSequence() const
		{
			// get sequence without object transform
			drawinglayer::primitive3d::Primitive3DSequence xRetval(getVIP3DSWithoutObjectTransform());

			if(xRetval.hasElements())
			{
				// add object transform if it's used
				const basegfx::B3DHomMatrix& rObjectTransform(GetE3dObject().GetTransform());

				if(!rObjectTransform.isIdentity())
				{
					const drawinglayer::primitive3d::Primitive3DReference xReference(
						new drawinglayer::primitive3d::TransformPrimitive3D(
							rObjectTransform, 
							xRetval));

					xRetval = drawinglayer::primitive3d::Primitive3DSequence(&xReference, 1);
				}
			}

			// return current Primitive2DSequence
			return xRetval;
		}
		
		drawinglayer::primitive2d::Primitive2DSequence ViewContactOfE3d::createViewIndependentPrimitive2DSequence() const
		{
            // also need to create a 2D embedding when the view-independent part is requested,
			// see view-dependent part in ViewObjectContactOfE3d::createPrimitive2DSequence
			// get 3d primitive vector, isPrimitiveVisible() is done in 3d creator
    		return impCreateWithGivenPrimitive3DSequence(getViewIndependentPrimitive3DSequence());
		}

		ViewObjectContact& ViewContactOfE3d::CreateObjectSpecificViewObjectContact(ObjectContact& rObjectContact)
		{
			ViewObjectContact* pRetval = new ViewObjectContactOfE3d(rObjectContact, *this);
			DBG_ASSERT(pRetval, "ViewContactOfE3d::CreateObjectSpecificViewObjectContact() failed (!)");

			return *pRetval;
		}

		drawinglayer::attribute::SdrLineAttribute* ViewContactOfE3d::impCreateFallbackLineAttribute(const basegfx::BColor& rBColor) const
		{
			static bool bFallbackToCreateAsLineForTest(false);

			if(bFallbackToCreateAsLineForTest)
			{
				return new drawinglayer::attribute::SdrLineAttribute(rBColor);
			}
			else
			{
				return 0;
			}
		}
	} // end of namespace contact
} // end of namespace sdr

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