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

#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>

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

namespace basegfx
{
    ::rtl::OUString exportToSvg( const B2DHomMatrix& rMatrix )
    {
        rtl::OUStringBuffer aStrBuf;
        aStrBuf.appendAscii("matrix(");

        aStrBuf.append(rMatrix.get(0,0));
        aStrBuf.appendAscii(", ");

        aStrBuf.append(rMatrix.get(1,0));
        aStrBuf.appendAscii(", ");

        aStrBuf.append(rMatrix.get(0,1));
        aStrBuf.appendAscii(", ");

        aStrBuf.append(rMatrix.get(1,1));
        aStrBuf.appendAscii(", ");

        aStrBuf.append(rMatrix.get(0,2));
        aStrBuf.appendAscii(", ");

        aStrBuf.append(rMatrix.get(1,2));
        aStrBuf.appendAscii(")");

        return aStrBuf.makeStringAndClear();
    }

	namespace tools
	{
		void createSinCosOrthogonal(double& o_rSin, double& o_rCos, double fRadiant)
		{
			if( fTools::equalZero( fmod( fRadiant, F_PI2 ) ) )
			{
				// determine quadrant
				const sal_Int32 nQuad( 
					(4 + fround( 4/F_2PI*fmod( fRadiant, F_2PI ) )) % 4 );
				switch( nQuad )
				{
					case 0: // -2pi,0,2pi
						o_rSin = 0.0;
						o_rCos = 1.0;
						break;

					case 1: // -3/2pi,1/2pi
						o_rSin = 1.0;
						o_rCos = 0.0;
						break;

					case 2: // -pi,pi
						o_rSin = 0.0;
						o_rCos = -1.0;
						break;

					case 3: // -1/2pi,3/2pi
						o_rSin = -1.0;
						o_rCos = 0.0;
						break;

					default:
						OSL_ENSURE( false, "createSinCos: Impossible case reached" );
				}
			}
			else
			{
				// TODO(P1): Maybe use glibc's sincos here (though
				// that's kinda non-portable...)
				o_rSin = sin(fRadiant);
				o_rCos = cos(fRadiant);
			}
		}

		B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY)
		{
			B2DHomMatrix aRetval;
			const double fOne(1.0);

			if(!fTools::equal(fScaleX, fOne))
			{
				aRetval.set(0, 0, fScaleX);
			}

			if(!fTools::equal(fScaleY, fOne))
			{
				aRetval.set(1, 1, fScaleY);
			}

			return aRetval;
		}

		B2DHomMatrix createShearXB2DHomMatrix(double fShearX)
		{
			B2DHomMatrix aRetval;

			if(!fTools::equalZero(fShearX))
			{
				aRetval.set(0, 1, fShearX);
			}

			return aRetval;
		}

		B2DHomMatrix createShearYB2DHomMatrix(double fShearY)
		{
			B2DHomMatrix aRetval;

			if(!fTools::equalZero(fShearY))
			{
				aRetval.set(1, 0, fShearY);
			}

			return aRetval;
		}

		B2DHomMatrix createRotateB2DHomMatrix(double fRadiant)
		{
			B2DHomMatrix aRetval;

			if(!fTools::equalZero(fRadiant))
			{
				double fSin(0.0);
				double fCos(1.0);

				createSinCosOrthogonal(fSin, fCos, fRadiant);
				aRetval.set(0, 0, fCos);
				aRetval.set(1, 1, fCos);
				aRetval.set(1, 0, fSin);
				aRetval.set(0, 1, -fSin);
			}

			return aRetval;
		}

		B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY)
		{
			B2DHomMatrix aRetval;

			if(!(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY)))
			{
				aRetval.set(0, 2, fTranslateX);
				aRetval.set(1, 2, fTranslateY);
			}

			return aRetval;
		}

		B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix(
			double fScaleX, double fScaleY,
			double fShearX,
			double fRadiant,
			double fTranslateX, double fTranslateY)
		{
			const double fOne(1.0);

			if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne))
			{
				/// no scale, take shortcut
				return createShearXRotateTranslateB2DHomMatrix(fShearX, fRadiant, fTranslateX, fTranslateY);
			}
			else
			{
				/// scale used
				if(fTools::equalZero(fShearX))
				{
					/// no shear
					if(fTools::equalZero(fRadiant))
					{
						/// no rotate, take shortcut
						return createScaleTranslateB2DHomMatrix(fScaleX, fScaleY, fTranslateX, fTranslateY);
					}
					else
					{
						/// rotate and scale used, no shear
						double fSin(0.0);
						double fCos(1.0);

						createSinCosOrthogonal(fSin, fCos, fRadiant);

						B2DHomMatrix aRetval(
							/* Row 0, Column 0 */ fCos * fScaleX,
							/* Row 0, Column 1 */ fScaleY * -fSin,
							/* Row 0, Column 2 */ fTranslateX,
							/* Row 1, Column 0 */ fSin * fScaleX,
							/* Row 1, Column 1 */ fScaleY * fCos,
							/* Row 1, Column 2 */ fTranslateY);
						
						return aRetval;
					}
				}
				else
				{
					/// scale and shear used
					if(fTools::equalZero(fRadiant))
					{
						/// scale and shear, but no rotate
						B2DHomMatrix aRetval(
							/* Row 0, Column 0 */ fScaleX,
							/* Row 0, Column 1 */ fScaleY * fShearX,
							/* Row 0, Column 2 */ fTranslateX,
							/* Row 1, Column 0 */ 0.0,
							/* Row 1, Column 1 */ fScaleY,
							/* Row 1, Column 2 */ fTranslateY);

						return aRetval;
					}
					else
					{
						/// scale, shear and rotate used
						double fSin(0.0);
						double fCos(1.0);

						createSinCosOrthogonal(fSin, fCos, fRadiant);

						B2DHomMatrix aRetval(
							/* Row 0, Column 0 */ fCos * fScaleX,
							/* Row 0, Column 1 */ fScaleY * ((fCos * fShearX) - fSin),
							/* Row 0, Column 2 */ fTranslateX,
							/* Row 1, Column 0 */ fSin * fScaleX,
							/* Row 1, Column 1 */ fScaleY * ((fSin * fShearX) + fCos),
							/* Row 1, Column 2 */ fTranslateY);
						
						return aRetval;
					}
				}
			}
		}
		
		B2DHomMatrix createShearXRotateTranslateB2DHomMatrix(
			double fShearX,
			double fRadiant,
			double fTranslateX, double fTranslateY)
		{
			if(fTools::equalZero(fShearX))
			{
				/// no shear
				if(fTools::equalZero(fRadiant))
				{
					/// no shear, no rotate, take shortcut
					return createTranslateB2DHomMatrix(fTranslateX, fTranslateY);
				}
				else
				{
					/// no shear, but rotate used
					double fSin(0.0);
					double fCos(1.0);

					createSinCosOrthogonal(fSin, fCos, fRadiant);

					B2DHomMatrix aRetval(
						/* Row 0, Column 0 */ fCos,
						/* Row 0, Column 1 */ -fSin,
						/* Row 0, Column 2 */ fTranslateX,
						/* Row 1, Column 0 */ fSin,
						/* Row 1, Column 1 */ fCos,
						/* Row 1, Column 2 */ fTranslateY);
					
					return aRetval;
				}
			}
			else
			{
				/// shear used
				if(fTools::equalZero(fRadiant))
				{
					/// no rotate, but shear used
					B2DHomMatrix aRetval(
						/* Row 0, Column 0 */ 1.0,
						/* Row 0, Column 1 */ fShearX,
						/* Row 0, Column 2 */ fTranslateX,
						/* Row 1, Column 0 */ 0.0,
						/* Row 1, Column 1 */ 1.0,
						/* Row 1, Column 2 */ fTranslateY);
					
					return aRetval;
				}
				else
				{
					/// shear and rotate used
					double fSin(0.0);
					double fCos(1.0);

					createSinCosOrthogonal(fSin, fCos, fRadiant);

					B2DHomMatrix aRetval(
						/* Row 0, Column 0 */ fCos,
						/* Row 0, Column 1 */ (fCos * fShearX) - fSin,
						/* Row 0, Column 2 */ fTranslateX,
						/* Row 1, Column 0 */ fSin,
						/* Row 1, Column 1 */ (fSin * fShearX) + fCos,
						/* Row 1, Column 2 */ fTranslateY);
					
					return aRetval;
				}
			}
		}
		
		B2DHomMatrix createScaleTranslateB2DHomMatrix(
			double fScaleX, double fScaleY,
			double fTranslateX, double fTranslateY)
		{
			const double fOne(1.0);

			if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne))
			{
				/// no scale, take shortcut
				return createTranslateB2DHomMatrix(fTranslateX, fTranslateY);
			}
			else
			{
				/// scale used
				if(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY))
				{
					/// no translate, but scale.
					B2DHomMatrix aRetval;
					
					aRetval.set(0, 0, fScaleX);
					aRetval.set(1, 1, fScaleY);

					return aRetval;
				}
				else
				{
					/// translate and scale
					B2DHomMatrix aRetval(
						/* Row 0, Column 0 */ fScaleX,
						/* Row 0, Column 1 */ 0.0,
						/* Row 0, Column 2 */ fTranslateX,
						/* Row 1, Column 0 */ 0.0,
						/* Row 1, Column 1 */ fScaleY,
						/* Row 1, Column 2 */ fTranslateY);
					
					return aRetval;
				}
			}
		}

        B2DHomMatrix createRotateAroundPoint(
            double fPointX, double fPointY,
            double fRadiant)
        {
			B2DHomMatrix aRetval;
            
			if(!fTools::equalZero(fRadiant))
			{
                double fSin(0.0);
				double fCos(1.0);

				createSinCosOrthogonal(fSin, fCos, fRadiant);
                
                aRetval.set3x2(
					/* Row 0, Column 0 */ fCos,
					/* Row 0, Column 1 */ -fSin,
					/* Row 0, Column 2 */ (fPointX * (1.0 - fCos)) + (fSin * fPointY),
					/* Row 1, Column 0 */ fSin,
					/* Row 1, Column 1 */ fCos,
					/* Row 1, Column 2 */ (fPointY * (1.0 - fCos)) - (fSin * fPointX));
            }

            return aRetval;
        }

        /// special for the case to map from source range to target range
        B2DHomMatrix createSourceRangeTargetRangeTransform(
            const B2DRange& rSourceRange,
            const B2DRange& rTargetRange)
        {
            B2DHomMatrix aRetval;

            if(&rSourceRange == &rTargetRange)
            {
                return aRetval;
            }

            if(!fTools::equalZero(rSourceRange.getMinX()) || !fTools::equalZero(rSourceRange.getMinY()))
            {
                aRetval.set(0, 2, -rSourceRange.getMinX());
                aRetval.set(1, 2, -rSourceRange.getMinY());
            }

            const double fSourceW(rSourceRange.getWidth());
            const double fSourceH(rSourceRange.getHeight());
            const bool bDivX(!fTools::equalZero(fSourceW) && !fTools::equal(fSourceW, 1.0));
            const bool bDivY(!fTools::equalZero(fSourceH) && !fTools::equal(fSourceH, 1.0));
            const double fScaleX(bDivX ? rTargetRange.getWidth() / fSourceW : rTargetRange.getWidth());
            const double fScaleY(bDivY ? rTargetRange.getHeight() / fSourceH : rTargetRange.getHeight());

            if(!fTools::equalZero(fScaleX) || !fTools::equalZero(fScaleY))
            {
                aRetval.scale(fScaleX, fScaleY);
            }

            if(!fTools::equalZero(rTargetRange.getMinX()) || !fTools::equalZero(rTargetRange.getMinY()))
            {
                aRetval.translate(
                    rTargetRange.getMinX(), 
                    rTargetRange.getMinY());
            }

            return aRetval;
        }

    } // end of namespace tools
} // end of namespace basegfx

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