
require "util"

-- [I]nte[RP]olation object 
--	a generic object that handles linear/non-linear interpolation of values

-- sample usage 
--[[
	-- ease an object from 1 to 100 in 3 seconds following a 1.5 second delay:

	self.m_oIrp = CIrp:Create( 1, 100, 3, CIrpFuncs.mEaseOutQuint, 1.5 )
	self.m_oIrp.m_oHookContext = self
	
	self.m_oIrp.mOnChange = function( i_oIrp, i_oContext )
		i_oContext.m_oTile:SetX( i_oIrp:GetVal() )
	end

	self.m_oIrp.mOnComplete = function( i_oIrp, i_oContext )
		log( "COMPLETE" )
	end

	function self:OnUpdate()
		self.m_oIrp:Update( 1 / 60 )
	end
--]]

-- $$ jbilas -- TODO:
--	add var support .. primarily "strength" and possibly random "variance"
--	add yoyo() for even more fun!

-- most of these are based on Robert Penner's sample easing equations (http://www.robertpenner.com/easing/)
-- see http://hosted.zeh.com.br/mctween/animationtypes.html for samples based on Penner's work
-- generate exciting, new ones with http://timotheegroleau.com/Flash/experiments/easing_function_generator.htm
-- (funcs covered under BSD license, http://www.robertpenner.com/easing_terms_of_use.html)

CIrpFuncs =
{
	-- all IRP'ing functions should follow this delegate pattern
	mLinear =	
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			return i_fDeltaV * i_fStartT / i_fTotalT + i_fInitV
		end,

	mBounce =	
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
		    return i_fDeltaV * math.sin( ( i_fStartT / i_fTotalT ) * 3.14 ) + i_fInitV
		end,

	mElasticEaseOut =
		function( t, b, c, d, a, p ) 
			if ( t == 0 ) then 
				return b
			end
			
			t = t / d
			if ( t == 1 ) then
				return  b + c
			end
			
			if ( p == 0 ) then
				p = d * 0.3
			end

			local s
			if ( a == 0 or a < Math.abs( c ) )  then
				a = c 
				s = p / 4
			else 
				s = p / ( 2 * 3.14159 ) * math.asin( c / a )
			end
			
			return ( a * math.pow( 2, -10 * t ) * math.sin( ( t * d - s ) * ( 2 * 3.14159 ) / p ) + c + b )
		end,
	
	
	mElastic =	
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
		    return i_fDeltaV * math.sin( ( i_fStartT / i_fTotalT ) * 3.14 * 2 ) + i_fInitV
		end,

	mEaseElastic =	
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
		    return i_fDeltaV * math.sin( ( i_fStartT / i_fTotalT ) * 3.14 * 2 ) + i_fInitV
		end,
		
	mEaseInQuad =	
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			i_fStartT = i_fStartT / i_fTotalT
			return i_fDeltaV * i_fStartT * i_fStartT + i_fInitV
		end,
				
	mEaseOutQuad =	
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			i_fStartT = i_fStartT / i_fTotalT
			return -i_fDeltaV * i_fStartT * ( i_fStartT - 2 ) + i_fInitV
		end,

	mEaseInOutQuad = 
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			i_fStartT = i_fStartT / ( i_fTotalT / 2 )
			if ( i_fStartT < 1 ) then
				return i_fDeltaV / 2 * i_fStartT * i_fStartT + i_fInitV
			end
			
			i_fStartT = i_fStartT - 1
			return -i_fDeltaV / 2 * ( i_fStartT * ( i_fStartT - 2 ) - 1 ) + i_fInitV
		end,

	mEaseInQuint =
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			i_fStartT = i_fStartT / i_fTotalT
			return i_fDeltaV * i_fStartT * i_fStartT * i_fStartT * i_fStartT * i_fStartT + i_fInitV
		end,

	mEaseOutQuint =
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			i_fStartT = i_fStartT / i_fTotalT - 1
			return i_fDeltaV * ( i_fStartT * i_fStartT * i_fStartT * i_fStartT * i_fStartT + 1 ) + i_fInitV
		end,
		
	mEaseInOutQuint =
		function( i_fStartT, i_fInitV, i_fDeltaV, i_fTotalT )
			i_fStartT = i_fStartT / ( i_fTotalT / 2 )
			if ( i_fStartT < 1 ) then
				return i_fDeltaV / 2 * i_fStartT * i_fStartT * i_fStartT * i_fStartT * i_fStartT + i_fInitV
			end
			
			i_fStartT = i_fStartT - 2
			return i_fDeltaV / 2 * ( i_fStartT * i_fStartT * i_fStartT * i_fStartT * i_fStartT + 2 ) + i_fInitV
		end,
}

CIrp =
{
}

----------------------------------------------------------------------------------------
-- CREATE/ATTACH
----------------------------------------------------------------------------------------

-- i_fBegin			: start (initial) value 
-- i_fFinish		: end (target) value 
-- i_fDuration		: time span for tween (doesn't include delay, if any)
-- iOpt_mIrpFunc	: function to use in IRP (nil assumes CIrpFuncs.mLinear with no easing, see this func for sample implementation)
-- iOpt_fDelay		: delay until IRP begins

function CIrp:Create( i_fValStart, i_fValFinish, i_fTimeDuration, iOpt_mIrpFunc, iOpt_fDelay )
	local oRet = CIrp:Bind( {} )
	
	oRet:Init( i_fValStart, i_fValFinish, i_fTimeDuration, iOpt_mIrpFunc, iOpt_fDelay )

	return oRet
end

----------------------------------------------------------------------------------------
-- PUBLIC
----------------------------------------------------------------------------------------

function CIrp:Init( i_fValStart, i_fValFinish, i_fTimeDuration, iOpt_mIrpFunc, iOpt_fDelay )

	self.m_bPlaying = true
	self.m_fLooping = false
	self.m_fTime = 0
	self.m_fDelay = iOpt_fDelay
	self.m_fVal = 0
	self.m_fDuration = i_fTimeDuration
	self.m_fValStart = i_fValStart
	self.m_fValDelta = 0
	
	self.m_fExtra1 = 0
	self.m_fExtra2 = 0

	-- hooks
	self.m_oHookContext = self
	self.mOnDelayComplete = nil
	self.mOnChange = nil
	self.mOnComplete = nil
	self.mCalcIrp = iOpt_mIrpFunc
	
	if ( self.mCalcIrp == nil ) then
		self.mCalcIrp = CIrpFuncs.mLinear
	end

    if ( self.m_fDelay == nil ) then
		self.m_fDelay = 0
	end

	if ( self.m_fDuration == null
			or self.m_fDuration <= 0 ) then

	    -- avoid div by zero
	    self.m_fDuration = 0.001
	end

	self:SetVal( self.m_fValStart )
	self:SetEndVal( i_fValFinish )
	self:Start()
end

function CIrp:Start()
	self:Rewind()
	self.m_bPlaying = true
end

function CIrp:IsDone()
	return self.m_fDelay + self.m_fTime >= self.m_fDuration
end

function CIrp:Stop()
	self.m_bPlaying = false
end

function CIrp:Update( i_fDeltaT )
    if ( self.m_bPlaying == false ) then
        return
    end

    self:SetTime( self.m_fTime + i_fDeltaT )
end

function CIrp:SetEndVal ( i_fVal )
	self.m_fValDelta = i_fVal - self.m_fValStart;
end

function CIrp:Finish()
    self:SetTime( self.m_fDelay + self.m_fDuration )
end


-- i_fTime is time from start
function CIrp:SetTime( i_fTime ) 

    -- $$ jbilas -- self.m_fDelay is a little hacky, probably breaks some functionality .. update to fully support
    
    -- delay is tacked onto tween, just decrement delay until it's time to start
    if ( self.m_fDelay > 0 ) then

        if ( self.m_fDelay >= i_fTime ) then

            -- decrement delay and break
    	    self.m_fDelay = self.m_fDelay - i_fTime
    	    
    	    return

        else

            -- decrement the time elapsed, but still simulate
            i_fTime = i_fTime - self.m_fDelay
            self.m_fDelay = 0
        end
        
        if ( self.m_fDelay == 0 
				and self.mOnDelayComplete ~= nil ) then
			self.mOnDelayComplete( self, self.m_oHookContext )
        end
	end

	if ( i_fTime >= self.m_fDuration ) then

		if ( self.m_fLooping ) then

			self:Rewind( i_fTime - self.m_fDuration )
			self:updateVal()

		else 

            self.m_fTime = self.m_fDuration
            self:updateVal()
			self.m_bPlaying = false

			if ( self.mOnComplete ~= nil ) then

				self.mOnComplete( self, self.m_oHookContext )
			end
		end

	elseif ( i_fTime < 0 ) then

		self:Rewind()
		self:updateVal()

	else 

		self.m_fTime = i_fTime
		self:updateVal()

	end
end

function CIrp:SetVal( i_fVal )

	local fDelta = i_fVal - self.m_fVal
	self.m_fVal = i_fVal
	
	if ( self.mOnChange ~= nil ) then
		self.mOnChange( self, self.m_oHookContext, fDelta )
	end
end

function CIrp:GetPctComplete()
	return self.m_fTime / self.m_fDuration
end


function CIrp:GetVal( iOpt_fTime ) 
	if ( iOpt_fTime == nil ) then

	    iOpt_fTime = self.m_fTime
	end
		
	if ( iOpt_fTime > self.m_fDuration ) then

	    -- just return total
	    return self.m_fValStart + self.m_fValDelta

	else

		return self.mCalcIrp( iOpt_fTime, self.m_fValStart, self.m_fValDelta, self.m_fDuration, self.m_fExtra1, self.m_fExtra2 )
	end
end

function CIrp:Rewind( i_fTime )

	if ( i_fTime ~= nil ) then
		self.m_fTime = i_fTime
	else
		self.m_fTime = 0
	end
	
	self:updateVal()
end

function CIrp:Bind( i_oObj )
	BindObj( CIrp, i_oObj )
	return i_oObj
end


----------------------------------------------------------------------------------------
-- PRIVATE
----------------------------------------------------------------------------------------

function CIrp:updateVal()
	self:SetVal( self:GetVal() )
end


