// This file is part of VSTGUI. It is subject to the license terms 
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#include "animations.h"
#include "../cview.h"
#include "../cframe.h"
#include "../controls/ccontrol.h"
#include <cassert>
#include <cmath>

namespace VSTGUI {
namespace Animation {
//------------------------------------------------------------------------
/*! @defgroup AnimationTargets Animation Targets
 *	@ingroup animation
 */
//------------------------------------------------------------------------

/** @class AlphaValueAnimation
	see @ref page_animation Support */
//-----------------------------------------------------------------------------
AlphaValueAnimation::AlphaValueAnimation (float endValue, bool forceEndValueOnFinish)
: startValue (0.f)
, endValue (endValue)
, forceEndValueOnFinish (forceEndValueOnFinish)
{
}

//-----------------------------------------------------------------------------
void AlphaValueAnimation::animationStart (CView* view, IdStringPtr name)
{
	startValue = view->getAlphaValue ();
}

//-----------------------------------------------------------------------------
void AlphaValueAnimation::animationTick (CView* view, IdStringPtr name, float pos)
{
	float alpha = startValue + (endValue - startValue) * pos;
	view->setAlphaValue (alpha);
}

//-----------------------------------------------------------------------------
void AlphaValueAnimation::animationFinished (CView* view, IdStringPtr name, bool wasCanceled)
{
	if (!wasCanceled || forceEndValueOnFinish)
		view->setAlphaValue (endValue);
}

//-----------------------------------------------------------------------------
/** @class ViewSizeAnimation
	see @ref page_animation Support */
//-----------------------------------------------------------------------------
ViewSizeAnimation::ViewSizeAnimation (const CRect& inNewRect, bool forceEndValueOnFinish)
: newRect (inNewRect)
, forceEndValueOnFinish (forceEndValueOnFinish)
{
}

//-----------------------------------------------------------------------------
void ViewSizeAnimation::animationStart (CView* view, IdStringPtr name)
{
	startRect = view->getViewSize ();
}

//-----------------------------------------------------------------------------
void ViewSizeAnimation::animationFinished (CView* view, IdStringPtr name, bool wasCanceled)
{
	if (!wasCanceled || forceEndValueOnFinish)
	{
		if (view->getViewSize () != newRect)
		{
			view->invalid ();
			view->setViewSize (newRect);
			view->setMouseableArea (view->getViewSize ());
			view->invalid ();
		}
	}
}

//-----------------------------------------------------------------------------
void ViewSizeAnimation::animationTick (CView* view, IdStringPtr name, float pos)
{
	CRect r;
	r.left = (int32_t)(startRect.left + ((newRect.left - startRect.left) * pos));
	r.right = (int32_t)(startRect.right + ((newRect.right - startRect.right) * pos));
	r.top = (int32_t)(startRect.top + ((newRect.top - startRect.top) * pos));
	r.bottom = (int32_t)(startRect.bottom + ((newRect.bottom - startRect.bottom) * pos));
	if (view->getViewSize () != r)
	{
		view->invalid ();
		view->setViewSize (r);
		view->setMouseableArea (view->getViewSize ());
		view->invalid ();
	}
}

//-----------------------------------------------------------------------------
/** @class ExchangeViewAnimation
	see @ref page_animation Support */
//-----------------------------------------------------------------------------
ExchangeViewAnimation::ExchangeViewAnimation (CView* oldView, CView* newView, AnimationStyle style)
: newView (newView)
, viewToRemove (oldView)
, style (style)
{
	vstgui_assert (newView->isAttached () == false);
	vstgui_assert (viewToRemove->isAttached ());

	if (auto parent = viewToRemove->getParentView ()->asViewContainer ())
		parent->addView (newView);

	init ();
}

//-----------------------------------------------------------------------------
ExchangeViewAnimation::~ExchangeViewAnimation () noexcept
{
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::updateViewSize (CView* view, const CRect& rect)
{
	view->invalid ();
	view->setViewSize (rect);
	view->setMouseableArea (rect);
	view->invalid ();
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::init ()
{
	if (style == kAlphaValueFade)
	{
		oldViewAlphaValueStart = viewToRemove->getAlphaValue ();
		newViewAlphaValueEnd = newView->getAlphaValue ();
		newView->setAlphaValue (0.f);
	}
	else
	{
		destinationRect = viewToRemove->getViewSize ();
		switch (style)
		{
			case kAlphaValueFade: break;
			case kPushInFromLeft:
			{
				doPushInFromLeft (0.f);
				break;
			}
			case kPushInFromRight:
			{
				doPushInFromRight (0.f);
				break;
			}
			case kPushInFromTop:
			{
				doPushInFromTop (0.f);
				break;
			}
			case kPushInFromBottom:
			{
				doPushInFromBottom (0.f);
				break;
			}
			case kPushInOutFromLeft:
			{
				doPushInOutFromLeft (0.f);
				break;
			}
			case kPushInOutFromRight:
			{
				doPushInOutFromRight (0.f);
				break;
			}
		}
	}
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doAlphaFade (float pos)
{
	float alpha = oldViewAlphaValueStart - (oldViewAlphaValueStart * pos);
	viewToRemove->setAlphaValue (alpha);
	alpha = newViewAlphaValueEnd * pos;
	newView->setAlphaValue (alpha);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doPushInFromLeft (float pos)
{
	CRect viewSize (newView->getViewSize ());
	CCoord leftOrigin = destinationRect.left;
	CCoord offset = viewSize.getWidth () * (1.f - pos);
	viewSize.offset (-viewSize.left, 0);
	viewSize.offset (leftOrigin - offset, 0);
	updateViewSize (newView, viewSize);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doPushInFromRight (float pos)
{
	CRect viewSize (newView->getViewSize ());
	CCoord rightOrigin = destinationRect.left + destinationRect.getWidth ();
	CCoord offset = viewSize.getWidth () * pos;
	viewSize.offset (-viewSize.left, 0);
	viewSize.offset (rightOrigin - offset, 0);
	updateViewSize (newView, viewSize);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doPushInFromTop (float pos)
{
	CRect viewSize (newView->getViewSize ());
	CCoord topOrigin = destinationRect.top;
	CCoord offset = viewSize.getHeight () * (1.f - pos);
	viewSize.offset (0, -viewSize.top);
	viewSize.offset (0, topOrigin - offset);
	updateViewSize (newView, viewSize);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doPushInFromBottom (float pos)
{
	CRect viewSize (newView->getViewSize ());
	CCoord bottomOrigin = destinationRect.top + destinationRect.getHeight ();
	CCoord offset = viewSize.getHeight () * pos;
	viewSize.offset (0, -viewSize.top);
	viewSize.offset (0, bottomOrigin - offset);
	updateViewSize (newView, viewSize);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doPushInOutFromLeft (float pos)
{
	CRect viewSize (newView->getViewSize ());
	CCoord offset = viewSize.getWidth () * (1.f - pos);
	viewSize.offset (-viewSize.left, 0);
	viewSize.offset (destinationRect.left - offset, 0);
	updateViewSize (newView, viewSize);
	
	offset = viewToRemove->getWidth () * pos;
	viewSize = destinationRect;
	viewSize.offset (offset, 0);
	updateViewSize (viewToRemove, viewSize);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::doPushInOutFromRight (float pos)
{
	CRect viewSize (newView->getViewSize ());
	CCoord offset = viewSize.getWidth () * pos;
	viewSize.offset (-viewSize.left, 0);
	viewSize.offset ((destinationRect.left + destinationRect.getWidth ()) - offset, 0);
	updateViewSize (newView, viewSize);
	
	offset = viewToRemove->getWidth () * pos;
	viewSize = destinationRect;
	viewSize.offset (-offset, 0);
	updateViewSize (viewToRemove, viewSize);
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::animationStart (CView* view, IdStringPtr name)
{
	#if DEBUG
	CViewContainer* parent = viewToRemove->getParentView ()->asViewContainer ();
	vstgui_assert (view == parent);
	#endif
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::animationTick (CView* view, IdStringPtr name, float pos)
{
	switch (style)
	{
		case kAlphaValueFade:
		{
			doAlphaFade (pos);
			break;
		}
		case kPushInFromLeft:
		{
			doPushInFromLeft (pos);
			break;
		}
		case kPushInFromRight:
		{
			doPushInFromRight (pos);
			break;
		}
		case kPushInFromTop:
		{
			doPushInFromTop (pos);
			break;
		}
		case kPushInFromBottom:
		{
			doPushInFromBottom (pos);
			break;
		}
		case kPushInOutFromLeft:
		{
			doPushInOutFromLeft (pos);
			break;
		}
		case kPushInOutFromRight:
		{
			doPushInOutFromRight (pos);
			break;
		}
	}
}

//-----------------------------------------------------------------------------
void ExchangeViewAnimation::animationFinished (CView* view, IdStringPtr name, bool wasCanceled)
{
	animationTick (nullptr, nullptr, 1.f);
	if (auto viewContainer = viewToRemove->getParentView ()->asViewContainer ())
	{
		viewContainer->removeView (viewToRemove);
	}
}

//-----------------------------------------------------------------------------
/** @class ControlValueAnimation
	see @ref page_animation Support */
//-----------------------------------------------------------------------------
ControlValueAnimation::ControlValueAnimation (float endValue, bool forceEndValueOnFinish)
: startValue (0.f)
, endValue (endValue)
, forceEndValueOnFinish (forceEndValueOnFinish)
{
}

//-----------------------------------------------------------------------------
void ControlValueAnimation::animationStart (CView* view, IdStringPtr name)
{
	auto* control = dynamic_cast<CControl*> (view);
	if (control)
		startValue = control->getValue ();
}

//-----------------------------------------------------------------------------
void ControlValueAnimation::animationTick (CView* view, IdStringPtr name, float pos)
{
	auto* control = dynamic_cast<CControl*> (view);
	if (control)
	{
		float value = startValue + (endValue - startValue) * pos;
		control->setValue (value);
		if (control->isDirty ())
			control->invalid ();
	}
}

//-----------------------------------------------------------------------------
void ControlValueAnimation::animationFinished (CView* view, IdStringPtr name, bool wasCanceled)
{
	auto* control = dynamic_cast<CControl*> (view);
	if (control)
	{
		if (!wasCanceled || forceEndValueOnFinish)
			control->setValue (endValue);
	}
}

//------------------------------------------------------------------------
FuncAnimation::FuncAnimation (StartFunc&& start, TickFunc&& tick, FinishedFunc&& finished)
: start (std::move (start)), tick (std::move (tick)), finished (std::move (finished))
{
}

//------------------------------------------------------------------------
void FuncAnimation::animationStart (CView* view, IdStringPtr name) { start (view, name); }

//------------------------------------------------------------------------
void FuncAnimation::animationTick (CView* view, IdStringPtr name, float pos)
{
	tick (view, name, pos);
}

//------------------------------------------------------------------------
void FuncAnimation::animationFinished (CView* view, IdStringPtr name, bool wasCanceled)
{
	finished (view, name, wasCanceled);
}
}} // namespaces
