/**************************************************************
 * 
 * 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/geometry/viewinformation3d.hxx>
#include <basegfx/range/b3drange.hxx>
#include <basegfx/matrix/b3dhommatrix.hxx>
#include <com/sun/star/geometry/AffineMatrix3D.hpp>
#include <com/sun/star/geometry/RealRectangle3D.hpp>
#include <basegfx/tools/canvastools.hxx>

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

using namespace com::sun::star;

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

namespace drawinglayer
{
	namespace geometry
	{
        /** Implementation class for ViewInformation3D
        */
		class ImpViewInformation3D
		{
		private:
			// ViewInformation3D implementation can change refcount, so we have only
			// two memory regions for pairs of ViewInformation3D/ImpViewInformation3D
			friend class ::drawinglayer::geometry::ViewInformation3D;

			// the refcounter. 0 means exclusively used
			sal_uInt32									mnRefCount;

			// the 3D transformations
			// Object to World. This may change and being adapted when entering 3D transformation
			// groups
			basegfx::B3DHomMatrix						maObjectTransformation;	

			// World to Camera. This includes VRP, VPN and VUV camera coordinate system
			basegfx::B3DHomMatrix						maOrientation;		

			// Camera to Device with X,Y and Z [-1.0 .. 1.0]. This is the
			// 3D to 2D projection which may be parallell or perspective. When it is perspective,
			// the last line of the homogen matrix will NOT be unused
			basegfx::B3DHomMatrix						maProjection;		
			
			// Device to View with X,Y and Z [0.0 .. 1.0]. This converts from -1 to 1 coordinates
			// in camera coordinate system to 0 to 1 in unit 2D coordinates. This way it stays
			// view-independent. To get discrete coordinates, the 2D transformation of a scene
			// as 2D object needs to be involved
			basegfx::B3DHomMatrix						maDeviceToView;		

			// Object to View is the linear combination of all four transformations. It's
			// buffered to avoid too much matrix multiplying and created on demand
			basegfx::B3DHomMatrix						maObjectToView;

			// the point in time
			double										mfViewTime;

			// the complete PropertyValue representation (if already created)
			uno::Sequence< beans::PropertyValue >		mxViewInformation;

			// the extra PropertyValues; does not contain the transformations
			uno::Sequence< beans::PropertyValue >		mxExtendedInformation;

			// the local UNO API strings
			const ::rtl::OUString& getNamePropertyObjectTransformation()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("ObjectTransformation"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyOrientation()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("Orientation"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyProjection()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("Projection"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyProjection_30()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("Projection30"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyProjection_31()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("Projection31"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyProjection_32()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("Projection32"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyProjection_33()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("Projection33"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyDeviceToView()
			{
				static ::rtl::OUString s_sNameProperty(RTL_CONSTASCII_USTRINGPARAM("DeviceToView"));
				return s_sNameProperty;
			}
			
			const ::rtl::OUString& getNamePropertyTime()
			{
				static ::rtl::OUString s_sNamePropertyTime(RTL_CONSTASCII_USTRINGPARAM("Time"));
				return s_sNamePropertyTime;
			}

            // a central PropertyValue parsing method to allow transportatin of
            // all ViewParameters using UNO API
			void impInterpretPropertyValues(const uno::Sequence< beans::PropertyValue >& rViewParameters)
			{
				if(rViewParameters.hasElements())
				{
					const sal_Int32 nCount(rViewParameters.getLength());
					sal_Int32 nExtendedInsert(0);

					// prepare extended information for filtering. Maximum size is nCount
					mxExtendedInformation.realloc(nCount);

					for(sal_Int32 a(0); a < nCount; a++)
					{
						const beans::PropertyValue& rProp = rViewParameters[a];
						
						if(rProp.Name == getNamePropertyObjectTransformation())
						{
							com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
							rProp.Value >>= aAffineMatrix3D;
							maObjectTransformation = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D);
						}
						else if(rProp.Name == getNamePropertyOrientation())
						{
							com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
							rProp.Value >>= aAffineMatrix3D;
							maOrientation = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D);
						}
						else if(rProp.Name == getNamePropertyProjection())
						{
							// projection may be defined using a frustum in which case the last line of
							// the 4x4 matrix is not (0,0,0,1). Since AffineMatrix3D does not support that,
							// these four values need to be treated extra
							const double f_30(maProjection.get(3, 0));
							const double f_31(maProjection.get(3, 1));
							const double f_32(maProjection.get(3, 2));
							const double f_33(maProjection.get(3, 3));
							
							com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
							rProp.Value >>= aAffineMatrix3D;
							maProjection = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D);

							maProjection.set(3, 0, f_30);
							maProjection.set(3, 1, f_31);
							maProjection.set(3, 2, f_32);
							maProjection.set(3, 3, f_33);
						}
						else if(rProp.Name == getNamePropertyProjection_30())
						{
							double f_30(0.0);
							rProp.Value >>= f_30;
							maProjection.set(3, 0, f_30);
						}
						else if(rProp.Name == getNamePropertyProjection_31())
						{
							double f_31(0.0);
							rProp.Value >>= f_31;
							maProjection.set(3, 1, f_31);
						}
						else if(rProp.Name == getNamePropertyProjection_32())
						{
							double f_32(0.0);
							rProp.Value >>= f_32;
							maProjection.set(3, 2, f_32);
						}
						else if(rProp.Name == getNamePropertyProjection_33())
						{
							double f_33(1.0);
							rProp.Value >>= f_33;
							maProjection.set(3, 3, f_33);
						}
						else if(rProp.Name == getNamePropertyDeviceToView())
						{
							com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
							rProp.Value >>= aAffineMatrix3D;
							maDeviceToView = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D);
						}
						else if(rProp.Name == getNamePropertyTime())
						{
							rProp.Value >>= mfViewTime;
						}
						else
						{
							// extra information; add to filtered information
							mxExtendedInformation[nExtendedInsert++] = rProp;
						}
					}

					// extra information size is now known; realloc to final size
					mxExtendedInformation.realloc(nExtendedInsert);
				}
			}

            // central method to create a Sequence of PropertyValues containing he complete
            // data set
			void impFillViewInformationFromContent()
			{
				uno::Sequence< beans::PropertyValue > xRetval;
				const bool bObjectTransformationUsed(!maObjectTransformation.isIdentity());
				const bool bOrientationUsed(!maOrientation.isIdentity());
				const bool bProjectionUsed(!maProjection.isIdentity());
				const bool bDeviceToViewUsed(!maDeviceToView.isIdentity());
				const bool bTimeUsed(0.0 < mfViewTime);
				const bool bExtraInformation(mxExtendedInformation.hasElements());
				
				// projection may be defined using a frustum in which case the last line of
				// the 4x4 matrix is not (0,0,0,1). Since AffineMatrix3D does not support that,
				// these four values need to be treated extra
				const bool bProjectionUsed_30(bProjectionUsed && !basegfx::fTools::equalZero(maProjection.get(3, 0)));
				const bool bProjectionUsed_31(bProjectionUsed && !basegfx::fTools::equalZero(maProjection.get(3, 1)));
				const bool bProjectionUsed_32(bProjectionUsed && !basegfx::fTools::equalZero(maProjection.get(3, 2)));
				const bool bProjectionUsed_33(bProjectionUsed && !basegfx::fTools::equal(maProjection.get(3, 3), 1.0));
				
				sal_uInt32 nIndex(0);
				const sal_uInt32 nCount(
					(bObjectTransformationUsed ? 1 : 0) +
					(bOrientationUsed ? 1 : 0) +
					(bProjectionUsed ? 1 : 0) +
					(bProjectionUsed_30 ? 1 : 0) +
					(bProjectionUsed_31 ? 1 : 0) +
					(bProjectionUsed_32 ? 1 : 0) +
					(bProjectionUsed_33 ? 1 : 0) +
					(bDeviceToViewUsed ? 1 : 0) +
					(bTimeUsed ? 1 : 0) + 
					(bExtraInformation ? mxExtendedInformation.getLength() : 0));

				mxViewInformation.realloc(nCount);

				if(bObjectTransformationUsed)
				{
					com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
					basegfx::unotools::affineMatrixFromHomMatrix3D(aAffineMatrix3D, maObjectTransformation);
					mxViewInformation[nIndex].Name = getNamePropertyObjectTransformation();
					mxViewInformation[nIndex].Value <<= aAffineMatrix3D;
					nIndex++;
				}

				if(bOrientationUsed)
				{
					com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
					basegfx::unotools::affineMatrixFromHomMatrix3D(aAffineMatrix3D, maOrientation);
					mxViewInformation[nIndex].Name = getNamePropertyOrientation();
					mxViewInformation[nIndex].Value <<= aAffineMatrix3D;
					nIndex++;
				}

				if(bProjectionUsed)
				{
					com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
					basegfx::unotools::affineMatrixFromHomMatrix3D(aAffineMatrix3D, maProjection);
					mxViewInformation[nIndex].Name = getNamePropertyProjection();
					mxViewInformation[nIndex].Value <<= aAffineMatrix3D;
					nIndex++;
				}

				if(bProjectionUsed_30)
				{
					mxViewInformation[nIndex].Name = getNamePropertyProjection_30();
					mxViewInformation[nIndex].Value <<= maProjection.get(3, 0);
					nIndex++;
				}

				if(bProjectionUsed_31)
				{
					mxViewInformation[nIndex].Name = getNamePropertyProjection_31();
					mxViewInformation[nIndex].Value <<= maProjection.get(3, 1);
					nIndex++;
				}

				if(bProjectionUsed_32)
				{
					mxViewInformation[nIndex].Name = getNamePropertyProjection_32();
					mxViewInformation[nIndex].Value <<= maProjection.get(3, 2);
					nIndex++;
				}

				if(bProjectionUsed_33)
				{
					mxViewInformation[nIndex].Name = getNamePropertyProjection_33();
					mxViewInformation[nIndex].Value <<= maProjection.get(3, 3);
					nIndex++;
				}

				if(bDeviceToViewUsed)
				{
					com::sun::star::geometry::AffineMatrix3D aAffineMatrix3D;
					basegfx::unotools::affineMatrixFromHomMatrix3D(aAffineMatrix3D, maDeviceToView);
					mxViewInformation[nIndex].Name = getNamePropertyDeviceToView();
					mxViewInformation[nIndex].Value <<= aAffineMatrix3D;
					nIndex++;
				}

				if(bTimeUsed)
				{
					mxViewInformation[nIndex].Name = getNamePropertyTime();
					mxViewInformation[nIndex].Value <<= mfViewTime;
					nIndex++;
				}

				if(bExtraInformation)
				{
					const sal_Int32 nExtra(mxExtendedInformation.getLength());

					for(sal_Int32 a(0); a < nExtra; a++)
					{
						mxViewInformation[nIndex++] = mxExtendedInformation[a];
					}
				}
			}

		public:
			ImpViewInformation3D(
				const basegfx::B3DHomMatrix& rObjectTransformation, 
				const basegfx::B3DHomMatrix& rOrientation, 
				const basegfx::B3DHomMatrix& rProjection, 
				const basegfx::B3DHomMatrix& rDeviceToView, 
				double fViewTime,
				const uno::Sequence< beans::PropertyValue >& rExtendedParameters)
			:	mnRefCount(0),
				maObjectTransformation(rObjectTransformation),
				maOrientation(rOrientation),
				maProjection(rProjection),
				maDeviceToView(rDeviceToView),
				mfViewTime(fViewTime),
				mxViewInformation(),
				mxExtendedInformation()
			{
				impInterpretPropertyValues(rExtendedParameters);
			}

			ImpViewInformation3D(const uno::Sequence< beans::PropertyValue >& rViewParameters)
			:	mnRefCount(0),
				maObjectTransformation(),
				maOrientation(),
				maProjection(),
				maDeviceToView(),
				mfViewTime(),
				mxViewInformation(rViewParameters),
				mxExtendedInformation()
			{
				impInterpretPropertyValues(rViewParameters);
			}

			ImpViewInformation3D()
			:	mnRefCount(0),
				maObjectTransformation(),
				maOrientation(),
				maProjection(),
				maDeviceToView(),
				mfViewTime(),
				mxViewInformation(),
				mxExtendedInformation()
			{
			}

			const basegfx::B3DHomMatrix& getObjectTransformation() const { return maObjectTransformation; }
			const basegfx::B3DHomMatrix& getOrientation() const { return maOrientation; }
			const basegfx::B3DHomMatrix& getProjection() const { return maProjection; }
			const basegfx::B3DHomMatrix& getDeviceToView() const { return maDeviceToView; }
			double getViewTime() const { return mfViewTime; }

			const basegfx::B3DHomMatrix& getObjectToView() const
			{
				// on demand WorldToView creation
			    ::osl::Mutex m_mutex;

				if(maObjectToView.isIdentity())
				{
					const_cast< ImpViewInformation3D* >(this)->maObjectToView = maDeviceToView * maProjection * maOrientation * maObjectTransformation;
				}

				return maObjectToView; 
			}

			const uno::Sequence< beans::PropertyValue >& getViewInformationSequence() const
			{
			    ::osl::Mutex m_mutex;

                if(!mxViewInformation.hasElements())
				{
					const_cast< ImpViewInformation3D* >(this)->impFillViewInformationFromContent();
				}

				return mxViewInformation;
			}

			const uno::Sequence< beans::PropertyValue >& getExtendedInformationSequence() const
			{
				return mxExtendedInformation;
			}

			bool operator==(const ImpViewInformation3D& rCandidate) const
			{
				return (maObjectTransformation == rCandidate.maObjectTransformation
					&& maOrientation == rCandidate.maOrientation
					&& maProjection == rCandidate.maProjection
					&& maDeviceToView == rCandidate.maDeviceToView
					&& mfViewTime == rCandidate.mfViewTime
					&& mxExtendedInformation == rCandidate.mxExtendedInformation);
			}

			static ImpViewInformation3D* get_global_default()
            {
                static ImpViewInformation3D* pDefault = 0;

                if(!pDefault)
                {
                    pDefault = new ImpViewInformation3D();

                    // never delete; start with RefCount 1, not 0
    			    pDefault->mnRefCount++;
                }

                return pDefault;
            }
		};
	} // end of anonymous namespace
} // end of namespace drawinglayer

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

namespace drawinglayer
{
	namespace geometry
	{
		ViewInformation3D::ViewInformation3D(
			const basegfx::B3DHomMatrix& rObjectObjectTransformation, 
			const basegfx::B3DHomMatrix& rOrientation, 
			const basegfx::B3DHomMatrix& rProjection, 
			const basegfx::B3DHomMatrix& rDeviceToView, 
			double fViewTime,
			const uno::Sequence< beans::PropertyValue >& rExtendedParameters)
		:	mpViewInformation3D(new ImpViewInformation3D(
				rObjectObjectTransformation, rOrientation, rProjection, 
				rDeviceToView, fViewTime, rExtendedParameters))
		{
		}

		ViewInformation3D::ViewInformation3D(const uno::Sequence< beans::PropertyValue >& rViewParameters)
		:	mpViewInformation3D(new ImpViewInformation3D(rViewParameters))
		{
		}

		ViewInformation3D::ViewInformation3D()
        :	mpViewInformation3D(ImpViewInformation3D::get_global_default())
		{
			mpViewInformation3D->mnRefCount++;
		}

		ViewInformation3D::ViewInformation3D(const ViewInformation3D& rCandidate)
		:	mpViewInformation3D(rCandidate.mpViewInformation3D)
		{
		    ::osl::Mutex m_mutex;
			mpViewInformation3D->mnRefCount++;
		}

		ViewInformation3D::~ViewInformation3D()
		{
		    ::osl::Mutex m_mutex;

			if(mpViewInformation3D->mnRefCount)
			{
				mpViewInformation3D->mnRefCount--;
			}
			else
			{
				delete mpViewInformation3D;
			}
		}

        bool ViewInformation3D::isDefault() const
        {
            return mpViewInformation3D == ImpViewInformation3D::get_global_default();
        }

		ViewInformation3D& ViewInformation3D::operator=(const ViewInformation3D& rCandidate)
		{
		    ::osl::Mutex m_mutex;

			if(mpViewInformation3D->mnRefCount)
			{
				mpViewInformation3D->mnRefCount--;
			}
			else
			{
				delete mpViewInformation3D;
			}
			
			mpViewInformation3D = rCandidate.mpViewInformation3D;
			mpViewInformation3D->mnRefCount++;

			return *this;
		}

		bool ViewInformation3D::operator==(const ViewInformation3D& rCandidate) const
		{
			if(rCandidate.mpViewInformation3D == mpViewInformation3D)
			{
				return true;
			}

			if(rCandidate.isDefault() != isDefault())
			{
				return false;
			}

			return (*rCandidate.mpViewInformation3D == *mpViewInformation3D);
		}

		const basegfx::B3DHomMatrix& ViewInformation3D::getObjectTransformation() const
		{
			return mpViewInformation3D->getObjectTransformation();
		}

		const basegfx::B3DHomMatrix& ViewInformation3D::getOrientation() const
		{
			return mpViewInformation3D->getOrientation();
		}

		const basegfx::B3DHomMatrix& ViewInformation3D::getProjection() const
		{
			return mpViewInformation3D->getProjection();
		}

		const basegfx::B3DHomMatrix& ViewInformation3D::getDeviceToView() const
		{
			return mpViewInformation3D->getDeviceToView();
		}

		const basegfx::B3DHomMatrix& ViewInformation3D::getObjectToView() const
		{
			return mpViewInformation3D->getObjectToView();
		}

		double ViewInformation3D::getViewTime() const
		{
			return mpViewInformation3D->getViewTime();
		}

		const uno::Sequence< beans::PropertyValue >& ViewInformation3D::getViewInformationSequence() const
		{
			return mpViewInformation3D->getViewInformationSequence();
		}

		const uno::Sequence< beans::PropertyValue >& ViewInformation3D::getExtendedInformationSequence() const
		{
			return mpViewInformation3D->getExtendedInformationSequence();
		}
	} // end of namespace geometry
} // end of namespace drawinglayer

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