
--[[
	LibFeedback is under development ATM
	It works as a nice replacement for the message frame in my addons so far
	Eventually it will have more animations, use StickyFrames and handle BigWigs-like alerts
	Some code ideas were borrowed, and probably badly-reused, from LibSharedMedia and Parrot
]]

local MAJOR_VERSION = "LibFeedback-1.0"
local MINOR_VERSION = tonumber(("$Revision: 14 $"):match("%d+"))

local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not lib then return end

local _G = _G
local exp = _G.exp
local type = _G.type
local pairs = _G.pairs
local tinsert = _G.tinsert
local tremove = _G.tremove
local random = _G.random
local strsplit = _G.strsplit
local strupper = _G.strupper
local UIParent = _G.UIParent
local PlaySound = _G.PlaySound
local PlaySoundFile = _G.PlaySoundFile
local GetBuildInfo = _G.GetBuildInfo
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")

local FEEDBACK_ICON = "Interface\\Icons\\INV_Misc_QuestionMark"
local FEEDBACK_FONTSIZE = 16
local FEEDBACK_FONTTYPE = "Fonts\\ARIALN.TTF"
local FEEDBACK_FONTCOLOR = { r = 1.0, g = 1.0, b = 1.0, a = 1.0 }
local FEEDBACK_FONTOUTLINE = "OUTLINE"
local FEEDBACK_ANIMATION_STYLE = "Scroll"
local FEEDBACK_ANIMATION_DIRECTION = "UP:NONE"
local FEEDBACK_ANIMATION_OVERLAP = false
local FEEDBACK_ANIMATION_RADIUS = 130
local FEEDBACK_ANIMATION_DELAY = 0.025
local FEEDBACK_ANIMATION_STEPS = 128
local FEEDBACK_ANIMATION_STICK = 16
local FEEDBACK_ANIMATION = {
	["Show"] = function(feedbackFrame, feedbackObject)
		local direction1, direction2 = string.split(":", strupper(lib.frames[feedbackFrame].animationdirection))
		lib.objects[feedbackFrame][feedbackObject].step = lib.objects[feedbackFrame][feedbackObject].step + 1
		if lib.objects[feedbackFrame][feedbackObject - 1] and lib.objects[feedbackFrame][feedbackObject - 1].step and not lib.frames[feedbackFrame].animationoverlap then
			if (direction1 == "DOWN" or direction2 == "DOWN" or ((direction1 == "LEFT" or direction1 == "RIGHT") and direction2 == "ALT" and feedbackObject % 2 == 0)) and lib.objects[feedbackFrame][feedbackObject].yoffset > lib.objects[feedbackFrame][feedbackObject - 1].yoffset + lib.objects[feedbackFrame][feedbackObject - 1].fontsize then
				lib.objects[feedbackFrame][feedbackObject].yoffset = lib.objects[feedbackFrame][feedbackObject - 1].yoffset + lib.objects[feedbackFrame][feedbackObject - 1].fontsize
			elseif (direction1 == "UP" or direction2 == "UP" or ((direction1 == "LEFT" or direction1 == "RIGHT") and direction2 == "ALT" and feedbackObject % 2 ~= 0)) and lib.objects[feedbackFrame][feedbackObject].yoffset > lib.objects[feedbackFrame][feedbackObject - 1].yoffset - lib.objects[feedbackFrame][feedbackObject - 1].fontsize then
				lib.objects[feedbackFrame][feedbackObject].yoffset = lib.objects[feedbackFrame][feedbackObject - 1].yoffset - lib.objects[feedbackFrame][feedbackObject - 1].fontsize
			end
		end
		lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
		lib.objects[feedbackFrame][feedbackObject].fontstring:SetFont(lib.objects[feedbackFrame][feedbackObject].fonttype, lib.objects[feedbackFrame][feedbackObject].fontsize, lib.objects[feedbackFrame][feedbackObject].fontoutline)
		lib.objects[feedbackFrame][feedbackObject].fontstring:SetTextColor(lib.objects[feedbackFrame][feedbackObject]['fontcolor'].r, lib.objects[feedbackFrame][feedbackObject]['fontcolor'].g, lib.objects[feedbackFrame][feedbackObject]['fontcolor'].b)
		lib.objects[feedbackFrame][feedbackObject].fontstring:SetText(lib.objects[feedbackFrame][feedbackObject].text)
		lib.objects[feedbackFrame][feedbackObject].fontstring:SetParent(lib.frames[feedbackFrame])
		lib.objects[feedbackFrame][feedbackObject].fontstring:Show()
		if lib.objects[feedbackFrame][feedbackObject].func then
			lib.objects[feedbackFrame][feedbackObject].func()
		end
		if lib.objects[feedbackFrame][feedbackObject].sound then
			PlaySound(lib.objects[feedbackFrame][feedbackObject].sound)
		end
		if lib.objects[feedbackFrame][feedbackObject].soundfile then
			PlaySoundFile(lib.objects[feedbackFrame][feedbackObject].soundfile)
		end
	end,
	["Shoot"] = function(feedbackFrame, feedbackObject, elapsed)
		local direction1, direction2 = string.split(":", strupper(lib.frames[feedbackFrame].animationdirection))
		lib.objects[feedbackFrame][feedbackObject].elapsed = lib.objects[feedbackFrame][feedbackObject].elapsed + elapsed
		if lib.objects[feedbackFrame][feedbackObject].elapsed < lib.frames[feedbackFrame].animationdelay then return end
		lib.objects[feedbackFrame][feedbackObject].step = lib.objects[feedbackFrame][feedbackObject].step + 1
		lib.objects[feedbackFrame][feedbackObject].elapsed = 0
		local xoffset, yoffset = lib.frames[feedbackFrame].animationsteps / lib.objects[feedbackFrame][feedbackObject].step, lib.frames[feedbackFrame].animationsteps / lib.objects[feedbackFrame][feedbackObject].step
		if direction1 == "LEFT" or direction2 == "LEFT" or ((direction1 == "DOWN" or direction1 == "UP") and direction2 == "ALT" and feedbackObject % 2 == 0) then
			lib.objects[feedbackFrame][feedbackObject].xoffset = lib.objects[feedbackFrame][feedbackObject].xoffset - xoffset
		elseif direction1 == "RIGHT" or direction2 == "RIGHT" or ((direction1 == "DOWN" or direction1 == "UP") and direction2 == "ALT" and feedbackObject % 2 ~= 0)  then
			lib.objects[feedbackFrame][feedbackObject].xoffset = lib.objects[feedbackFrame][feedbackObject].xoffset + xoffset
		end
		if direction1 == "DOWN" or direction2 == "DOWN" or ((direction1 == "LEFT" or direction1 == "RIGHT") and direction2 == "ALT" and feedbackObject % 2 == 0)  then
			lib.objects[feedbackFrame][feedbackObject].yoffset = lib.objects[feedbackFrame][feedbackObject].yoffset - yoffset
		elseif direction1 == "UP" or direction2 == "UP" or ((direction1 == "LEFT" or direction1 == "RIGHT") and direction2 == "ALT" and feedbackObject % 2 ~= 0)  then
			lib.objects[feedbackFrame][feedbackObject].yoffset = lib.objects[feedbackFrame][feedbackObject].yoffset + yoffset
		end
		if lib.objects[feedbackFrame][feedbackObject].sticky and lib.objects[feedbackFrame][feedbackObject].step == lib.frames[feedbackFrame].animationsteps - lib.frames[feedbackFrame].animationstick then
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
			_, _, _, lib.objects[feedbackFrame][feedbackObject].stickxoffset, lib.objects[feedbackFrame][feedbackObject].stickyoffset = lib.objects[feedbackFrame][feedbackObject].fontstring:GetPoint()
		elseif lib.objects[feedbackFrame][feedbackObject].sticky and lib.objects[feedbackFrame][feedbackObject].step > lib.frames[feedbackFrame].animationsteps - lib.frames[feedbackFrame].animationstick then
			local shakex, shakey = random(-2, 2), random(-3, 3)
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].stickxoffset + shakex, lib.objects[feedbackFrame][feedbackObject].stickyoffset + shakey)
		elseif lib.objects[feedbackFrame][feedbackObject].step > lib.frames[feedbackFrame].animationsteps - lib.frames[feedbackFrame].animationstick then
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetAlpha((lib.frames[feedbackFrame].animationsteps - lib.objects[feedbackFrame][feedbackObject].step) / lib.frames[feedbackFrame].animationstick)
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
		else
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
		end
	end,
	["Scroll"] = function(feedbackFrame, feedbackObject, elapsed)
		local direction1, direction2 = string.split(":", strupper(lib.frames[feedbackFrame].animationdirection))
		lib.objects[feedbackFrame][feedbackObject].elapsed = lib.objects[feedbackFrame][feedbackObject].elapsed + elapsed
		if lib.objects[feedbackFrame][feedbackObject].elapsed < lib.frames[feedbackFrame].animationdelay then return end
		lib.objects[feedbackFrame][feedbackObject].step = lib.objects[feedbackFrame][feedbackObject].step + 1
		lib.objects[feedbackFrame][feedbackObject].elapsed = 0
		local xoffset, yoffset = 2, 3
		if direction1 == "LEFT" or direction2 == "LEFT" or ((direction1 == "DOWN" or direction1 == "UP") and direction2 == "ALT" and feedbackObject % 2 == 0) then
			lib.objects[feedbackFrame][feedbackObject].xoffset = lib.objects[feedbackFrame][feedbackObject].xoffset - xoffset
		elseif direction1 == "RIGHT" or direction2 == "RIGHT" or ((direction1 == "DOWN" or direction1 == "UP") and direction2 == "ALT" and feedbackObject % 2 ~= 0)  then
			lib.objects[feedbackFrame][feedbackObject].xoffset = lib.objects[feedbackFrame][feedbackObject].xoffset + xoffset
		end
		if direction1 == "DOWN" or direction2 == "DOWN" or ((direction1 == "LEFT" or direction1 == "RIGHT") and direction2 == "ALT" and feedbackObject % 2 == 0)  then
			lib.objects[feedbackFrame][feedbackObject].yoffset = lib.objects[feedbackFrame][feedbackObject].yoffset - yoffset
		elseif direction1 == "UP" or direction2 == "UP" or ((direction1 == "LEFT" or direction1 == "RIGHT") and direction2 == "ALT" and feedbackObject % 2 ~= 0)  then
			lib.objects[feedbackFrame][feedbackObject].yoffset = lib.objects[feedbackFrame][feedbackObject].yoffset + yoffset
		end
		if lib.objects[feedbackFrame][feedbackObject].sticky and lib.objects[feedbackFrame][feedbackObject].step == lib.frames[feedbackFrame].animationsteps - lib.frames[feedbackFrame].animationstick then
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
			_, _, _, lib.objects[feedbackFrame][feedbackObject].stickxoffset, lib.objects[feedbackFrame][feedbackObject].stickyoffset = lib.objects[feedbackFrame][feedbackObject].fontstring:GetPoint()
		elseif lib.objects[feedbackFrame][feedbackObject].sticky and lib.objects[feedbackFrame][feedbackObject].step > lib.frames[feedbackFrame].animationsteps - lib.frames[feedbackFrame].animationstick then
			local shakex, shakey = random(-2, 2), random(-3, 3)
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].stickxoffset + shakex, lib.objects[feedbackFrame][feedbackObject].stickyoffset + shakey)
		elseif lib.objects[feedbackFrame][feedbackObject].step > lib.frames[feedbackFrame].animationsteps - lib.frames[feedbackFrame].animationstick then
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetAlpha((lib.frames[feedbackFrame].animationsteps - lib.objects[feedbackFrame][feedbackObject].step) / lib.frames[feedbackFrame].animationstick)
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
		else
			lib.objects[feedbackFrame][feedbackObject].fontstring:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", lib.objects[feedbackFrame][feedbackObject].xoffset, lib.objects[feedbackFrame][feedbackObject].yoffset)
		end
	end,
	["Hide"] = function(feedbackFrame, feedbackObject)
		lib.objects[feedbackFrame][feedbackObject].fontstring:Hide()
		lib.objects[feedbackFrame][feedbackObject].step = nil
	end,
}

lib.frames = lib.frames or {}
lib.objects = lib.objects or {}
lib.callbacks = lib.callbacks or CallbackHandler:New(lib, "AddCallback", "RemoveCallback", "RemoveAllCallbacks")
lib.mainframe = lib.mainframe or CreateFrame("Frame", "Feedback", UIParent)
lib.mainframe:SetScript("OnUpdate", function(self, elapsed)
	for feedbackFrame in pairs(lib.frames) do
		if lib.frames[feedbackFrame] and lib.objects[feedbackFrame] then
			for feedbackObject in pairs(lib.objects[feedbackFrame]) do
				if lib.objects[feedbackFrame][feedbackObject] and lib.objects[feedbackFrame][feedbackObject].step then
					if lib.objects[feedbackFrame][feedbackObject].step == 0 then
						FEEDBACK_ANIMATION["Show"](feedbackFrame, feedbackObject)
					elseif lib.objects[feedbackFrame][feedbackObject].step < lib.frames[feedbackFrame].animationsteps then
						FEEDBACK_ANIMATION[lib.frames[feedbackFrame].animationstyle](feedbackFrame, feedbackObject, elapsed)
					else
						FEEDBACK_ANIMATION["Hide"](feedbackFrame, feedbackObject)
					end
				end
			end
		end
	end
end)

--[[
Notes:
	Create a feedback frame
Arguments:
	string - the frame name
	table [optional] - the frame attributes
		table.strata string - the frame strata [default = "BACKGROUND"]
		table.width number - the frame width [default = 128]
		table.height number - the frame height [default = 64]
		table.xoffset number - the frame relative xoffset [default = 0]
		table.yoffset number - the frame relative yoffset [default = 0]
		table.fontsize number - the default message font size [default = 16]
		table.fonttype string - the default message font type [default = "Fonts\\ARIALN.TTF"]
		table.fontcolor table - the default message font color [default = { r = 1.0, g = 1.0, b = 1.0, a = 1.0 }]
		table.fontoutline string - the default message font outline [default = "OUTLINE"]
		table.animationstyle string - the type of animation to associate with the message [default = "Scroll"]
		table.animationdirection string - the direction[s] to associate with the animation [default = "UP:NONE"]
		table.animationradius number - the radius to associate with the animation [default = 135]
		table.animationdelay number- the update delay to associate with the animation [default = 0.025]
		table.animationsteps number- the number of steps to associate with the animation [default = 128]
		table.animationstick number - the number of steps to pause a sticky animation [default = 16]
Example:
	local FB = LibStub("LibFeedback-1.0")
	FB:Create("MyFeedback")
]]
function lib:Create(feedbackFrame, feedbackAttributes)
	if feedbackFrame and type(feedbackFrame) == 'string' and not lib.frames[feedbackFrame] then
		lib.frames[feedbackFrame] = CreateFrame("Frame", "Feedback_" .. feedbackFrame, lib.mainframe)
		lib.frames[feedbackFrame].strata = feedbackAttributes and feedbackAttributes.strata or "BACKGROUND"
		lib.frames[feedbackFrame].width = feedbackAttributes and feedbackAttributes.width or 128
		lib.frames[feedbackFrame].height = feedbackAttributes and feedbackAttributes.height or 64
		lib.frames[feedbackFrame].xoffset = feedbackAttributes and feedbackAttributes.xoffset or 0
		lib.frames[feedbackFrame].yoffset = feedbackAttributes and feedbackAttributes.yoffset or 0
		lib.frames[feedbackFrame].fontcolor = feedbackAttributes and feedbackAttributes.fontcolor or FEEDBACK_FONTCOLOR
		lib.frames[feedbackFrame].fonttype = feedbackAttributes and feedbackAttributes.fonttype or FEEDBACK_FONTTYPE
		lib.frames[feedbackFrame].fontsize = feedbackAttributes and feedbackAttributes.fontsize or FEEDBACK_FONTSIZE
		lib.frames[feedbackFrame].fontoutline = feedbackAttributes and feedbackAttributes.fontoutline or FEEDBACK_FONTOUTLINE
		lib.frames[feedbackFrame].animationstyle = feedbackAttributes and feedbackAttributes.animationstyle or FEEDBACK_ANIMATION_STYLE
		lib.frames[feedbackFrame].animationoverlap = feedbackAttributes and feedbackAttributes.animationoverlap or FEEDBACK_ANIMATION_OVERLAP
		lib.frames[feedbackFrame].animationdirection = feedbackAttributes and feedbackAttributes.animationdirection or FEEDBACK_ANIMATION_DIRECTION
		lib.frames[feedbackFrame].animationradius = feedbackAttributes and feedbackAttributes.animationradius or FEEDBACK_ANIMATION_RADIUS
		lib.frames[feedbackFrame].animationdelay = feedbackAttributes and feedbackAttributes.animationdelay or FEEDBACK_ANIMATION_DELAY
		lib.frames[feedbackFrame].animationsteps = feedbackAttributes and feedbackAttributes.animationsteps or FEEDBACK_ANIMATION_STEPS
		lib.frames[feedbackFrame].animationstick = feedbackAttributes and feedbackAttributes.animationstick or FEEDBACK_ANIMATION_STICK
		lib.frames[feedbackFrame]:SetFrameStrata(lib.frames[feedbackFrame].strata)
		lib.frames[feedbackFrame]:SetWidth(lib.frames[feedbackFrame].width)
		lib.frames[feedbackFrame]:SetHeight(lib.frames[feedbackFrame].height)
		lib.frames[feedbackFrame]:SetPoint("CENTER", lib.frames[feedbackFrame].parent, "CENTER", lib.frames[feedbackFrame].xoffset, lib.frames[feedbackFrame].yoffset)
		lib.frames[feedbackFrame]:SetBackdrop({ bgFile = "Interface/Tooltips/UI-Tooltip-Background", edgeFile = "Interface/Tooltips/UI-Tooltip-Border", edgeSize = 16, tileSize = 16, tile = true, insets = { left = 4, right = 4, top = 4, bottom = 4 } })
		if select(1, GetBuildInfo()) < "3.0.0" then
			lib.frames[feedbackFrame].title = lib.frames[feedbackFrame]:CreateFontString("Feedback_" .. feedbackFrame .. "Title_FontString", "ARTWORK", "MasterFont")
		else
			lib.frames[feedbackFrame].title = lib.frames[feedbackFrame]:CreateFontString("Feedback_" .. feedbackFrame .. "Title_FontString", "ARTWORK")
		end
		lib.frames[feedbackFrame].title:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", 0, 10)
		lib.frames[feedbackFrame].title:SetFont(lib.frames[feedbackFrame].fonttype, 16, "")
		lib.frames[feedbackFrame].title:SetTextColor(1.0, 1.0, 1.0, 0.5)
		lib.frames[feedbackFrame].title:SetText(feedbackFrame)
		if select(1, GetBuildInfo()) < "3.0.0" then
			lib.frames[feedbackFrame].offsets = lib.frames[feedbackFrame]:CreateFontString("Feedback_" .. feedbackFrame .. "Offsets_FontString", "ARTWORK", "MasterFont")
		else
			lib.frames[feedbackFrame].offsets = lib.frames[feedbackFrame]:CreateFontString("Feedback_" .. feedbackFrame .. "Offsets_FontString", "ARTWORK")
		end
		lib.frames[feedbackFrame].offsets:SetPoint("CENTER", lib.frames[feedbackFrame], "CENTER", 0, -10)
		lib.frames[feedbackFrame].offsets:SetFont(lib.frames[feedbackFrame].fonttype, 16, "")
		lib.frames[feedbackFrame].offsets:SetTextColor(1.0, 1.0, 1.0, 0.5)
		lib.frames[feedbackFrame].offsets:SetText("(" .. floor(lib.frames[feedbackFrame].xoffset) .. "," .. floor(lib.frames[feedbackFrame].yoffset) ..")")
		lib:FireCallback("OnFeedbackFrameCreated", feedbackFrame)
		lib:Lock(feedbackFrame)
	end
end

--[[
Notes:
	Delete a feedback frame
Arguments:
	string - the frame name
Example:
	local FB = LibStub("LibFeedback-1.0")
	FB:Create("MyFeedback")
	FB:Destroy("MyFeedback")
]]
function lib:Destroy(feedbackFrame)
	if feedbackFrame and type(feedbackFrame) == 'string' and lib.frames[feedbackFrame] then
		if lib.objects[feedbackFrame] then
			for feedbackObject in pairs(lib.objects[feedbackFrame]) do
				if lib.objects[feedbackFrame][feedbackObject].fontstring then
					lib.objects[feedbackFrame][feedbackObject].fontstring:Hide()
				end
			end
		end
		lib.objects[feedbackFrame] = nil
		lib.frames[feedbackFrame] = nil
		lib:FireCallback("OnFeedbackFrameDestroyed", feedbackFrame)
	end
end

--[[
Notes:
	Lock a feedback frame
Arguments:
	string - the frame name
Example:
	local FB = LibStub("LibFeedback-1.0")
	FB:Create("MyFeedback")
	FB:Lock("MyFeedback")
]]
function lib:Lock(feedbackFrame)
	if feedbackFrame and type(feedbackFrame) == 'string' and lib.frames[feedbackFrame] then
		lib.frames[feedbackFrame].title:Hide()
		lib.frames[feedbackFrame].offsets:Hide()
		lib.frames[feedbackFrame]:SetMovable(false)
		lib.frames[feedbackFrame]:EnableMouse(false)
		lib.frames[feedbackFrame]:SetBackdropColor(1.0, 1.0, 1.0, 0.0)
		lib.frames[feedbackFrame]:SetBackdropBorderColor(1.0, 1.0, 1.0, 0.0)
		lib:FireCallback("OnFeedbackFrameLocked", feedbackFrame)
	end
end

--[[
Notes:
	Unlock a feedback frame
Arguments:
	string - the frame name
Example:
	local FB = LibStub("LibFeedback-1.0")
	FB:Create("MyFeedback")
	FB:Unlock("MyFeedback")
]]
function lib:Unlock(feedbackFrame)
	if feedbackFrame and type(feedbackFrame) == 'string' and lib.frames[feedbackFrame] then
		lib.frames[feedbackFrame].title:Show()
		lib.frames[feedbackFrame].offsets:Show()
		lib.frames[feedbackFrame]:SetMovable(true)
		lib.frames[feedbackFrame]:EnableMouse(true)
		lib.frames[feedbackFrame]:RegisterForDrag("LeftButton")
		lib.frames[feedbackFrame]:SetBackdropColor(1.0, 1.0, 1.0, 0.2)
		lib.frames[feedbackFrame]:SetBackdropBorderColor(1.0, 1.0, 1.0, 0.2)
		lib.frames[feedbackFrame]:SetScript("OnDragStart", function ()
			lib.frames[feedbackFrame]:StartMoving()
		end)
		lib.frames[feedbackFrame]:SetScript("OnDragStop", function()
			lib.frames[feedbackFrame]:StopMovingOrSizing()
			_, _, _, lib.frames[feedbackFrame].xoffset, lib.frames[feedbackFrame].yoffset = lib.frames[feedbackFrame]:GetPoint()
			lib.frames[feedbackFrame].offsets:SetText("(" .. floor(lib.frames[feedbackFrame].xoffset) .. "," .. floor(lib.frames[feedbackFrame].yoffset) ..")")
			lib:FireCallback("OnFeedbackFrameMoved", feedbackFrame, lib.frames[feedbackFrame].xoffset, lib.frames[feedbackFrame].yoffset)
		end)
		lib:FireCallback("OnFeedbackFrameUnlocked", feedbackFrame)
	end
end

--[[
Notes:
	Add data to a feedback frame
Arguments:
	string - the frame name
	table - the feedback data
		table.text string - the text message
		table.icon string [optional] - the icon to use if enabled [default = nil]
		table.func string [optional] - the function to call if enabled [default = nil]
		table.sound string [optional] - the sound to play if enabled [default = nil]
		table.soundfile string [optional] - the soundfile to play if enabled [default = nil]
		table.fonttype string [optional] - the message font type [default = feedback frame font type]
		table.fontsize number [optional] - the message font size [default = feedback frame font size]
		table.fontcolor table [optional] - the message font color [default = feedback frame font color]
		table.fontoutline string [optional] - the message font outline [default = feedback frame font outline]
		table.sticky boolean [optional] - produce a sticky message [default=false]
Example:
	local FB = LibStub("LibFeedback-1.0")
	FB:Create("MyFeedback")
	FB:AddData("MyFeedback", { text = "Hello, World of Warcraft!" })
]]
function lib:AddData(feedbackFrame, feedbackData)
	if feedbackFrame and type(feedbackFrame) == 'string' and lib.frames[feedbackFrame] and feedbackData and type(feedbackData) == 'table' and feedbackData.text then
		if lib.frames[feedbackFrame].objects and lib.frames[feedbackFrame].objects < 128 then
			lib.frames[feedbackFrame].objects = lib.frames[feedbackFrame].objects + 1
		else
			lib.frames[feedbackFrame].objects = 1
		end
		if not lib.objects[feedbackFrame] then
			lib.objects[feedbackFrame] = {}
		end
		if not lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects] then
			lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects] = {}
		end
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].text = feedbackData.text
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].icon = feedbackData.icon or nil
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].sticky = feedbackData.sticky or false
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].func = feedbackData.func or nil
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].sound = feedbackData.sound or nil
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].soundfile = feedbackData.soundfile or nil
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fonttype = feedbackData.fonttype or lib.frames[feedbackFrame].fonttype
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fontsize = feedbackData.fontsize or lib.frames[feedbackFrame].fontsize
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fontcolor = feedbackData.fontcolor or lib.frames[feedbackFrame].fontcolor
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fontoutline = feedbackData.fontoutline or lib.frames[feedbackFrame].fontoutline
		if not lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fontstring then
			if select(1, GetBuildInfo()) < "3.0.0" then
				lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fontstring = lib.frames[feedbackFrame]:CreateFontString("Feedback_" .. feedbackFrame .. "_FontString" .. lib.frames[feedbackFrame].objects, "ARTWORK", "MasterFont")
			else
				lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].fontstring = lib.frames[feedbackFrame]:CreateFontString("Feedback_" .. feedbackFrame .. "_FontString" .. lib.frames[feedbackFrame].objects, "ARTWORK")
			end
		end
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].stickxoffset = 0
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].stickyoffset = 0
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].xoffset = 0
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].yoffset = 0
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].elapsed = 0
		lib.objects[feedbackFrame][lib.frames[feedbackFrame].objects].step = 0
	end
end

--[[
Notes:
	Enable sink support if available
if LibStub:("LibSink-2.0") then
	LibStub("LibSink-2.0"):RegisterSink("Notification", "Feedback Notification", "Feedback Notification Frame", lib.PourData)
	function lib:PourData(addon, text, r, g, b, fonttype, fontsize, fontoutline, sticky, feedbackFrame, icon, sound)
		if feedbackFrame and type(feedbackFrame) == 'string' and lib.frames[feedbackFrame] and text then
			local feedbackData = {
				text = text,
				icon = icon,
				sticky = sticky,
				sound = sound,
				fonttype = fonttype,
				fontsize = fontsize,
				fontoutline = fontoutline,
				fontcolor = {
					r = r,
					g = g,
					b = b,
				},
			}
			lib:AddData(feedbackFrame, feedbackData)
		end
	end
end
]]

--[[
Notes:
	Allow mixin embedding
]]
lib.FireCallback = lib.callbacks.Fire
lib.CreateFeedback, lib.DestroyFeedback = lib.Create, lib.Destroy
lib.LockFeedback, lib.UnlockFeedback = lib.Lock, lib.Unlock
lib.AddFeedbackData = lib.AddData
lib.embeded = lib.embeded or {}
local mixins = {
	"AddCallback", "RemoveCallback", "RemoveAllCallbacks",
	"CreateFeedback", "DestroyFeedback",
	"LockFeedback", "UnlockFeedback",
	"AddFeedbackData",
}
function lib:Embed(target)
	for k, v in pairs(mixins) do
		target[v] = self[v]
	end
	self.embeded[target] = true
	target.callbacks = target.callbacks or {}
	return target
end
for target, v in pairs(lib.embeded) do
	lib:Embed(target)
end

