/**************************************************************
 * 
 * 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/animation/animationtiming.hxx>
#include <basegfx/numeric/ftools.hxx>

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

namespace drawinglayer
{
	namespace animation
	{
		//////////////////////////////////////////////////////////////////////////////

		AnimationEntry::AnimationEntry()
		{
		}

		AnimationEntry::~AnimationEntry()
		{
		}

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

		AnimationEntryFixed::AnimationEntryFixed(double fDuration, double fState)
		:	mfDuration(fDuration),
			mfState(fState)
		{
		}

		AnimationEntryFixed::~AnimationEntryFixed()
		{
		}

		AnimationEntry* AnimationEntryFixed::clone() const
		{
			return new AnimationEntryFixed(mfDuration, mfState);
		}

		bool AnimationEntryFixed::operator==(const AnimationEntry& rCandidate) const
		{
			const AnimationEntryFixed* pCompare = dynamic_cast< const AnimationEntryFixed* >(&rCandidate);

			return (pCompare 
				&& basegfx::fTools::equal(mfDuration, pCompare->mfDuration)
				&& basegfx::fTools::equal(mfState, pCompare->mfState));
		}

		double AnimationEntryFixed::getDuration() const
		{
			return mfDuration;
		}

		double AnimationEntryFixed::getStateAtTime(double /*fTime*/) const
		{
			return mfState;
		}

		double AnimationEntryFixed::getNextEventTime(double fTime) const
		{
			if(basegfx::fTools::less(fTime, mfDuration))
			{
				return mfDuration;
			}
			else
			{
				return 0.0;
			}
		}

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

		AnimationEntryLinear::AnimationEntryLinear(double fDuration, double fFrequency, double fStart, double fStop)
		:	mfDuration(fDuration),
			mfFrequency(fFrequency),
			mfStart(fStart),
			mfStop(fStop)
		{
		}

		AnimationEntryLinear::~AnimationEntryLinear()
		{
		}

		AnimationEntry* AnimationEntryLinear::clone() const
		{
			return new AnimationEntryLinear(mfDuration, mfFrequency, mfStart, mfStop);
		}

		bool AnimationEntryLinear::operator==(const AnimationEntry& rCandidate) const
		{
			const AnimationEntryLinear* pCompare = dynamic_cast< const AnimationEntryLinear* >(&rCandidate);
			
			return (pCompare 
				&& basegfx::fTools::equal(mfDuration, pCompare->mfDuration)
				&& basegfx::fTools::equal(mfStart, pCompare->mfStart)
				&& basegfx::fTools::equal(mfStop, pCompare->mfStop));
		}

		double AnimationEntryLinear::getDuration() const
		{
			return mfDuration;
		}

		double AnimationEntryLinear::getStateAtTime(double fTime) const
		{
			if(basegfx::fTools::more(mfDuration, 0.0))
			{
				const double fFactor(fTime / mfDuration);

				if(fFactor > 1.0)
				{
					return mfStop;
				}
				else
				{
					return mfStart + ((mfStop - mfStart) * fFactor);
				}
			}
			else
			{
				return mfStart;
			}
		}

		double AnimationEntryLinear::getNextEventTime(double fTime) const
		{
			if(basegfx::fTools::less(fTime, mfDuration))
			{
				// use the simple solution: just add the frequency. More correct (but also more
				// complicated) would be to calculate the slice of time we are in and when this
				// slice will end. For the animations, this makes no quality difference.
				fTime += mfFrequency;

				if(basegfx::fTools::more(fTime, mfDuration))
				{
					fTime = mfDuration;
				}

				return fTime;
			}
			else
			{
				return 0.0;
			}
		}

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

		sal_uInt32 AnimationEntryList::impGetIndexAtTime(double fTime, double &rfAddedTime) const
		{
			sal_uInt32 nIndex(0L);

			while(nIndex < maEntries.size() && basegfx::fTools::lessOrEqual(rfAddedTime + maEntries[nIndex]->getDuration(), fTime))
			{
				rfAddedTime += maEntries[nIndex++]->getDuration();
			}

			return nIndex;
		}

		AnimationEntryList::AnimationEntryList()
		:	mfDuration(0.0)
		{
		}

		AnimationEntryList::~AnimationEntryList()
		{
			for(sal_uInt32 a(0L); a < maEntries.size(); a++)
			{
				delete maEntries[a];
			}
		}

		AnimationEntry* AnimationEntryList::clone() const
		{
			AnimationEntryList* pNew = new AnimationEntryList();
			
			for(sal_uInt32 a(0L); a < maEntries.size(); a++)
			{
				pNew->append(*maEntries[a]);
			}
			
			return pNew;
		}

		bool AnimationEntryList::operator==(const AnimationEntry& rCandidate) const
		{
			const AnimationEntryList* pCompare = dynamic_cast< const AnimationEntryList* >(&rCandidate);
			
			if(pCompare && mfDuration == pCompare->mfDuration)
			{
				for(sal_uInt32 a(0L); a < maEntries.size(); a++)
				{
					if(!(*maEntries[a] == *pCompare->maEntries[a]))
					{
						return false;
					}
				}

				return true;
			}

			return false;
		}

		void AnimationEntryList::append(const AnimationEntry& rCandidate)
		{
			const double fDuration(rCandidate.getDuration());

			if(!basegfx::fTools::equalZero(fDuration))
			{
				maEntries.push_back(rCandidate.clone());
				mfDuration += fDuration;
			}
		}

		double AnimationEntryList::getDuration() const
		{
			return mfDuration;
		}

		double AnimationEntryList::getStateAtTime(double fTime) const
		{
			if(!basegfx::fTools::equalZero(mfDuration))
			{
				double fAddedTime(0.0);
				const sal_uInt32 nIndex(impGetIndexAtTime(fTime, fAddedTime));

				if(nIndex < maEntries.size())
				{
					return maEntries[nIndex]->getStateAtTime(fTime - fAddedTime);
				}
			}

			return 0.0;
		}

		double AnimationEntryList::getNextEventTime(double fTime) const
		{
			double fNewTime(0.0);

			if(!basegfx::fTools::equalZero(mfDuration))
			{
				double fAddedTime(0.0);
				const sal_uInt32 nIndex(impGetIndexAtTime(fTime, fAddedTime));

				if(nIndex < maEntries.size())
				{
					fNewTime = maEntries[nIndex]->getNextEventTime(fTime - fAddedTime) + fAddedTime;
				}
			}

			return fNewTime;
		}

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

		AnimationEntryLoop::AnimationEntryLoop(sal_uInt32 nRepeat)
		:	AnimationEntryList(),
			mnRepeat(nRepeat)
		{
		}

		AnimationEntryLoop::~AnimationEntryLoop()
		{
		}

		AnimationEntry* AnimationEntryLoop::clone() const
		{
			AnimationEntryLoop* pNew = new AnimationEntryLoop(mnRepeat);
			
			for(sal_uInt32 a(0L); a < maEntries.size(); a++)
			{
				pNew->append(*maEntries[a]);
			}
			
			return pNew;
		}

		bool AnimationEntryLoop::operator==(const AnimationEntry& rCandidate) const
		{
			const AnimationEntryLoop* pCompare = dynamic_cast< const AnimationEntryLoop* >(&rCandidate);
			
			return (pCompare 
				&& mnRepeat == pCompare->mnRepeat
				&& AnimationEntryList::operator==(rCandidate));
		}

		double AnimationEntryLoop::getDuration() const
		{
			return (mfDuration * (double)mnRepeat);
		}

		double AnimationEntryLoop::getStateAtTime(double fTime) const
		{
			if(mnRepeat && !basegfx::fTools::equalZero(mfDuration))
			{
				const sal_uInt32 nCurrentLoop((sal_uInt32)(fTime / mfDuration));

				if(nCurrentLoop > mnRepeat)
				{
					return 1.0;
				}
				else
				{
					const double fTimeAtLoopStart((double)nCurrentLoop * mfDuration);
					const double fRelativeTime(fTime - fTimeAtLoopStart);
					return AnimationEntryList::getStateAtTime(fRelativeTime);
				}
			}

			return 0.0;
		}

		double AnimationEntryLoop::getNextEventTime(double fTime) const
		{
			double fNewTime(0.0);

			if(mnRepeat && !basegfx::fTools::equalZero(mfDuration))
			{
				const sal_uInt32 nCurrentLoop((sal_uInt32)(fTime / mfDuration));

				if(nCurrentLoop <= mnRepeat)
				{
					const double fTimeAtLoopStart((double)nCurrentLoop * mfDuration);
					const double fRelativeTime(fTime - fTimeAtLoopStart);
					const double fNextEventAtLoop(AnimationEntryList::getNextEventTime(fRelativeTime));
					
					if(!basegfx::fTools::equalZero(fNextEventAtLoop))
					{
						fNewTime = fNextEventAtLoop + fTimeAtLoopStart;
					}
				}
			}

			return fNewTime;
		}
	} // end of namespace animation
} // end of namespace drawinglayer

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