local E, L, V, P, G = unpack(select(2, ...))

local _G = _G
local random, next, unpack, strsub = random, next, unpack, strsub

E.AnimShake = {{-9, 7, -7, 12}, {-5, 9, -9, 5}, {-5, 7, -7, 5}, {-9, 9, -9, 9}, {-5, 7, -7, 5}, {-9, 7, -9, 5}}
E.AnimShakeH = {-5, 5, -2, 5, -2, 5}

function E:FlashLoopFinished(requested)
	if not requested then self:Play() end
end

function E:RandomAnimShake(index)
	local s = E.AnimShake[index]
	return random(s[1], s[2]), random(s[3], s[4])
end

function E:SetUpAnimGroup(obj, Type, ...)
	if not Type then Type = "Flash" end

	if strsub(Type, 1, 5) == "Flash" then
		obj.anim = obj:CreateAnimationGroup("Flash")
		obj.anim.fadein = obj.anim:CreateAnimation("ALPHA", "FadeIn")
		obj.anim.fadein:SetChange(1)
		obj.anim.fadein:SetOrder(2)

		obj.anim.fadeout = obj.anim:CreateAnimation("ALPHA", "FadeOut")
		obj.anim.fadeout:SetChange(-1)
		obj.anim.fadeout:SetOrder(1)

		if Type == "FlashLoop" then
			obj.anim:SetScript("OnFinished", E.FlashLoopFinished)
		end
	elseif strsub(Type, 1, 5) == "Shake" then
		local shake = obj:CreateAnimationGroup(Type)
		shake:SetLooping("REPEAT")
		shake.path = shake:CreateAnimation("Path")

		if Type == "Shake" then
			shake.path:SetDuration(0.7)
			obj.shake = shake
		elseif Type == "ShakeH" then
			shake.path:SetDuration(2)
			obj.shakeh = shake
		end

		for i = 1, 6 do
			shake.path[i] = shake.path:CreateControlPoint()

			if Type == "Shake" then
				shake.path[i]:SetOffset(E:RandomAnimShake(i))
			else
				shake.path[i]:SetOffset(E.AnimShakeH[i], 0)
			end
		end
		shake.path[1]:SetOrder(1)
		shake.path[2]:SetOrder(2)
		shake.path[3]:SetOrder(3)
		shake.path[4]:SetOrder(4)
		shake.path[5]:SetOrder(5)
		shake.path[6]:SetOrder(6)
	elseif Type == "Elastic" then
		local width, height, duration, loop = ...
		obj.elastic = _G.CreateAnimationGroup(obj)

		for i = 1, 4 do
			local anim  = obj.elastic:CreateAnimation(i < 3 and "width" or "height")
			anim:SetChange((i == 1 and width * 0.45) or (i == 2 and width) or (i == 3 and height * 0.45) or height)
			anim:SetEasing("inout-elastic")
			anim:SetDuration(duration)
			obj.elastic[i] = anim
		end

		obj.elastic[1]:SetScript("OnFinished", function(anim) anim:Stop() obj.elastic[2]:Play() end)
		obj.elastic[3]:SetScript("OnFinished", function(anim) anim:Stop() obj.elastic[4]:Play() end)
		obj.elastic[2]:SetScript("OnFinished", function(anim) anim:Stop() if loop then obj.elastic[1]:Play() end end)
		obj.elastic[4]:SetScript("OnFinished", function(anim) anim:Stop() if loop then obj.elastic[3]:Play() end end)
	elseif Type == "Number" then
		local endingNumber, duration = ...
		obj.NumberAnimGroup = _G.CreateAnimationGroup(obj)
		obj.NumberAnim = obj.NumberAnimGroup:CreateAnimation("number")
		obj.NumberAnim:SetChange(endingNumber)
		obj.NumberAnim:SetEasing("in-circular")
		obj.NumberAnim:SetDuration(duration)
	else
		local x, y, duration, customName = ...
		if not customName then customName = "anim" end

		local anim = obj:CreateAnimationGroup("Move_In")
		obj[customName] = anim

		anim.in1 = anim:CreateAnimation("Translation")
		anim.in1:SetDuration(0)
		anim.in1:SetOrder(1)
		anim.in1:SetOffset(E:Scale(x), E:Scale(y))

		anim.in2 = anim:CreateAnimation("Translation")
		anim.in2:SetDuration(duration)
		anim.in2:SetOrder(2)
		anim.in2:SetSmoothing("OUT")
		anim.in2:SetOffset(E:Scale(-x), E:Scale(-y))

		anim.out1 = obj:CreateAnimationGroup("Move_Out")
		anim.out1:SetScript("OnFinished", function() obj:Hide() end)

		anim.out2 = anim.out1:CreateAnimation("Translation")
		anim.out2:SetDuration(duration)
		anim.out2:SetOrder(1)
		anim.out2:SetSmoothing("IN")
		anim.out2:SetOffset(E:Scale(x), E:Scale(y))
	end
end

function E:Elasticize(obj, width, height)
	if not obj.elastic then
		if not width then width = obj:GetWidth() end
		if not height then height = obj:GetHeight() end
		E:SetUpAnimGroup(obj, "Elastic", width, height, 2, false)
	end

	obj.elastic[1]:Play()
	obj.elastic[3]:Play()
end

function E:StopElasticize(obj)
	if obj.elastic then
		obj.elastic[1]:Stop(true)
		obj.elastic[3]:Stop(true)
	end
end

function E:Shake(obj)
	if not obj.shake then
		E:SetUpAnimGroup(obj, "Shake")
	end

	obj.shake:Play()
end

function E:StopShake(obj)
	if obj.shake then
		obj.shake:Finish()
	end
end

function E:ShakeHorizontal(obj)
	if not obj.shakeh then
		E:SetUpAnimGroup(obj, "ShakeH")
	end

	obj.shakeh:Play()
end

function E:StopShakeHorizontal(obj)
	if obj.shakeh then
		obj.shakeh:Finish()
	end
end

function E:Flash(obj, duration, loop)
	if not obj.anim then
		E:SetUpAnimGroup(obj, loop and "FlashLoop" or "Flash")
	end

	if not obj.anim:IsPlaying() then
		obj.anim.fadein:SetDuration(duration)
		obj.anim.fadeout:SetDuration(duration)
		obj.anim:Play()
	end
end

function E:StopFlash(obj)
	if obj.anim and obj.anim:IsPlaying() then
		obj.anim:Stop()
	end
end

function E:SlideIn(obj, customName)
	if not customName then customName = "anim" end
	if not obj[customName] then return end

	obj[customName].out1:Stop()
	obj[customName]:Play()
	obj:Show()
end

function E:SlideOut(obj, customName)
	if not customName then customName = "anim" end
	if not obj[customName] then return end

	obj[customName]:Finish()
	obj[customName]:Stop()
	obj[customName].out1:Play()
end

local FADEFRAMES, FADEMANAGER = {}, CreateFrame("FRAME")
FADEMANAGER.delay = 0.025

function E:UIFrameFade_OnUpdate(elapsed)
	FADEMANAGER.timer = (FADEMANAGER.timer or 0) + elapsed

	if FADEMANAGER.timer > FADEMANAGER.delay then
		FADEMANAGER.timer = 0

		for frame, info in next, FADEFRAMES do
			-- Reset the timer if there isn't one, this is just an internal counter
			if frame:IsVisible() then
				info.fadeTimer = (info.fadeTimer or 0) + (elapsed + FADEMANAGER.delay)
			else
				info.fadeTimer = info.timeToFade + 1
			end

			-- If the fadeTimer is less then the desired fade time then set the alpha otherwise hold the fade state, call the finished function, or just finish the fade
			if info.fadeTimer < info.timeToFade then
				if info.mode == "IN" then
					frame:SetAlpha((info.fadeTimer / info.timeToFade) * info.diffAlpha + info.startAlpha)
				else
					frame:SetAlpha(((info.timeToFade - info.fadeTimer) / info.timeToFade) * info.diffAlpha + info.endAlpha)
				end
			else
				frame:SetAlpha(info.endAlpha)

				-- If there is a fadeHoldTime then wait until its passed to continue on
				if info.fadeHoldTime and info.fadeHoldTime > 0 then
					info.fadeHoldTime = info.fadeHoldTime - elapsed
				else
					-- Complete the fade and call the finished function if there is one
					E:UIFrameFadeRemoveFrame(frame)

					if info.finishedFunc then
						if info.finishedArgs then
							info.finishedFunc(unpack(info.finishedArgs))
						else -- optional method
							info.finishedFunc(info.finishedArg1, info.finishedArg2, info.finishedArg3, info.finishedArg4, info.finishedArg5)
						end

						if not info.finishedFuncKeep then
							info.finishedFunc = nil
						end
					end
				end
			end
		end

		if not next(FADEFRAMES) then
			FADEMANAGER:SetScript("OnUpdate", nil)
		end
	end
end

-- Generic fade function
function E:UIFrameFade(frame, info)
	if not frame then return end

	frame.fadeInfo = info

	if not info.mode then
		info.mode = "IN"
	end

	if info.mode == "IN" then
		if not info.startAlpha then info.startAlpha = 0 end
		if not info.endAlpha then info.endAlpha = 1 end
		if not info.diffAlpha then info.diffAlpha = info.endAlpha - info.startAlpha end
	else
		if not info.startAlpha then info.startAlpha = 1 end
		if not info.endAlpha then info.endAlpha = 0 end
		if not info.diffAlpha then info.diffAlpha = info.startAlpha - info.endAlpha end
	end

	frame:SetAlpha(info.startAlpha)

	if not frame:IsProtected() then
		frame:Show()
	end

	if not FADEFRAMES[frame] then
		FADEFRAMES[frame] = info -- read below comment
		FADEMANAGER:SetScript("OnUpdate", E.UIFrameFade_OnUpdate)
	else
		FADEFRAMES[frame] = info -- keep these both, we need this updated in the event its changed to another ref from a plugin or sth, don't move it up!
	end
end

-- Convenience function to do a simple fade in
function E:UIFrameFadeIn(frame, timeToFade, startAlpha, endAlpha)
	if not frame then return end

	if frame.FadeObject then
		frame.FadeObject.fadeTimer = nil
	else
		frame.FadeObject = {}
	end

	frame.FadeObject.mode = "IN"
	frame.FadeObject.timeToFade = timeToFade
	frame.FadeObject.startAlpha = startAlpha
	frame.FadeObject.endAlpha = endAlpha
	frame.FadeObject.diffAlpha = endAlpha - startAlpha

	E:UIFrameFade(frame, frame.FadeObject)
end

-- Convenience function to do a simple fade out
function E:UIFrameFadeOut(frame, timeToFade, startAlpha, endAlpha)
	if not frame then return end

	if frame.FadeObject then
		frame.FadeObject.fadeTimer = nil
	else
		frame.FadeObject = {}
	end

	frame.FadeObject.mode = "OUT"
	frame.FadeObject.timeToFade = timeToFade
	frame.FadeObject.startAlpha = startAlpha
	frame.FadeObject.endAlpha = endAlpha
	frame.FadeObject.diffAlpha = startAlpha - endAlpha

	E:UIFrameFade(frame, frame.FadeObject)
end

function E:UIFrameFadeRemoveFrame(frame)
	if frame and FADEFRAMES[frame] then
		if frame.FadeObject then
			frame.FadeObject.fadeTimer = nil
		end

		FADEFRAMES[frame] = nil
	end
end