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

#include "impanmvw.hxx"
#include <vcl/virdev.hxx>
#include <vcl/window.hxx>
#include <vcl/salbtype.hxx>

// ----------------
// - ImplAnimView -
// ----------------

ImplAnimView::ImplAnimView( Animation* pParent, OutputDevice* pOut,
							const Point& rPt, const Size& rSz,
							sal_uLong nExtraData,
							OutputDevice* pFirstFrameOutDev ) :
		mpParent		( pParent ),
		mpOut			( pFirstFrameOutDev ? pFirstFrameOutDev : pOut ),
		mnExtraData		( nExtraData ),
		maPt			( rPt ),
		maSz			( rSz ),
		maSzPix			( mpOut->LogicToPixel( maSz ) ),
		maClip			( mpOut->GetClipRegion() ),
		mpBackground	( new VirtualDevice ),
		mpRestore		( new VirtualDevice ),
		meLastDisposal	( DISPOSE_BACK ),
		mbPause			( sal_False ),
		mbMarked		( sal_False ),
		mbHMirr			( maSz.Width() < 0L ),
		mbVMirr			( maSz.Height() < 0L )
{
	mpParent->ImplIncAnimCount();

	// mirrored horizontically?
	if( mbHMirr )
	{
		maDispPt.X() = maPt.X() + maSz.Width() + 1L;
		maDispSz.Width() = -maSz.Width();
		maSzPix.Width() = -maSzPix.Width();
	}
	else
	{
		maDispPt.X() = maPt.X();
		maDispSz.Width() = maSz.Width();
	}

	// mirrored vertically?
	if( mbVMirr )
	{
		maDispPt.Y() = maPt.Y() + maSz.Height() + 1L;
		maDispSz.Height() = -maSz.Height();
		maSzPix.Height() = -maSzPix.Height();
	}
	else
	{
		maDispPt.Y() = maPt.Y();
		maDispSz.Height() = maSz.Height();
	}

	// save background
	mpBackground->SetOutputSizePixel( maSzPix );

	if( mpOut->GetOutDevType() == OUTDEV_WINDOW )
	{
		MapMode aTempMap( mpOut->GetMapMode() );
		aTempMap.SetOrigin( Point() );
		mpBackground->SetMapMode( aTempMap );
		( (Window*) mpOut )->SaveBackground( maDispPt, maDispSz, Point(), *mpBackground );
		mpBackground->SetMapMode( MapMode() );
	}
	else
		mpBackground->DrawOutDev( Point(), maSzPix, maDispPt, maDispSz, *mpOut );
	
	// initial drawing to actual position
	ImplDrawToPos( mpParent->ImplGetCurPos() );

	// if first frame OutputDevice is set, update variables now for real OutputDevice
	if( pFirstFrameOutDev )
		maClip = ( mpOut = pOut )->GetClipRegion();
}

// ------------------------------------------------------------------------

ImplAnimView::~ImplAnimView()
{
	delete mpBackground;
	delete mpRestore;

	mpParent->ImplDecAnimCount();
}

// ------------------------------------------------------------------------

sal_Bool ImplAnimView::ImplMatches( OutputDevice* pOut, long nExtraData ) const
{
	sal_Bool bRet = sal_False;
	
	if( nExtraData )
	{
		if( ( mnExtraData == nExtraData ) && ( !pOut || ( pOut == mpOut ) ) )
			bRet = sal_True;
	}
	else if( !pOut || ( pOut == mpOut ) )
		bRet = sal_True;

	return bRet;
}

// ------------------------------------------------------------------------

void ImplAnimView::ImplGetPosSize( const AnimationBitmap& rAnm, Point& rPosPix, Size& rSizePix )
{
	const Size& rAnmSize = mpParent->GetDisplaySizePixel();
	Point		aPt2( rAnm.aPosPix.X() + rAnm.aSizePix.Width() - 1L,
					  rAnm.aPosPix.Y() + rAnm.aSizePix.Height() - 1L );
	double		fFactX, fFactY;

	// calculate x scaling
	if( rAnmSize.Width() > 1L )
		fFactX = (double) ( maSzPix.Width() - 1L ) / ( rAnmSize.Width() - 1L );
	else
		fFactX = 1.0;

	// calculate y scaling
	if( rAnmSize.Height() > 1L )
		fFactY = (double) ( maSzPix.Height() - 1L ) / ( rAnmSize.Height() - 1L );
	else
		fFactY = 1.0;

	rPosPix.X() = FRound( rAnm.aPosPix.X() * fFactX );
	rPosPix.Y() = FRound( rAnm.aPosPix.Y() * fFactY );

	aPt2.X() = FRound( aPt2.X() * fFactX );
	aPt2.Y() = FRound( aPt2.Y() * fFactY );

	rSizePix.Width() = aPt2.X() - rPosPix.X() + 1L;
	rSizePix.Height() = aPt2.Y() - rPosPix.Y() + 1L;

	// mirrored horizontically?
	if( mbHMirr )
		rPosPix.X() = maSzPix.Width() - 1L - aPt2.X();

	// mirrored vertically?
	if( mbVMirr )
		rPosPix.Y() = maSzPix.Height() - 1L - aPt2.Y();
}

// ------------------------------------------------------------------------

void ImplAnimView::ImplDrawToPos( sal_uLong nPos )
{
	VirtualDevice	aVDev;
	Region*			pOldClip = !maClip.IsNull() ? new Region( mpOut->GetClipRegion() ) : NULL;

	aVDev.SetOutputSizePixel( maSzPix, sal_False );
	nPos = Min( nPos, (sal_uLong) mpParent->Count() - 1UL );

	for( sal_uLong i = 0UL; i <= nPos; i++ )
		ImplDraw( i, &aVDev );

	if( pOldClip )
		mpOut->SetClipRegion( maClip );

	mpOut->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, aVDev );

	if( pOldClip )
	{
		mpOut->SetClipRegion( *pOldClip );
		delete pOldClip;
	}
}

// ------------------------------------------------------------------------

void ImplAnimView::ImplDraw( sal_uLong nPos )
{
	ImplDraw( nPos, NULL ); 
}

// ------------------------------------------------------------------------

void ImplAnimView::ImplDraw( sal_uLong nPos, VirtualDevice* pVDev )
{
	Rectangle aOutRect( mpOut->PixelToLogic( Point() ), mpOut->GetOutputSize() );

	// check, if output lies out of display
	if( aOutRect.Intersection( Rectangle( maDispPt, maDispSz ) ).IsEmpty() )
		ImplSetMarked( sal_True );
	else if( !mbPause )
	{
		VirtualDevice*			pDev;
		Point					aPosPix;
		Point					aBmpPosPix;
		Size					aSizePix;
		Size					aBmpSizePix;
		const sal_uLong				nLastPos = mpParent->Count() - 1;
		const AnimationBitmap&	rAnm = mpParent->Get( (sal_uInt16) ( mnActPos = Min( nPos, nLastPos ) ) );

		ImplGetPosSize( rAnm, aPosPix, aSizePix );

		// mirrored horizontically?
		if( mbHMirr )
		{
			aBmpPosPix.X() = aPosPix.X() + aSizePix.Width() - 1L;
			aBmpSizePix.Width() = -aSizePix.Width();
		}
		else
		{
			aBmpPosPix.X() = aPosPix.X();
			aBmpSizePix.Width() = aSizePix.Width();
		}

		// mirrored vertically?
		if( mbVMirr )
		{
			aBmpPosPix.Y() = aPosPix.Y() + aSizePix.Height() - 1L;
			aBmpSizePix.Height() = -aSizePix.Height();
		}
		else
		{
			aBmpPosPix.Y() = aPosPix.Y();
			aBmpSizePix.Height() = aSizePix.Height();
		}

		// get output device
		if( !pVDev )
		{
			pDev = new VirtualDevice;
			pDev->SetOutputSizePixel( maSzPix, sal_False );
			pDev->DrawOutDev( Point(), maSzPix, maDispPt, maDispSz, *mpOut );
		}
		else
			pDev = pVDev;

		// restore background after each run
		if( !nPos )
		{
			meLastDisposal = DISPOSE_BACK;
			maRestPt = Point();
			maRestSz = maSzPix;
		}

		// restore
		if( ( DISPOSE_NOT != meLastDisposal ) && maRestSz.Width() && maRestSz.Height() )
		{
			if( DISPOSE_BACK == meLastDisposal )
				pDev->DrawOutDev( maRestPt, maRestSz, maRestPt, maRestSz, *mpBackground );
			else
				pDev->DrawOutDev( maRestPt, maRestSz, Point(), maRestSz, *mpRestore );
		}

		meLastDisposal = rAnm.eDisposal;
		maRestPt = aPosPix;
		maRestSz = aSizePix;

		// Was muessen wir beim naechsten Mal restaurieren ?
		// ==> ggf. in eine Bitmap stecken, ansonsten SaveBitmap
		// aus Speichergruenden loeschen
		if( ( meLastDisposal == DISPOSE_BACK ) || ( meLastDisposal == DISPOSE_NOT ) )
			mpRestore->SetOutputSizePixel( Size( 1, 1 ), sal_False );
		else
		{
			mpRestore->SetOutputSizePixel( maRestSz, sal_False );
			mpRestore->DrawOutDev( Point(), maRestSz, aPosPix, aSizePix, *pDev );
		}

		pDev->DrawBitmapEx( aBmpPosPix, aBmpSizePix, rAnm.aBmpEx );

		if( !pVDev )
		{
			Region* pOldClip = !maClip.IsNull() ? new Region( mpOut->GetClipRegion() ) : NULL;

			if( pOldClip )
				mpOut->SetClipRegion( maClip );

			mpOut->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *pDev );

			if( pOldClip )
			{
				mpOut->SetClipRegion( *pOldClip );
				delete pOldClip;
			}
			
			delete pDev;

			if( mpOut->GetOutDevType() == OUTDEV_WINDOW )
				( (Window*) mpOut )->Sync();
		}
	}
}

// ------------------------------------------------------------------------

void ImplAnimView::ImplRepaint()
{
	const sal_Bool bOldPause = mbPause;

	if( mpOut->GetOutDevType() == OUTDEV_WINDOW )
	{
		MapMode aTempMap( mpOut->GetMapMode() );
		aTempMap.SetOrigin( Point() );
		mpBackground->SetMapMode( aTempMap );
		( (Window*) mpOut )->SaveBackground( maDispPt, maDispSz, Point(), *mpBackground );
		mpBackground->SetMapMode( MapMode() );
	}
	else
		mpBackground->DrawOutDev( Point(), maSzPix, maDispPt, maDispSz, *mpOut );

	mbPause = sal_False;
	ImplDrawToPos( mnActPos ); 
	mbPause = bOldPause;
}

// ------------------------------------------------------------------------

AInfo* ImplAnimView::ImplCreateAInfo() const
{
	AInfo* pAInfo = new AInfo;

	pAInfo->aStartOrg = maPt;
	pAInfo->aStartSize = maSz;
	pAInfo->pOutDev = mpOut;
	pAInfo->pViewData = (void*) this;
	pAInfo->nExtraData = mnExtraData;
	pAInfo->bPause = mbPause;

	return pAInfo;
}
