--- Actor class sugar and helpers
-- @module 02_Actor

-- Convenience aliases:
left = "HorizAlign_Left"
center = "HorizAlign_Center"
right = "HorizAlign_Right"
top = "VertAlign_Top"
middle = "VertAlign_Middle"
bottom = "VertAlign_Bottom"

function Actor:GetSibling(n)
	return self:GetParent():GetChild(n)
end

function Actor:ease(t, fEase)
	-- Optimizations:
	-- fEase = -100 is equivalent to TweenType_Accelerate.
	if fEase == -100 then
		self:accelerate(t)
		return self
	end

	-- fEase = 0 is equivalent to TweenType_Linear.
	if fEase == 0 then
		self:linear(t)
		return self
	end

	-- fEase = +100 is equivalent to TweenType_Decelerate.
	if fEase == 100 then
		self:decelerate(t)
		return self
	end

	self:tween(
		t,
		"TweenType_Bezier",
		{
			0,
			scale(fEase, -100, 100, 0 / 3, 2 / 3),
			scale(fEase, -100, 100, 1 / 3, 3 / 3),
			1
		}
	)
	return self
end
-- Notes On Beziers --
-- They can be 1D ( Quadratic ) or 2D ( Bezier )
-- 1D:
--	XA XB YC YD
-- 2D:
--  XA XB XC XD YA YB YC YD
-- In 1D Quads, XA XB are beginning time and size, YC YD are ending time and size
-- In 2D Quads, X
local BounceBeginBezier = {
	0,
	0,
	0.42,
	-0.42,
	2 / 3,
	0.3,
	1,
	1
}
function Actor:bouncebegin(t)
	self:tween(t, "TweenType_Bezier", BounceBeginBezier)
	return self
end

local BounceEndBezier = {
	0,
	0,
	1 / 3,
	0.7,
	0.58,
	1.42,
	1,
	1
}
function Actor:bounceend(t)
	self:tween(t, "TweenType_Bezier", BounceEndBezier)
	return self
end

local SmoothBezier = {
	0,
	0,
	1,
	1
}
function Actor:smooth(t)
	self:tween(t, "TweenType_Bezier", SmoothBezier)
	return self
end
-- SSC Additions
local DropBezier = {
	0,
	0,
	1 / 3,
	1,
	2 / 3,
	0.5,
	1,
	1
}
function Actor:drop(t)
	self:tween(t, "TweenType_Bezier", DropBezier)
	return self
end

-- compound tweens "combine multiple interpolators to allow generating more
-- complex tweens." length is how long to span the animation for, while
-- ... is either a string (e.g. "linear,0.25,accelerate,0.75") or a table
-- with the tween information.
function Actor:compound(length, ...)
	local tweens = ...

	if type(tweens) == "string" then
		local parsed = split(";", tweens)
		tweens = {} -- convert to table
		for i, s in pairs(parsed) do
			local res = split(",", s)

			tweens[i] = {
				Type = res[1],
				Percent = res[2],
				Bezier = res[3] or nil
			}
		end
	end

	for i, t in pairs(tweens) do
		if t.Type == "linear" then
			self:linear(t.Percent * length)
		elseif t.Type == "accelerate" then
			self:accelerate(t.Percent * length)
		elseif t.Type == "decelerate" then
			self:decelerate(t.Percent * length)
		elseif t.Type == "spring" then
			self:spring(t.Percent * length)
		elseif t.Type == "bouncebegin" then
			self:bouncebegin(t.Percent * length)
		elseif t.Type == "bounceend" then
			self:bounceend(t.Percent * length)
		elseif t.Type == "smooth" then
			self:smooth(t.Percent * length)
		elseif t.Type == "drop" then
			--elseif t.Type == "ease" then self:ease(t.Percent*length)
			self:smooth(t.Percent * length)
		elseif t.Type == "bezier" then
		-- todo: handle using tween and 'TweenType_Bezier'
		end
	end
	return self
end

-- Hide if b is true, but don't unhide if b is false.
function Actor:hide_if(b)
	if b then
		self:visible(false)
	end
	return self
end

function Actor:player(p)
	self:visible(GAMESTATE:IsHumanPlayer())
	return self
end

function ActorFrame:propagatecommand(...)
	self:propagate(1)
	self:playcommand(...)
	self:propagate(0)
	return self
end

-- Shortcut for alignment.
--   cmd(align,0.5,0.5)  -- align center
--   cmd(align,0.0,0.0)  -- align top-left
--   cmd(align,0.5,0.0)  -- align top-center
function Actor:align(h, v)
	self:halign(h)
	self:valign(v)
	return self
end

function Actor:FullScreen()
	self:stretchto(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
	return self
end

bg_fit_functions = {
	BackgroundFitMode_CoverDistort = function(self, width, height)
		local xscale = width / self:GetWidth()
		local yscale = height / self:GetHeight()
		self:zoomx(xscale)
		self:zoomy(yscale)
	end,
	BackgroundFitMode_CoverPreserve = function(self, width, height)
		local xscale = width / self:GetWidth()
		local yscale = height / self:GetHeight()
		self:zoom(math.max(xscale, yscale))
	end,
	BackgroundFitMode_FitInside = function(self, width, height)
		local xscale = width / self:GetWidth()
		local yscale = height / self:GetHeight()
		self:zoom(math.min(xscale, yscale))
	end,
	BackgroundFitMode_FitInsideAvoidLetter = function(self, width, height)
		local yscale = height / self:GetHeight()
		self:zoom(yscale)
	end,
	BackgroundFitMode_FitInsideAvoidPillar = function(self, width, height)
		local xscale = width / self:GetWidth()
		self:zoom(xscale)
	end
}

function Actor:scale_or_crop_background_no_move()
	local fit_mode = PREFSMAN:GetPreference("BackgroundFitMode")
	if bg_fit_functions[fit_mode] then
		bg_fit_functions[fit_mode](self, SCREEN_WIDTH, SCREEN_HEIGHT)
	else
		self:scaletocover(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
	end
	return self
end

function Actor:scale_or_crop_background()
	self:scale_or_crop_background_no_move()
	self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y)
	return self
end

function Actor:CenterX()
	self:x(SCREEN_CENTER_X)
	return self
end
function Actor:CenterY()
	self:y(SCREEN_CENTER_Y)
	return self
end
function Actor:Center()
	self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y)
	return self
end

function Actor:bezier(time, curve)
	assert(type(time) == "number", "bezier tween time must be a number")
	assert((#curve == 4 or #curve == 8), "bezier tween curve must have 4 or 8 elements")
	return self:tween(time, "TweenType_Bezier", curve)
end

function Actor:Real()
	-- scale back down to real pixels.
	self:basezoom(GetReal())
	-- don't make this ugly
	self:SetTextureFiltering(false)
	return self
end

-- Scale things back up after they have already been scaled down.
function Actor:RealInverse()
	-- scale back up to theme resolution
	self:basezoom(GetRealInverse())
	self:SetTextureFiltering(true)
	return self
end

-- MaskSource([clearzbuffer])
-- Sets an actor up as the source for a mask. Clears zBuffer by default.
function Actor:MaskSource(noclear)
	if noclear == true then
		self:clearzbuffer(true)
	end
	self:zwrite(true)
	self:blend("BlendMode_NoEffect")
	return self
end

-- MaskDest()
-- Sets an actor up to be masked by anything with MaskSource().
function Actor:MaskDest()
	self:ztest(true)
	return self
end

-- Thump()
-- A customized version of pulse that is more appealing for on-beat
-- effects;
function Actor:thump(fEffectPeriod)
	self:pulse()
	if fEffectPeriod ~= nil then
		self:effecttiming(0, 0, 0.75 * fEffectPeriod, 0.25 * fEffectPeriod)
	else
		self:effecttiming(0, 0, 0.75, 0.25)
	end
	-- The default effectmagnitude will make this effect look very bad.
	self:effectmagnitude(1, 1.125, 1)
	return self
end

-- Heartbeat()
-- A customized version of pulse that is more appealing for on-beat
-- effects;
function Actor:heartbeat(fEffectPeriod)
	self:pulse()
	if fEffectPeriod ~= nil then
		self:effecttiming(0, 0.125 * fEffectPeriod, 0.125 * fEffectPeriod, 0.75 * fEffectPeriod)
	else
		self:effecttiming(0, 0.125, 0.125, 0.75)
	end
	self:effectmagnitude(1, 1.125, 1)
	return self
end

--[[ BitmapText commands ]]
-- PixelFont()
-- An alias that turns off texture filtering.
-- Named because it works best with pixel fonts.
function BitmapText:PixelFont()
	self:SetTextureFiltering(false)
	return self
end

-- Stroke(color)
-- Sets the text's stroke color.
function BitmapText:Stroke(c)
	self:strokecolor(c)
	return self
end

-- NoStroke()
-- Removes any stroke.
function BitmapText:NoStroke()
	self:strokecolor(color("0,0,0,0"))
	return self
end

-- Set Text With Format (contributed by Daisuke Master)
-- this function is my hero - shake
function BitmapText:settextf(...)
	self:settext(string.format(...))
	return self
end

-- DiffuseAndStroke(diffuse,stroke)
-- Set diffuse and stroke at the same time.
function BitmapText:DiffuseAndStroke(diffuseC, strokeC)
	self:diffuse(diffuseC)
	self:strokecolor(strokeC)
	return self
end
--[[ end BitmapText commands ]]
function Actor:LyricCommand(side)
	self:settext(Var "LyricText")
	self:draworder(102)

	self:stoptweening()
	self:shadowlengthx(0)
	self:shadowlengthy(5)
	self:strokecolor(color("#000000"))

	local Zoom = SCREEN_WIDTH / (self:GetZoomedWidth() + 1)
	if (Zoom > 1) then
		Zoom = 1
	end
	self:zoomx(Zoom)

	local lyricColor = Var "LyricColor"
	local Factor = 1
	if side == "Back" then
		Factor = 0.5
	elseif side == "Front" then
		Factor = 0.9
	end
	self:diffuse(
		{
			lyricColor[1] * Factor,
			lyricColor[2] * Factor,
			lyricColor[3] * Factor,
			lyricColor[4] * Factor
		}
	)

	if side == "Front" then
		self:cropright(1)
	else
		self:cropleft(0)
	end

	self:diffusealpha(0)
	self:linear(0.2)
	self:diffusealpha(0.75)
	self:linear(Var "LyricDuration" * 0.75)
	if side == "Front" then
		self:cropright(0)
	else
		self:cropleft(1)
	end
	self:sleep(Var "LyricDuration" * 0.25)
	self:linear(0.2)
	self:diffusealpha(0)
	return self
end

-- Play the sound on the given player's side. Must set SupportPan = true
-- on load.
function ActorSound:playforplayer(pn)
	local fBalance = SOUND:GetPlayerBalance(pn)
	self:get():SetProperty("Pan", fBalance)
	self:play()
	return self
end

function PositionPerPlayer(player, p1X, p2X)
	return player == PLAYER_1 and p1X or p2X
end

-- Make graphics their true size at any resolution.
--[[ Note: for screens taller than wide (i.e. phones, sideways displays),
you'll need to get width rather than height (I just don't feel like
uglyfying my code just to handle rare cases). -shake ]]
function GetReal()
	local theme = THEME:GetMetric("Common", "ScreenHeight")
	local res = PREFSMAN:GetPreference("DisplayHeight")
	return theme / res
end
function GetRealInverse()
	local theme = THEME:GetMetric("Common", "ScreenHeight")
	local res = PREFSMAN:GetPreference("DisplayHeight")
	return res / theme
end

-- command aliases:
function Actor:SetSize(w, h)
	self:setsize(w, h)
	return self
end

-- Simple draworder mappings
DrawOrder = {
	Background = -100,
	Underlay = 0,
	Decorations = 100,
	Content = 105,
	Screen = 120,
	Overlay = 200
}

-- function Actor:SetDrawOrder

-- deprecated aliases:
function Actor:hidden(bHide)
	lua.ReportScriptError("hidden is deprecated, use visible instead. (used on " .. self:GetName() .. ")")
	self:visible(not bHide)
end

-- safely get a very deep child of an ActorFrame
-- just in case something in between doesnt exist
-- the names should be in the order you would chain the GetChild usages
-- GetChild("Top"):GetChild("childchild"):GetChild("greatgrandchild") ...
-- if something doesnt exist, return nil
function ActorFrame:GetDescendant(...)
    local names = {...}
    local final = self
    for i, name in ipairs(names) do
        if final ~= nil and final.GetChild ~= nil then
            final = final:GetChild(name)
        else
            return final
        end
    end
    return final
end

-- return a flat table of every child and every child's child recursively
function ActorFrame:GetAllDescendants()
	local o = {}
	self:RunCommandsRecursively(function(self)
		o[#o+1] = self
	end)
	return o
end

-- (c) 2006-2012 Glenn Maynard, the Spinal Shark Collective, et al.
-- All rights reserved.
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, and/or sell copies of the Software, and to permit persons to
-- whom the Software is furnished to do so, provided that the above
-- copyright notice(s) and this permission notice appear in all copies of
-- the Software and that both the above copyright notice(s) and this
-- permission notice appear in supporting documentation.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
-- THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
-- INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
-- OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-- PERFORMANCE OF THIS SOFTWARE.
