
require "util"
require "CVector2"
require "CIrpMan"

if ( CBook == nil ) then
	CBook =
	{
	}
end

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

function CBook:Attach( i_oObj )
	CIrpMan:Attach( i_oObj )
	CBook:Bind( i_oObj )
	
	i_oObj:Init()

	return i_oObj
end

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

function CBook:Init()
	CBook.G = self
	
	self.m_oLogoEmitterL = nil
	self.m_oLogoEmitterR = nil
	self.m_iLastLogoAnimTimeElapsed = 10000		-- setting this high means you'll see it shortly after intro anim
	self.m_iEmitterCount = 0
	self.m_bTransitionPlayed = false
	self.m_oItineraryAnim = nil
	self.m_iSparkleCount = 0
	self.m_iFlowerCount = 0
	self.m_iAnimCount = 0
	self.m_iUpdateCount = 0
	self.m_iLastItineraryAnim = 0
	
	self.m_tIntroEmitters = {}
	
	self:SetOnUpdateEnabled( true );

	local oLogo = self:GetLogo()
	
	if ( oLogo ~= nil ) then
		oLogo.m_oBook = self
		function oLogo:OnLeftMouseDown()
			self.m_oBook:RequestLogoAnim( )
		end
	end
	
	if ( IsRetail() == false ) then
		self:AddAsGlobalHandler()
	end
end

if ( IsRetail() == false ) then
	function CBook:OnKeyDown( i_iChar )
		if ( i_iChar == 190 ) then		-- '.'
			log( "RELOAD LUA!" )
			console( "lua.dofile CBook.lua" )
			ISpyPanel.G:Reload()
		elseif ( i_iChar == 188 ) then	-- ','
			log( "RELOAD ART!" )
			console( "library.reloadpackage input" )
			console( "library.reloadpackage ui" )
		end
	end
end	

function CBook:OnUpdate()

	self:UpdateIrps( 1 / 60 )
	
	self.m_iUpdateCount = self.m_iUpdateCount + 1
	
	-- only allow idle logo animations on start
	if ( self.pageLayout.pageRight.itinerary ~= nil ) then
		local oLetterAnim = self.pageLayout.pageRight.introLetterAnim
		if ( oLetterAnim == nil
					or oLetterAnim:GetVisible() == false
					or oLetterAnim:GetFrame() == oLetterAnim:GetNumFrames() - 1 ) then
			self:RequestLogoAnim()
		end
	end
	
	if ( self.pageLayout.pageLeft.winText ~= nil ) then
		self:RequestWinAnim()
	end
end

function CBook:RequestWinAnim()
	if ( self:Lua_CanPlayAnimations() == false ) then
		return
	end	
	
	if ( self.m_iUpdateCount - self.m_iLastItineraryAnim > 100 ) then
		self:RequestItineraryAnim( 
			self.pageLayout.pageLeft, 
			self.pageLayout.pageLeft.winText,
			self.pageLayout.pageRight,
			self.pageLayout.pageRight.winText,
			true
		 )
	end

end

function CBook:RequestLogoAnim()

	if ( self:Lua_CanPlayAnimations() == false ) then
		return
	end	

	local oLogo = self:GetLogo()

--log( "::" .. self.m_iLastLogoAnimTimeElapsed .. ":" .. tostring( oLogo ) .. "," .. self:GetIrpCount() )
	
	if ( oLogo ~= nil ) then
		oLogo.m_oBook = self
		if ( self:Lua_InTransitions() ) then
			if ( self.m_bTransitionPlayed == false ) then
				self.m_bTransitionPlayed = true
				self:PlayLogoAnim( oLogo )
			end
		else
			self.m_bTransitionPlayed = false
		end

		if ( ( self.m_oLogoEmitterL ~= nil 
					or self.m_oLogoEmitterR ~= nil )
				and self:GetIrpCount() == 0 ) then
			self.m_oLogoEmitterL = nil
			self.m_oLogoEmitterR = nil
		end

		if ( self.m_oLogoEmitterL == nil 
				and self.m_oLogoEmitterR == nil ) then
			if ( math.random( -600 + self.m_iLastLogoAnimTimeElapsed, -600 + self.m_iLastLogoAnimTimeElapsed ) > 900 ) then
				self.m_iLastLogoAnimTimeElapsed = 0
				self:PlayLogoAnim( oLogo )
			else
				self.m_iLastLogoAnimTimeElapsed = self.m_iLastLogoAnimTimeElapsed + 1
			end
		end
	end
end
	
function CBook:OnRightMouseDown()
	--if ( IsDebug() ) then
		--self:RequestPageShimmerAnim( true )
		--self:RequestTextHighlightAnim( 4, 1.5 )
		--self:RequestIntroAnim()
		--self:RequestOldIntroAnim()
		--self:RequestWipeAnim()
	--end
end

function CBook:RequestPageShimmerAnim( i_bRight )
	if ( i_bRight ) then
		local oPage = self.pageLayout.pageRight
		if ( oPage ~= nil ) then
			CPageShimmerEffect:Create( self, oPage, oPage._rightPage )
		end
	else
		local oPage = self.pageLayout.pageLeft
		if ( oPage ~= nil ) then
			CPageShimmerEffect:Create( self, oPage, oPage._leftPage )
		end
	end
	
end

function CBook:RequestWipeAnim()
	--CWipeTransitionEffect:Create( self, 10 )
	CGrowTransitionEffect:Create( self, 5 )
end

function CBook:RequestIntroAnim()
	for i = 430, 740, 40 do
		CLetterFlowEffect:Create( self, i, 600, i, -70, 0.1, false, "IntroFlowerAnim" )
	end

	local oLetter = self.pageLayout.pageRight.introLetter

	local oLetterBase = self.pageLayout.pageRight.letterBase
	if ( oLetterBase ~= nil ) then
		oLetterBase:DeleteSelf()
	end
	
	oLetterBase = self.pageLayout.pageRight:CreateInstance( "Letter", "letterBase" )
	
	oLetterBase:ToggleMaskBase( true )
	oLetterBase:SetX( oLetter:GetX() )
	oLetterBase:SetY( oLetter:GetY() )

	local oLetterIrpY = self:CreateIrp( oLetterBase:GetY(),
									oLetterBase:GetY() - oLetterBase:GetHeight(), 
									1.7, 
									CIrpFuncs.mEaseInQuad, 
									0 )

	oLetterIrpY.m_oHookContext = oLetterBase

	oLetterIrpY.mOnChange = function( i_oIrp, i_oContext )
		i_oContext:SetY( i_oIrp:GetVal() )
	end

	oLetterIrpY.mOnComplete = function( i_oIrp, i_oContext )
		i_oContext:DeleteSelf()
	end

	oLetter:ToggleMaskIn( true )
	oLetter:MoveToFront()
	
	local oLetterTop = self.pageLayout.pageRight.letterTop
	
	if ( self.pageLayout.pageRight.letterTop ~= nil ) then
		oLetterTop:DeleteSelf()
	end	
	
	oLetterTop = self.pageLayout.pageRight:CreateInstance( "Letter", "letterTop" )
	oLetterTop:ToggleMaskClear( true )
	oLetterTop:SetVisible( false )
end
	
function CBook:RequestOldIntroAnim()
	-- don't start anim if no envelope, or the start anim hasn't completed yet
	if ( self.pageLayout.pageRight.introLetter == nil
			or self.pageLayout.pageRight.introLetter:GetVisible() == false 
			or self.pageLayout.pageRight.letterBase ~= nil ) then
		return		
	end
	
	local fInitY = 500
	local fDestY = -70
	local fDur = 2
	local fInitXMin = 50
	local fInitXMax = 770
	local fIntervalMin = 10
	local fIntervalMax = 15
	
	local fCurX = fInitXMin
	local fBaseDelay = 1
	local iEmitterInterval = 30
	local iLastEmitter = 0
	
	-- $ jbilas -- necessary since I couldn't get MoveToFront() to work on the flowers vs particle layer
	--	this means a glitterLayer will remain dormant on the root layer, but it shouldn't hurt anything
	if ( root.glitterLayer == nil ) then
		root:CreateEmptyInstance( "glitterLayer" )
	end
	root.glitterLayer:MoveToFront()
	
	local iCount = 0
	
	while fCurX < fInitXMax do
		self.m_iFlowerCount = self.m_iFlowerCount + 1
		local oInst = root:CreateInstance( "IntroFlowerAnim", "introFlowerAnim" .. self.m_iFlowerCount )
		
		local iNumber = tostring( math.random( 1, 4 ) )

		oInst:SetX( fCurX )
		oInst:SetY( fInitY + math.random( -10, 30 ) )-- + iCount * -2 )
		oInst:SetRotation( 3.14 * math.random( 0, 200 ) / 100 )
		local fScale = math.random( 800, 1000 ) / 1000
		oInst:SetScale( 0.55 * fScale, 0.55 * fScale )
		
		iCount = iCount + 1
		
		oInst.m_fRotationConst = math.random( 60, 80 ) / 1000
		if ( math.random( 0, 1 ) == 0 ) then
			oInst.m_fRotationConst = oInst.m_fRotationConst * -1
		end
		
		local fInstDur = fDur + math.random( -10, 10 ) / 100
		local fInstDelay = fBaseDelay + math.random( 1, 30 ) / 100 + ( fCurX - fInitXMin ) / 1000

		
		local oPosIrpY = self:CreateIrp( oInst:GetY(), 
									fDestY, 
										fInstDur, 
										CIrpFuncs.mEaseInQuad, 
										fInstDelay ) 

		oPosIrpY.m_oHookContext = oInst

		
		if ( fCurX > iLastEmitter + iEmitterInterval ) then
			iLastEmitter = fCurX

			root.glitterLayer:CreateParticleEmitter( "flowerGlitter.xml", "flowerGlitter", oInst:GetX(), oInst:GetY() )
			oInst.m_iEmitterId = root.glitterLayer:GetLastEmitterId()
		end

		oPosIrpY.mOnChange = function( i_oIrp, i_oContext )
			i_oContext:SetY( i_oIrp:GetVal() )
		end
		
		-- sway
		local mSwayFunc = function(t, b, c, d ) 
			t = t / d
			local ts =t*t
			local tc =ts*t
			return b+c*(-2.15*tc*ts + 4.65*ts*ts + -1*tc + -0.5*t);
		end

		local oPosIrpX = self:CreateIrp( oInst:GetX(), 
										oInst:GetX() + math.random( 100, 200 ), 
										fInstDur - 0, 
										mSwayFunc, 
										fInstDelay + math.random( 10, 100 ) / 100 )

		oPosIrpX.m_oHookContext = oInst
		
		oPosIrpX.mOnChange = function( i_oIrp, i_oContext )
			i_oContext:SetX( i_oIrp:GetVal() )
			i_oContext:SetPlaying( true )
			--i_oContext:SetRotation( 0 - 1 / math.sin( i_oIrp:GetVal() ) ) -- * i_oContext.m_fRotationConst )
			
			
			if ( i_oContext.m_iEmitterId ~= nil ) then
				root.glitterLayer:MoveParticleEmitterById( i_oContext.m_iEmitterId, 
												i_oContext:GetX(), 
												i_oContext:GetY() )
			
				
			end
		end
		oPosIrpX.mOnComplete = function( i_oIrp, i_oContext )
			i_oContext:DeleteSelf()
		end
		
		fCurX = fCurX + math.random( fIntervalMin, fIntervalMax )
	end
	
	-- $$ jbilas -- this should move in front of emitters .. why doesn't it??
	--for i = 1, table.getn( aFlowers ), 1 do
		-- move in front of emitters
		--aFlowers[ i ]:MoveToFront()
	--end
	
	local oLetter = self.pageLayout.pageRight.introLetter

	local oLetterBase = self.pageLayout.pageRight:CreateEmptyInstance( "letterBase" )
	local oLetterBaseInner = oLetterBase:CreateInstance( "Letter", "base" )
	oLetterBaseInner:SetScale( 2, 2 )
	oLetterBaseInner:SetX( 0 - oLetterBaseInner:GetWidth() / 2 )
	oLetterBaseInner:SetY( 0 - oLetterBaseInner:GetHeight() / 2 )
	
	oLetterBase:ToggleMaskBase( true )
	oLetterBase:SetRotation( -1 / 16 * 3.14 )
	oLetterBase:SetX( oLetter:GetX() + 100 )
	oLetterBase:SetY( oLetter:GetY() - 10 - oLetter:GetHeight() )

	local oLetterIrpY = self:CreateIrp( oLetterBase:GetY(),
									oLetterBase:GetY() + 800, 
									fDur, 
									CIrpFuncs.mEaseInQuad, 
									fBaseDelay + 0.3 )

	oLetterIrpY.m_oHookContext = oLetterBase

	oLetterIrpY.mOnChange = function( i_oIrp, i_oContext )
		i_oContext:SetY( i_oIrp:GetVal() )
	end

	oLetterIrpY.mOnComplete = function( i_oIrp, i_oContext )
		i_oContext:DeleteSelf()
	end

	oLetter:ToggleMaskOut( true )
	oLetter:MoveToFront()

	local oLetterTop = self.pageLayout.pageRight:CreateInstance( "Letter", "letterTop" )
	oLetterTop:ToggleMaskClear( true )
	oLetterTop:SetVisible( false )
end
	
function CBook:RequestTextHighlightAnim( i_fSpeed, i_fDelay )

	self.m_iAnimCount = self.m_iAnimCount + 1
	local oIntroAnim = self:CreateInstance( "LevelIntroTextParticleAnim", "levelIntroParticleAnim" .. self.m_iAnimCount )
	
	-- move it off screen, we may get a frame of extra glitter
	root:CreateParticleEmitter( "levelIntroGlitter.xml",
										"levelIntroGlitter", 
										-100,
										-100 )
	oIntroAnim.m_iParticleLeftId = root:GetLastEmitterId()
	
	root:CreateParticleEmitter( "levelIntroGlitter.xml",
										"levelIntroGlitter", 
										-100,
										-100 )
	oIntroAnim.m_iParticleRightId = root:GetLastEmitterId()
	
	
	local oFrameIrp = self:CreateIrp( 0, 
								oIntroAnim:GetNumFrames() - 1, 
								i_fSpeed, 
								CIrpFuncs.mLinear, 
								i_fDelay )
	oFrameIrp.m_oHookContext = oIntroAnim
	
	function oFrameIrp.mOnChange( i_oIrp, i_oContext )
		i_oContext:SetFrame( math.floor( i_oIrp:GetVal() ) )
		
		root:MoveParticleEmitterById( i_oContext.m_iParticleLeftId,
										i_oContext._markerParticleItemsRemaining:GetX(),
										i_oContext._markerParticleItemsRemaining:GetY() )
		
		if ( i_oContext._markerParticleItemsRemainingSize ~= nil ) then
			root:SetParticleEmitterDimensionsById( i_oContext.m_iParticleLeftId,
													i_oContext._markerParticleItemsRemainingSize:GetX() - i_oContext._markerParticleItemsRemaining:GetX(),
													i_oContext._markerParticleItemsRemainingSize:GetY() - i_oContext._markerParticleItemsRemaining:GetY() )
		else
			root:SetParticleEmitterDimensionsById( i_oContext.m_iParticleLeftId, 5, 5 )		
		end
										
		root:MoveParticleEmitterById( i_oContext.m_iParticleRightId,
										i_oContext._markerParticleTimeRemaining:GetX(),
										i_oContext._markerParticleTimeRemaining:GetY() )

		if ( i_oContext._markerParticleTimeRemainingSize ~= nil ) then
			root:SetParticleEmitterDimensionsById( i_oContext.m_iParticleRightId,
													i_oContext._markerParticleTimeRemainingSize:GetX() - i_oContext._markerParticleTimeRemaining:GetX(),
													i_oContext._markerParticleTimeRemainingSize:GetY() - i_oContext._markerParticleTimeRemaining:GetY() )
		else
			root:SetParticleEmitterDimensionsById( i_oContext.m_iParticleRightId, 5, 5 )
		end
		
	end
end	

function CBook:RequestItineraryAnim( i_oPage, i_oItinerary, i_oItineraryNextPage, i_oItineraryNext, i_bPlayLogoAnimAtEnd )
	
	if ( i_oPage == nil or i_oItinerary == nil ) then
		--log( "!!! RequestItineraryAnim NIL values: " .. tostring( i_oPage ) .. "," .. tostring( i_oItinerary ) )
		return
	end
	
	if ( i_bPlayLogoAnimAtEnd == nil ) then
		i_bPlayLogoAnimAtEnd = false
	end

	self.m_iLastItineraryAnim = self.m_iUpdateCount
	
	local page = i_oPage
	local oItinerary = i_oItinerary
	local symbolName = oItinerary:GetSourceName()
	local speed = 6
	local distance = 650

	if ( symbolName == "BookItineraryLeft" ) then
		speed = 3.6
		distance = 390
	elseif ( symbolName == "BookItinerary" ) then
		speed = 6
		distance = 650
	elseif ( symbolName == "WinTextLeft" ) then
		speed = 6
		distance = 440
	elseif ( symbolName == "WinTextRight" ) then
		speed = 3.6
		distance = 300
	end

	if ( oItinerary ~= nil ) then

		if ( self.m_oItineraryAnim == nil ) then

			local oItineraryBottom = page.bookItineraryMaskBottom
			if ( oItineraryBottom == nil ) then

				oItineraryBottom = page:CreateEmptyInstance( "bookItineraryMaskBottom" )

				local ul = oItineraryBottom:CreateInstance( symbolName, "ul" )
				ul:SetX( oItinerary:GetX() )
				ul:SetY( oItinerary:GetY() )
				oItineraryBottom:ToggleMaskBase( true )
			end

			self.m_oItineraryAnim = page:CreateInstance( "TextMaskGradient", "bookItineraryMask" )
			self.m_oItineraryAnim:SetX( oItinerary:GetX() - 80)
			self.m_oItineraryAnim:SetY( oItinerary:GetY() - 150 )
			self.m_oItineraryAnim:ToggleMaskIn( true )
			self.m_oItineraryAnim:SetScale( 50, 3 )
			self.m_oItineraryAnim:SetOpacity( 0.7 )
			self.m_oItineraryAnim:SetRotation( 3.14 * 23 / 12 )

			
			self.m_oItineraryAnimIrpY = self:CreateIrp( self.m_oItineraryAnim:GetY(), 
														self.m_oItineraryAnim:GetY() + distance, 
														speed, 
														CIrpFuncs.mLinear, 
														0 )
			self.m_oItineraryAnimIrpY.m_oHookContext = self.m_oItineraryAnim
			self.m_oItineraryAnim.m_oBook = self
			self.m_oItineraryAnim.m_oItineraryNextPage = i_oItineraryNextPage
			self.m_oItineraryAnim.m_oItineraryNext = i_oItineraryNext
			self.m_oItineraryAnim.m_bPlayLogoAnimAtEnd = i_bPlayLogoAnimAtEnd

			self.m_oItineraryAnimIrpY.mOnChange = function( i_oIrp, i_oContext )
				i_oContext:SetY( i_oIrp:GetVal() )
				
				if ( i_oContext.m_bPlayLogoAnimAtEnd 
						and i_oIrp.m_fLastPctComplete ~= nil
						and i_oIrp:GetPctComplete() >= 0.7
						and i_oIrp.m_fLastPctComplete < 0.7 ) then
					self:PlayLogoAnim( i_oContext.m_oBook:GetLogo() )
				end
				
				i_oIrp.m_fLastPctComplete = i_oIrp:GetPctComplete()
			end
			self.m_oItineraryAnimIrpY.mOnComplete = function( i_oIrp, i_oContext )

				i_oContext.m_oBook:KillItineraryIrp()
				
				-- glint the right page as well if we currently on the left
				if ( i_oContext.m_oItineraryNextPage ~= nil and i_oContext.m_oItineraryNext ~= nil ) then
					i_oContext.m_oBook:RequestItineraryAnim( i_oContext.m_oItineraryNextPage, 
																i_oContext.m_oItineraryNext )
				end
			end
			
			local oItineraryTop = page.bookItineraryMaskTop
			if ( oItineraryTop == nil ) then
				oItineraryTop = page:CreateInstance( symbolName, "bookItineraryMaskTop" )
				oItineraryTop:SetX( oItinerary:GetX() )
				oItineraryTop:SetY( oItinerary:GetY() )
				oItineraryTop:ToggleMaskClear( true )
				oItineraryTop:SetVisible( false )
			end
			
			--oItinerary:MoveToFront()

			-- do sparkle timings
			for i = 0, 30, 1 do
				local oSparkleMarker = page[ "_sparkle" .. tostring( i ) ]
				if ( oSparkleMarker ~= nil ) then

					local fScaleConst = math.random( 1, 150 ) * 0.010
					
					self.m_iSparkleCount = self.m_iSparkleCount + 1

					local oSparkle = page:CreateInstance( "Sparkle", "sparkle" .. self.m_iSparkleCount )
					oSparkle:SetX( oSparkleMarker:GetX() )
					oSparkle:SetY( oSparkleMarker:GetY() )
					oSparkle:SetOpacity( 0 )
					oSparkle:SetRotation( 3.14 / 4 )
					oSparkle.m_fScaleConst = fScaleConst
					
					local fTiming = ( oSparkleMarker:GetY() + 500 ) / 110
					
					local oIrp = self:CreateIrp( 0, 
												1, 
												3, 
												CIrpFuncs.mBounce,
												fTiming )

					oIrp.m_oHookContext = oSparkle
					if ( math.random( 1, 2 ) == 1 ) then
						oSparkle:SetColor( 0xFFFF00 )
					else
						oSparkle:SetColor( 0xFF0000 )
					end

					oIrp.mOnChange = function( i_oIrp, i_oContext )
						if ( i_oContext ~= nil ) then
							i_oContext:SetOpacity( 1 + i_oIrp:GetVal() * 0 )
							i_oContext:SetScale( i_oIrp:GetVal() * i_oContext.m_fScaleConst * 1.25, i_oIrp:GetVal() * i_oContext.m_fScaleConst * 1.25 )
						end
					end
					oIrp.mOnComplete = function( i_oIrp, i_oContext )
						if ( i_oContext ~= nil ) then
							i_oContext:DeleteSelf()
						end
					end

					self.m_iSparkleCount = self.m_iSparkleCount + 1

					oSparkle = page:CreateInstance( "Sparkle", "sparkle" .. self.m_iSparkleCount )
					oSparkle:SetX( oSparkleMarker:GetX() )
					oSparkle:SetY( oSparkleMarker:GetY() )
					oSparkle:SetOpacity( 0 )
					oSparkle:SetRotation( 3.14 / 4 )
					oSparkle.m_fScaleConst = fScaleConst

					oIrp = self:CreateIrp( 0.3, 
												1, 
												3, 
												CIrpFuncs.mBounce,
												fTiming + 0.05 )

					oIrp.m_oHookContext = oSparkle
					
					oIrp.mOnChange = function( i_oIrp, i_oContext )
						if ( i_oContext ~= nil ) then
							i_oContext:SetOpacity( i_oIrp:GetVal() )
							i_oContext:SetScale( i_oIrp:GetVal() * i_oContext.m_fScaleConst, i_oIrp:GetVal() * i_oContext.m_fScaleConst )
						end
					end
					oIrp.mOnComplete = function( i_oIrp, i_oContext )
						if ( i_oContext ~= nil ) then
							i_oContext:DeleteSelf()
						end
					end
				end
			end
			
			
		elseif ( self:Lua_CanPlayAnimations() == false ) then
			-- kill the anim
			self:KillItineraryIrp()
		end
	else
		-- engine handles cleanup
		self:KillItineraryIrp()
	end
end

function CBook:KillItineraryIrp()
	self:KillIrp( self.m_oItineraryAnimIrpY )

	local pages = { "pageLeft", "pageRight" }
	for i in pairs( pages ) do
		local page = self.pageLayout[ pages[ i ] ]
		
		local oItineraryTop = page.bookItineraryMaskTop
		local oItineraryBottom = page.bookItineraryMaskBottom

		if ( oItineraryTop ~= nil ) then
			oItineraryTop:DeleteSelf()
		end

		if ( oItineraryBottom ~= nil ) then
			oItineraryBottom:DeleteSelf()
		end
	end

	if ( self.m_oItineraryAnim ~= nil ) then
		self.m_oItineraryAnim:DeleteSelf()
		self.m_oItineraryAnim = nil
	end
end

function CBook:OnPageFlip()
	self:KillItineraryIrp()
	self:KillItineraryIrp()
	self:KillAllIrps( true )
end

function CBook:OnTransition()
	self:KillAllIrps( true )
end

function CBook:GetLogo()
	local aPages = { "pageLeft", "pageRight" }
	local oLayout = self.pageLayout

	for iPage in pairs(aPages) do
		local oPage = oLayout[ aPages[ iPage ] ]
		if ( oPage ~= nil ) then
			-- cheesy workaround for lua bug (each logo has unique name)
			for i = 1, 30, 1 do
				if ( oPage[ "logo" .. i ] ~= nil ) then
					return oPage[ "logo" .. i ]
				end
			end
		end
	end
	
	return nil
end

function CBook:OnPageFlipComplete()
	local oLogo = self:GetLogo()
	
	if ( oLogo ~= nil ) then
		oLogo.m_oBook = self
		function oLogo:OnLeftMouseDown()
			log( "CLICK" )
			self.m_oBook:RequestLogoAnim( )
		end
	end
end

function CBook:OnCharDown( i_sChar )
	--[[
	if ( IsDebug()
			and self:GetVisible() ) then
		if ( i_sChar == "R"  ) then
			if ( self.m_oItineraryAnim ~= nil ) then
				self:KillItineraryIrp()
			end
			console( "lua.dofile CBook.lua" )
		elseif ( i_sChar == "D"  ) then
			-- disable log spam
			console( "log.disable ui" )
			console( "log.disable resources" )
			console( "log.disable rendering" )
			log( "Log spam disabled!" )
		elseif ( i_sChar == "I" ) then
			if ( self.m_oItineraryAnim ~= nil ) then
				self:KillItineraryIrp()
			end
			self:RequestItineraryAnim( 
					self.pageLayout.pageLeft, 
					self.pageLayout.pageLeft.itineraryLeft,
					self.pageLayout.pageRight, 
					self.pageLayout.pageRight.itinerary )
		elseif ( i_sChar == "L" ) then
			self:RequestLogoAnim()
		elseif ( i_sChar == "G" ) then
		
			self.pageLayout.pageRight._particleLayerRight:CreateParticleEmitter( "photoGlitter.xml",
												"photoGlitter", 
												0,
												0 )
			self.pageLayout.pageLeft._particleLayerLeft:CreateParticleEmitter( "photoGlitter.xml",
												"photoGlitter", 
												0,
												0 )
		
		end
	end
	--]]
end

function CBook:PlayLogoAnim( i_oLogo )

	self.m_iLastLogoAnimTimeElapsed = 0

	local iMarkerIdx = 12
	local aMarkersRight = {}
	while ( i_oLogo[ "_marker" .. iMarkerIdx ] ~= nil ) do
		table.insert( aMarkersRight, i_oLogo[ "_marker" .. iMarkerIdx ] )
		iMarkerIdx = iMarkerIdx - 1
	end

	iMarkerIdx = 12
	local aMarkersLeft = {}
	while ( i_oLogo[ "_marker" .. iMarkerIdx ] ~= nil ) do
		table.insert( aMarkersLeft, i_oLogo[ "_marker" .. iMarkerIdx ] )
		iMarkerIdx = iMarkerIdx + 1
	end

	-- return to start
	table.insert( aMarkersLeft, i_oLogo[ "_marker0" ] )
	
	self.m_oLogoEmitterL = i_oLogo:CreateInstance( "Marker", "emitterMarker" .. self.m_iEmitterCount )
	self.m_oLogoEmitterR = i_oLogo:CreateInstance( "Marker", "emitterMarker" .. ( self.m_iEmitterCount + 1 ) )
	self.m_iEmitterCount = self.m_iEmitterCount + 2
	
	local aAllMarkers = { aMarkersRight, aMarkersLeft }
	local aAllEmitters = { self.m_oLogoEmitterR, self.m_oLogoEmitterL }
	
	
	-- lower is faster
	local fSpeed = 0.15
	
	for iAllMarkersIdx = 1, table.getn( aAllMarkers ), 1 do
		local aMarkers = aAllMarkers[ iAllMarkersIdx ]
		local oEmitter = aAllEmitters[ iAllMarkersIdx ]

		local oLastMarker = nil
		oEmitter.m_iCount = 0
		
		for iMarkerIdx = 1, table.getn( aMarkers ), 1 do
			local oMarker = aMarkers[ iMarkerIdx ]

			if ( oLastMarker ~= nil ) then
				self:createEmitterIrp( oEmitter, 
										oLastMarker, 
										oMarker, 
										fSpeed, 
										iMarkerIdx * fSpeed, 
										iMarkerIdx == table.getn( aMarkers ) )
			else
				oEmitter:SetX( 0 )
				oEmitter:SetY( 0 )
				oEmitter.m_iPartX = oMarker:GetX()
				oEmitter.m_iPartY = oMarker:GetY()
			end

			oLastMarker = oMarker
		end
	end	
end

function CBook:createEmitterIrp( i_oEmitter, i_oMarkerSrc, i_oMarkerDest, i_fDur, i_fDelay, i_bDoExplode )
	local oIrpX = self:CreateIrp( i_oMarkerSrc:GetX(), i_oMarkerDest:GetX(), i_fDur, CIrpFuncs.mLinear, i_fDelay )
	local oIrpY = self:CreateIrp( i_oMarkerSrc:GetY(), i_oMarkerDest:GetY(), i_fDur, CIrpFuncs.mLinear, i_fDelay )

	oIrpX.m_oHookContext = i_oEmitter
	oIrpY.m_oHookContext = i_oEmitter

	oIrpX.mOnChange = function( i_oIrp, i_oContext )

		i_oContext.m_iPartX = i_oIrp:GetVal()
	end

	oIrpY.mOnChange = function( i_oIrp, i_oContext )

		i_oContext.m_iPartY = i_oIrp:GetVal()
		
		if ( math.mod( i_oContext.m_iCount, 4 ) == 0 ) then
			
			local sType = "Yellow"
			if ( math.mod( i_oContext.m_iCount, 8 ) == 0 ) then
				sType = "White"
			end

			i_oContext:CreateParticleEmitter( "logo" .. sType .. "Flowers.xml",
												"logo" .. sType .. "Flowers", 
												i_oContext.m_iPartX,
												i_oContext.m_iPartY )
			
		end
		i_oContext.m_iCount = i_oContext.m_iCount + 1
	end

	if ( i_bDoExplode ) then
		i_oEmitter.m_oBook = self
		oIrpY.mOnComplete = function ( i_oIrp, i_oContext )
			i_oContext:CreateParticleEmitter( "logoRedFlowers.xml",
												"logoRedFlowers", 
												i_oContext.m_iPartX,
												i_oContext.m_iPartY )
			i_oContext.m_iExpireIn = 360
			function i_oContext:OnUpdate()
				local iOldExpireIn = i_oContext.m_iExpireIn
				i_oContext.m_iExpireIn = i_oContext.m_iExpireIn - 1
				if ( iOldExpireIn > 350 and i_oContext.m_iExpireIn <= 350 ) then

					i_oContext.m_oBook:RequestItineraryAnim( 
							i_oContext.m_oBook.pageLayout.pageLeft, 
							i_oContext.m_oBook.pageLayout.pageLeft.itineraryLeft,
							i_oContext.m_oBook.pageLayout.pageRight, 
							i_oContext.m_oBook.pageLayout.pageRight.itinerary )
				end
				
				if ( i_oContext.m_iExpireIn <= 0 ) then
					i_oContext:DeleteSelf()
				end
			end
		end			
	end

end

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

------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------

if ( CLetterFlowEffect == nil ) then
	CLetterFlowEffect =
	{
		s_iEffectCount = 0
	}
end

function CLetterFlowEffect:Create( i_oParent, i_fSrcX, i_fSrcY, i_fDestX, i_fDestY, i_fScaling, i_bAdjColor, i_sParticleName )
	local oRet = CLetterFlowEffect:Bind( {} )
	oRet:Init( i_oParent, i_fSrcX, i_fSrcY, i_fDestX, i_fDestY, i_fScaling, i_bAdjColor, i_sParticleName )
	return oRet
end

function CLetterFlowEffect:Init( i_oParent, i_fSrcX, i_fSrcY, i_fDestX, i_fDestY, i_fScaling, i_bAdjColor, i_sParticleName )
	self.m_oParent = i_oParent

	for i = 1, 3, 1 do 
		CLetterFlowEffect.s_iEffectCount = CLetterFlowEffect.s_iEffectCount + 1
		
		local oInst = self.m_oParent:CreateInstance( i_sParticleName, "flowEffectParticle" .. CLetterFlowEffect.s_iEffectCount )

		oInst:SetPlaying( true )
		oInst:SetX( i_fSrcX + math.random( -50, 50 ) )
		oInst:SetY( i_fSrcY + math.random( -30, 30 ) )
		oInst:SetOpacity( 0 )
		oInst.m_oParent = self.m_oParent
		
		if ( i_bAdjColor ) then
			local iColor = math.random( 220, 255 ) * 0x10000 --+ math.random( 1, 64 ) * 0x100
			oInst:SetColor( iColor )
		end

		local aQuadLoc = { { -1, -1 }, { -1, 1 }, { 1, -1 }, { 1, 1 } }
		for j = 1, table.getn( aQuadLoc ), 1 do
			local oSubInst = oInst:CreateInstance( i_sParticleName, "sub" .. j )
			oSubInst:SetX( aQuadLoc[ j ][ 1 ] )
			oSubInst:SetY( aQuadLoc[ j ][ 2 ] )
			oSubInst:SetColor( 0 )
			oSubInst:MoveToBack()
		end
		
		oInst.m_oSrc = CVector2:Create( oInst:GetX(), oInst:GetY() )
		oInst.m_oDelta = CVector2:Create( i_fDestX + math.random( -10, 10 ) - oInst:GetX(), 
							i_fDestY + math.random( -10, 10 ) - oInst:GetY() )
							
		oInst.m_oNormal = CVector2:Create( -oInst.m_oDelta.Y, oInst.m_oDelta.X )
		oInst.m_oNormal:NormalizeTo( 50 )
		oInst.m_fRotationStart = math.random( -20, 20 ) * 3.14 / 50
		oInst.m_fRotationSpeed = math.random( 50, 100 ) * 3.14 / 50
		if ( math.random( 0, 1 ) == 0 ) then
			oInst.m_fRotationSpeed = oInst.m_fRotationSpeed * -1
		end
		oInst:SetRotation( oInst.m_fRotationStart )
		
		oInst.m_fSkewVar = 1
		if ( math.random( 0, 1 ) == 0 ) then
			oInst.m_fSkewVar = -1
		end
		oInst.m_fSkewVar = oInst.m_fSkewVar * math.random( 100, 800 ) / 1000

		-- must be done in this order for it to work!
		local fRandWait = math.random( 0, 5 ) * 0.005
		local fAlphaSpeed = 0.3
		local fSpeed = math.max( 0.3, 0.003 * oInst.m_oDelta:GetMagnitude() )

		local oAlphaIrp = self.m_oParent:CreateIrp( 0, 1, fAlphaSpeed, CIrpFuncs.mEaseOutQuad, i * 0.1 + fRandWait )
		local oSkewIrp = self.m_oParent:CreateIrp( 0, 1, fSpeed, CIrpFuncs.mElastic, i * 0.1 + fRandWait )
		local oScaleIrp = self.m_oParent:CreateIrp( 0, i_fScaling, fSpeed, CIrpFuncs.mBounce, i * 0.0001 + fRandWait )
		local oIrp = self.m_oParent:CreateIrp( 0, 1, fSpeed, CIrpFuncs.mLinear, i * 0.1 + fRandWait )

		self.m_oParent:CreateParticleEmitter( "heartGlitter.xml", "heartGlitter", oInst:GetX(), oInst:GetY() );
		oInst.m_iEmitterId = self.m_oParent:GetLastEmitterId()

		oIrp.m_oHookContext = oInst

		oIrp.mOnChange = function( i_oIrp, i_oContext )

			i_oContext:SetX( i_oContext.m_oSrc.X + i_oContext.m_oDelta.X * i_oIrp:GetVal() )
			i_oContext:SetY( i_oContext.m_oSrc.Y + i_oContext.m_oDelta.Y * i_oIrp:GetVal() )
			i_oContext:SetRotation( i_oContext.m_fRotationStart + oInst.m_fRotationSpeed * i_oIrp:GetVal() )
		end

		oAlphaIrp.m_oHookContext = oInst

		oAlphaIrp.mOnChange = function( i_oIrp, i_oContext )
			i_oContext:SetOpacity( i_oIrp:GetVal() )
		end

		oScaleIrp.m_oHookContext = oInst

		oScaleIrp.mOnChange = function( i_oIrp, i_oContext )
			i_oContext:SetScale( 0.5 + i_oIrp:GetVal() * 2.5, 0.5 + i_oIrp:GetVal() * 2.5 )
		end

		oSkewIrp.m_oHookContext = oInst

		oSkewIrp.mOnChange = function( i_oIrp, i_oContext )

			i_oContext:SetX( i_oContext:GetX() + i_oContext.m_oNormal.X * i_oIrp:GetVal() * i_oContext.m_fSkewVar )
			i_oContext:SetY( i_oContext:GetY() + i_oContext.m_oNormal.Y * i_oIrp:GetVal() * i_oContext.m_fSkewVar )

			
			i_oContext.m_oParent:MoveParticleEmitterById( i_oContext.m_iEmitterId,
															i_oContext:GetX(),
															i_oContext:GetY() )
			
		end

		oSkewIrp.mOnComplete = function( i_oIrp, i_oContext )

			i_oContext:DeleteSelf()
		end
	end
end


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

------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------

if ( CWipeTransitionEffect == nil ) then
	CWipeTransitionEffect =
	{
		s_iEffectCount = 0
	}
end

function CWipeTransitionEffect:Create( i_oParent, i_fInterval )
	local oRet = CWipeTransitionEffect:Bind( {} )
	oRet:Init( i_oParent, i_fInterval )
	return oRet
end

function CWipeTransitionEffect:Init( i_oParent, i_fInterval )
	self.m_oParent = i_oParent
	self.m_fInterval = i_fInterval
	self.m_aEmitters = {}
	
	local oPosIrp = self.m_oParent:CreateIrp(	
		0 - self.m_fInterval,
		1600, 
		1.7, 
		CIrpFuncs.mLinear, 
		0 
	)

	oPosIrp.m_oHookContext = self

	oPosIrp.mOnChange = function( i_oIrp, i_oContext )
		i_oContext:UpdateToX( i_oIrp:GetVal() )
	end
	
end

function CWipeTransitionEffect:UpdateToX( i_fVal )
	-- use slope function : y = 4x / 3
	local iNextParticleEmitter = 1
	for iX = 0 - self.m_fInterval, i_fVal, self.m_fInterval do
		local yPos = -4 * iX / 3 + i_fVal
		
		if ( iX >= 0 - self.m_fInterval
				and iX <= 800 
				and yPos >= 0 - self.m_fInterval
				and yPos <= 600 ) then
		
--log( iX .. "," .. yPos )		
			-- particle here
			if ( iNextParticleEmitter >= table.getn( self.m_aEmitters ) ) then
				local oEmitter = self.m_oParent:CreateParticleEmitter( "heartTransitionGlitter.xml", "heartTransitionGlitter", iX, yPos )
				table.insert( self.m_aEmitters, self.m_oParent:GetLastEmitterId() )
			else
				self.m_oParent:MoveParticleEmitterById( self.m_aEmitters[ iNextParticleEmitter ], 
														iX, 
														yPos )
			end
			
			iNextParticleEmitter = iNextParticleEmitter + 1
		end	
	end
	--log( ":::" .. iNextParticleEmitter .. "/" .. table.getn( self.m_aEmitters ))
	for iEmitter = iNextParticleEmitter, table.getn( self.m_aEmitters ), 1 do
	--log( "KILL" )
		self.m_oParent:MoveParticleEmitterById( self.m_aEmitters[ iNextParticleEmitter ], 
												0, 
												0 )
		self.m_oParent:KillParticleEmitterById( self.m_aEmitters[ iEmitter ] )
	end

	for iEmitter = iNextParticleEmitter, table.getn( self.m_aEmitters ), 1 do
		table.remove( self.m_aEmitters, iEmitter )
	end
	

	--log( "UPDATE" .. i_fVal .. "," .. iNextParticleEmitter )
	
end

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

------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------

if ( CGrowTransitionEffect == nil ) then
	CGrowTransitionEffect =
	{
		s_iEffectCount = 0
	}
end

function CGrowTransitionEffect:Create( i_oParent, i_fDur )
	local oRet = CGrowTransitionEffect:Bind( {} )
	oRet:Init( i_oParent, i_fDur )
	return oRet
end

function CGrowTransitionEffect:Init( i_oParent, i_fDur )
	self.m_oParent = i_oParent
	
	if ( self.m_oParent.transitionHeart ~= nil ) then
		self.m_oParent:KillAllIrps()
		self.m_oParent.transitionHeart:DeleteSelf()
	end
	
	local oHeart = self.m_oParent:CreateInstance( "SmallHeart", "transitionHeart" )
	oHeart:SetX( 585 )
	oHeart:SetY( 370 )
	
	local oScaleIrp = self.m_oParent:CreateIrp(	
		0.5,
		35, 
		i_fDur, 
		CIrpFuncs.mLinear, 
		0 
	)

	oScaleIrp.m_oHookContext = oHeart
	oHeart.m_iOrigX = oHeart:GetX()
	oHeart.m_iOrigY = oHeart:GetY()
	
	oHeart.m_iDeltaX = oHeart.m_iOrigX - 400
	oHeart.m_iDeltaY = oHeart.m_iOrigY - 350
	
	oScaleIrp.mOnChange = function( i_oIrp, i_oContext )
		i_oContext:SetScale( i_oIrp:GetVal(), i_oIrp:GetVal() )
		i_oContext:SetOpacity( math.min( 1, 0.8 + i_oIrp:GetPctComplete() ) )
		i_oContext:SetX( i_oContext.m_iOrigX - math.floor( oHeart.m_iDeltaX * i_oIrp:GetPctComplete() ) )
		i_oContext:SetY( i_oContext.m_iOrigY - math.floor( oHeart.m_iDeltaY * i_oIrp:GetPctComplete() ) )
	end

	oScaleIrp.mOnComplete = function( i_oIrp, i_oContext )
		i_oContext:DeleteSelf()
	end
	
end


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

------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------

if ( CPageShimmerEffect == nil ) then
	CPageShimmerEffect =
	{
		s_iEffectCount = 0
	}
end

function CPageShimmerEffect:Create( i_oIrpMan, i_oParent, i_oPage )
	local oRet = CPageShimmerEffect:Bind( {} )
	oRet:Init( i_oIrpMan, i_oParent, i_oPage )
	return oRet
end

function CPageShimmerEffect:Init( i_oIrpMan, i_oParent, i_oPage )
	self.m_oParent = i_oParent
	if ( self.m_oParent._pageEffectLayer.pageMaskBase == nil ) then
		local oBase = self.m_oParent._pageEffectLayer:CreateInstance( i_oPage:GetSourceName(), "pageMaskBase" )
		oBase:ToggleMaskBase( true )
		oBase:SetX( i_oPage:GetX() )
		oBase:SetY( i_oPage:GetY() )
	end
	
	CPageShimmerEffect.s_iEffectCount = CPageShimmerEffect.s_iEffectCount + 1
	self.m_oEffect = self.m_oParent._pageEffectLayer:CreateEmptyInstance( "effect" .. CPageShimmerEffect.s_iEffectCount )
		
	if ( self.m_oParent._pageEffectLayer.pageMaskClear == nil ) then
		local oClear = self.m_oParent._pageEffectLayer:CreateInstance( i_oPage:GetSourceName(), "pageMaskClear" )
		oClear:ToggleMaskClear( true )
		oClear:SetVisible( false )
	end
	
	self.m_oParent._pageEffectLayer.pageMaskClear:MoveToFront()
	self.m_aEffectIds = {}
	self.m_aEffectStartCoords = {}
	
	local fRise = -6
	local fRun = 10
	self.m_iCount = 35
	
	for i = 9, self.m_iCount, 1 do
		local aCoord = { i_oPage:GetX() + fRun * i, i_oPage:GetY() + fRise * i }
		self.m_oEffect:CreateParticleEmitter( "maskedPageShimmer.xml", "maskedPageShimmer", aCoord[ 1 ], aCoord[ 2 ] )
		table.insert( self.m_aEffectIds, self.m_oEffect:GetLastEmitterId() )
		table.insert( self.m_aEffectStartCoords, aCoord )
	end
	
	self.m_oPosIrp = i_oIrpMan:CreateIrp(
		-150,
		i_oPage:GetHeight() - fRise * self.m_iCount + 500,
		3,
		CIrpFuncs.mLinear
	)
	
	self.m_oPosIrp.m_oHookContext = self
	
	self.m_oPosIrp.mOnChange = function( i_oIrp, i_oContext )
		for i = 1, table.getn( self.m_aEffectIds ), 1 do
			i_oContext.m_oEffect:MoveParticleEmitterById( 
				self.m_aEffectIds[ i ], 
				self.m_aEffectStartCoords[ i ][ 1 ], 
				self.m_aEffectStartCoords[ i ][ 2 ] + i_oIrp:GetVal() )
		end
	end
end


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

------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------



if ( root.Book.Init == nil ) then
	CBook:Attach( root.Book )
else
	CBook:Bind( root.Book )
end

