--[[
	Copyright (C) 2006-2007 Nymbia
	Copyright (C) 2010 Hendrik "Nevcairiel" Leppkes < h.leppkes@gmail.com >

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the Free Software Foundation, Inc.,
	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
]]
local Quartz3 = LibStub("AceAddon-3.0"):GetAddon("Quartz3")
local L = LibStub("AceLocale-3.0"):GetLocale("Quartz3")

local MODNAME = "Buff"
local Buff = Quartz3:NewModule(MODNAME, "AceEvent-3.0", "AceBucket-3.0", "AceTimer-3.0")
local Player = Quartz3:GetModule("Player")
local Focus = Quartz3:GetModule("Focus")
local Target = Quartz3:GetModule("Target")

local TimeFmt = Quartz3.Util.TimeFormat

local media = LibStub("LibSharedMedia-3.0")
local lsmlist = AceGUIWidgetLSMlists

----------------------------
-- Upvalues
-- GLOBALS: 
local CreateFrame, GetTime, UIParent = CreateFrame, GetTime, UIParent
local UnitIsUnit, UnitBuff, UnitDebuff = UnitIsUnit, UnitBuff, UnitDebuff
local unpack, pairs, next, unpack, sort = unpack, pairs, next, unpack, sort



local targetlocked = true
local focuslocked = true

local OnUpdate
local showicons

local db, getOptions

local defaults = {
	profile = {
		target = true,
		targetbuffs = true,
		targetdebuffs = true,
		targetfixedduration = 0,
		targeticons = true,
		targeticonside = "right",
		
		targetanchor = "player",--L["Free"], L["Target"], L["Focus"]
		targetx = 500,
		targety = 350,
		targetgrowdirection = "up", --L["Down"]
		targetposition = "topright",
		
		targetgap = 1,
		targetspacing = 1,
		targetoffset = 3,
		
		targetwidth = 120,
		targetheight = 12,
		
		focus = true,
		focusbuffs = true,
		focusdebuffs = true,
		focusfixedduration = 0,
		focusicons = true,
		focusiconside = "left",
		
		focusanchor = "player",--L["Free"], L["Target"], L["Focus"]
		focusx = 400,
		focusy = 350,
		focusgrowdirection = "up", --L["Down"]
		focusposition = "bottomleft",
		
		focusgap = 1,
		focusspacing = 1,
		focusoffset = 3,
		
		focuswidth = 120,
		focusheight = 12,
		
		buffnametext = true,
		bufftimetext = true,
		
		bufftexture = "LiteStep",
		bufffont = "Friz Quadrata TT",
		bufffontsize = 9,
		buffalpha = 1,
		
		buffcolor = {0,0.49, 1},
		
		debuffsbytype = true,
		debuffcolor = {1.0, 0.7, 0},
		Poison = {0, 1, 0},
		Magic = {0, 0, 1},
		Disease = {.55, .15, 0},
		Curse = {1, 0, 1},
		
		bufftextcolor = {1,1,1},
		
		timesort = true,
	}
}

do
	function OnUpdate(frame)
		local currentTime = GetTime()
		local endTime = frame.endTime
		if currentTime > endTime then
			Buff:UpdateBars()
		else
			local remaining = (currentTime - frame.startTime)
			frame:SetValue(endTime - remaining)
			frame.timetext:SetFormattedText(TimeFmt(endTime - currentTime))
		end
	end
end

local function OnShow(frame)
	frame:SetScript("OnUpdate", OnUpdate)
end

local function OnHide(frame)
	frame:SetScript("OnUpdate", nil)
end

local framefactory = {
	__index = function(t,k)
		local bar = CreateFrame("StatusBar", nil, UIParent)
		t[k] = bar
		bar:SetFrameStrata("MEDIUM")
		bar:Hide()
		bar:SetScript("OnShow", OnShow)
		bar:SetScript("OnHide", OnHide)
		bar:SetBackdrop({bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16})
		bar:SetBackdropColor(0,0,0)
		bar.text = bar:CreateFontString(nil, "OVERLAY")
		bar.timetext = bar:CreateFontString(nil, "OVERLAY")
		bar.icon = bar:CreateTexture(nil, "DIALOG")
		if k == 1 then
			bar:SetMovable(true)
			bar:RegisterForDrag("LeftButton")
			bar:SetClampedToScreen(true)
		end
		Buff:ApplySettings()
		return bar
	end
}
local targetbars = setmetatable({}, framefactory)
local focusbars = setmetatable({}, framefactory)

local getOptions
do
	local positions = {
		["bottom"] = L["Bottom"],
		["top"] = L["Top"],
		["topleft"] = L["Top Left"],
		["topright"] = L["Top Right"],
		["bottomleft"] = L["Bottom Left"],
		["bottomright"] = L["Bottom Right"],
		["leftup"] = L["Left (grow up)"],
		["leftdown"] = L["Left (grow down)"],
		["rightup"] = L["Right (grow up)"],
		["rightdown"] = L["Right (grow down)"],
	}
	
	local function hidedebuffsbytype()
		return not db.debuffsbytype
	end
	
	local function hidedebuffsnottype()
		return db.debuffsbytype
	end
	
	local function gettargetfreeoptionshidden()
		return db.targetanchor ~= "free"
	end
	
	local function gettargetnotfreeoptionshidden()
		return db.targetanchor == "free"
	end
	
	local function targetdragstart()
		targetbars[1]:StartMoving()
	end
	
	local function targetdragstop()
		db.targetx = targetbars[1]:GetLeft()
		db.targety = targetbars[1]:GetBottom()
		targetbars[1]:StopMovingOrSizing()
	end
	local function targetnothing()
		targetbars[1]:SetAlpha(db.buffalpha)
	end
	local function getfocusfreeoptionshidden()
		return db.focusanchor ~= "free"
	end
	local function getfocusnotfreeoptionshidden()
		return db.focusanchor == "free"
	end
	local function focusdragstart()
		focusbars[1]:StartMoving()
	end
	local function focusdragstop()
		db.focusx = focusbars[1]:GetLeft()
		db.focusy = focusbars[1]:GetBottom()
		focusbars[1]:StopMovingOrSizing()
	end
	local function focusnothing()
		focusbars[1]:SetAlpha(db.buffalpha)
	end
	
	local function setOpt(info, value)
		db[info.arg or info[#info]] = value
		Buff:ApplySettings()
	end

	local function getOpt(info)
		return db[info.arg or info[#info]]
	end
	
	local function setOptFocus(info, value)
		db[info.arg or ("focus"..info[#info])] = value
		Buff:ApplySettings()
	end

	local function getOptFocus(info)
		return db[info.arg or ("focus"..info[#info])]
	end
	
	local function setOptTarget(info, value)
		db[info.arg or ("target"..info[#info])] = value
		Buff:ApplySettings()
	end

	local function getOptTarget(info)
		return db[info.arg or ("target"..info[#info])]
	end

	local function getColor(info)
		return unpack(getOpt(info))
	end

	local function setColor(info, r, g, b, a)
		setOpt(info, {r, g, b, a})
	end

	local options
	function getOptions()
		if not options then 
			options = {
				type = "group",
				name = L["Buff"],
				order = 600,
				get = getOpt,
				set = setOpt,
				childGroups = "tab",
				args = {
					toggle = {
						type = "toggle",
						name = L["Enable"],
						desc = L["Enable"],
						get = function()
							return Quartz3:GetModuleEnabled(MODNAME)
						end,
						set = function(info, v)
							Quartz3:SetModuleEnabled(MODNAME, v)
						end,
						order = 100,
					},
					focus = {
						type = "group",
						name = L["Focus"],
						desc = L["Focus"],
						order = 101,
						get = getOptFocus,
						set = setOptFocus,
						args = {
							show = {
								type = "toggle",
								name = L["Enable %s"]:format(L["Focus"]),
								desc = L["Show buffs/debuffs for your %s"]:format(L["Focus"]),
								arg = "focus",
								order = 90,
								width = "full",
								disabled = false,
							},
							buffs = {
								type = "toggle",
								name = L["Enable Buffs"],
								desc = L["Show buffs for your %s"]:format(L["Focus"]),
								order = 91,
							},
							debuffs = {
								type = "toggle",
								name = L["Enable Debuffs"],
								desc = L["Show debuffs for your %s"]:format(L["Focus"]),
								order = 92,
							},
							fixedduration = {
								type = "range",
								name = L["Fixed Duration"],
								desc = L["Fix bars to a specified duration"],
								min = 0, max = 60, step = 1,
								order = 93,
							},
							nlf = {
								type = "description",
								name = "",
								order = 100,
							},
							width = {
								type = "range",
								name = L["Buff Bar Width"],
								desc = L["Set the width of the buff bars"],
								min = 50, max = 300, step = 1,
								order = 101,
							},
							height = {
								type = "range",
								name = L["Buff Bar Height"],
								desc = L["Set the height of the buff bars"],
								min = 4, max = 25, step = 1,
								order = 101,
							},
							anchor = {
								type = "select",
								name = L["Anchor Frame"],
								desc = L["Select where to anchor the %s bars"]:format(L["Focus"]),
								values = {["player"] = L["Player"], ["free"] = L["Free"], ["target"] = L["Target"], ["focus"] = L["Focus"]},
								order = 102,
							},
							-- free
							focuslock = {
								type = "toggle",
								name = L["Lock"],
								desc = L["Toggle %s bar lock"]:format(L["Focus"]),
								get = function()
									return focuslocked
								end,
								set = function(info, v)
									local bar = focusbars[1]
									if v then
										bar.Hide = nil
										bar:EnableMouse(false)
										bar:SetScript("OnDragStart", nil)
										bar:SetScript("OnDragStop", nil)
										Buff:UpdateBars()
									else
										bar:Show()
										bar:EnableMouse(true)
										bar:SetScript("OnDragStart", focusdragstart)
										bar:SetScript("OnDragStop", focusdragstop)
										bar:SetAlpha(1)
										bar.endTime = bar.endTime or 0
										bar.Hide = focusnothing
									end
									focuslocked = v
								end,
								hidden = getfocusfreeoptionshidden,
								order = 103,
							},
							x = {
								type = "range",
								name = L["X"],
								desc = L["Set an exact X value for this bar's position."],
								min = 0, max = 2560, step = 1,
								hidden = getfocusfreeoptionshidden,
								order = 104,
							},
							y = {
								type = "range",
								name = L["Y"],
								desc = L["Set an exact Y value for this bar's position."],
								min = 0, max = 1600, step = 1,
								hidden = getfocusfreeoptionshidden,
								order = 104,
							},
							growdirection = {
								type = "select",
								name = L["Grow Direction"],
								desc = L["Set the grow direction of the %s bars"]:format(L["Focus"]),
								values = {["up"] = L["Up"], ["down"] = L["Down"]},
								hidden = getfocusfreeoptionshidden,
								order = 105,
							},
							-- anchored to a cast bar
							position = {
								type = "select",
								name = L["Position"],
								desc = L["Position the bars for your %s"]:format(L["Focus"]),
								values = positions,
								hidden = getfocusnotfreeoptionshidden,
								order = 103,
							},
							gap = {
								type = "range",
								name = L["Gap"],
								desc = L["Tweak the vertical position of the bars for your %s"]:format(L["Focus"]),
								min = -35, max = 35, step = 1,
								hidden = getfocusnotfreeoptionshidden,
								order = 104,
							},
							offset = {
								type = "range",
								name = L["Offset"],
								desc = L["Tweak the horizontal position of the bars for your %s"]:format(L["Focus"]),
								min = -35, max = 35,step = 1,
								hidden = getfocusnotfreeoptionshidden,
								order = 106,
							},
							spacing = {
								type = "range",
								name = L["Spacing"],
								desc = L["Tweak the space between bars for your %s"]:format(L["Focus"]),
								min = -35, max = 35, step = 1,
								order = 107,
							},
							nli = {
								type = "description",
								name = "",
								order = 108,
							},
							icons = {
								type = "toggle",
								name = L["Show Icons"],
								desc = L["Show icons on buffs and debuffs for your %s"]:format(L["Focus"]),
								order = 109,
							},
							iconside = {
								type = "select",
								name = L["Icon Position"],
								desc = L["Set the side of the buff bar that the icon appears on"],
								values = {["left"] = L["Left"], ["right"] = L["Right"]},
								order = 110,
							},
						},
					},
					target = {
						type = "group",
						name = L["Target"],
						desc = L["Target"],
						order = 102,
						get = getOptTarget,
						set = setOptTarget,
						args = {
							show = {
								type = "toggle",
								name = L["Enable %s"]:format(L["Target"]),
								desc = L["Show buffs/debuffs for your %s"]:format(L["Target"]),
								arg = "target",
								disabled = false,
								width = "full",
								order = 90,
							},
							buffs = {
								type = "toggle",
								name = L["Enable Buffs"],
								desc = L["Show buffs for your %s"]:format(L["Target"]),
								order = 91,
							},
							debuffs = {
								type = "toggle",
								name = L["Enable Debuffs"],
								desc = L["Show debuffs for your %s"]:format(L["Target"]),
								order = 92,
							},
							fixedduration = {
								type = "range",
								name = L["Fixed Duration"],
								desc = L["Fix bars to a specified duration"],
								min = 0, max = 60, step = 1,
								order = 93,
							},
							nlf = {
								type = "description",
								name = "",
								order = 100,
							},
							width = {
								type = "range",
								name = L["Buff Bar Width"],
								desc = L["Set the width of the buff bars"],
								min = 50, max = 300, step = 1,
								order = 101,
							},
							height = {
								type = "range",
								name = L["Buff Bar Height"],
								desc = L["Set the height of the buff bars"],
								min = 4, max = 25, step = 1,
								order = 101,
							},
							anchor = {
								type = "select",
								name = L["Anchor Frame"],
								desc = L["Select where to anchor the %s bars"]:format(L["Target"]),
								values = {["player"] = L["Player"], ["free"] = L["Free"], ["target"] = L["Target"], ["focus"] = L["Focus"]},
								order = 102,
							},
							-- free
							targetlock = {
								type = "toggle",
								name = L["Lock"],
								desc = L["Toggle %s bar lock"]:format(L["Target"]),
								get = function()
									return targetlocked
								end,
								set = function(info, v)
									local bar = targetbars[1]
									if v then
										bar.Hide = nil
										bar:EnableMouse(false)
										bar:SetScript("OnDragStart", nil)
										bar:SetScript("OnDragStop", nil)
										Buff:UpdateBars()
									else
										bar:Show()
										bar:EnableMouse(true)
										bar:SetScript("OnDragStart", targetdragstart)
										bar:SetScript("OnDragStop", targetdragstop)
										bar:SetAlpha(1)
										bar.endTime = bar.endTime or 0
										bar.Hide = targetnothing
									end
									targetlocked = v
								end,
								hidden = gettargetfreeoptionshidden,
								order = 103,
							},
							x = {
								type = "range",
								name = L["X"],
								desc = L["Set an exact X value for this bar's position."],
								min = 0, max = 2560, bigStep = 1,
								hidden = gettargetfreeoptionshidden,
								order = 104,
							},
							y = {
								type = "range",
								name = L["Y"],
								desc = L["Set an exact Y value for this bar's position."],
								min = 0, max = 1600, bigStep = 1,
								hidden = gettargetfreeoptionshidden,
								order = 104,
							},
							growdirection = {
								type = "select",
								name = L["Grow Direction"],
								desc = L["Set the grow direction of the %s bars"]:format(L["Target"]),
								values = {["up"] = L["Up"], ["down"] = L["Down"]},
								hidden =  gettargetfreeoptionshidden,
								order = 105,
							},
							-- anchored to a cast bar
							position = {
								type = "select",
								name = L["Position"],
								desc = L["Position the bars for your %s"]:format(L["Target"]),
								values = positions,
								hidden = gettargetnotfreeoptionshidden,
								order = 103,
							},
							gap = {
								type = "range",
								name = L["Gap"],
								desc = L["Tweak the vertical position of the bars for your %s"]:format(L["Target"]),
								min = -35, max = 35, step = 1,
								order = 101,
								hidden = gettargetnotfreeoptionshidden,
								order = 104,
							},
							offset = {
								type = "range",
								name = L["Offset"],
								desc = L["Tweak the horizontal position of the bars for your %s"]:format(L["Target"]),
								min = -35, max = 35, step = 1,
								hidden = gettargetnotfreeoptionshidden,
								order = 106,
							},
							spacing = {
								type = "range",
								name = L["Spacing"],
								desc = L["Tweak the space between bars for your %s"]:format(L["Target"]),
								min = -35, max = 35, step = 1,
								order = 107,
							},
							nli = {
								type = "description",
								name = "",
								order = 108,
							},
							icons = {
								type = "toggle",
								name = L["Show Icons"],
								desc = L["Show icons on buffs and debuffs for your %s"]:format(L["Target"]),
								order = 109,
							},
							iconside = {
								type = "select",
								name = L["Icon Position"],
								desc = L["Set the side of the buff bar that the icon appears on"],
								values = {["left"] = L["Left"], ["right"] = L["Right"]},
								order = 110,
							},
						},
					},
					settings = {
						type = "group",
						name = L["Settings"],
						order = 1,
						args = {
							timesort = {
								type = "toggle",
								name = L["Sort by Remaining Time"],
								desc = L["Sort the buffs and debuffs by time remaining.  If unchecked, they will be sorted alphabetically."],
								order = 103,
								width = "full",
							},
							buffnametext = {
								type = "toggle",
								name = L["Buff Name Text"],
								desc = L["Display the names of buffs/debuffs on their bars"],
								order = 106,
							},
							bufftimetext = {
								type = "toggle",
								name = L["Buff Time Text"],
								desc = L["Display the time remaining on buffs/debuffs on their bars"],
								order = 107,
							},
							bufffont = {
								type = "select",
								dialogControl = "LSM30_Font",
								name = L["Font"],
								desc = L["Set the font used in the buff bars"],
								values = lsmlist.font,
								order = 108,
							},
							bufftexture = {
								type = "select",
								dialogControl = "LSM30_Statusbar", 
								name = L["Texture"],
								desc = L["Set the buff bar Texture"],
								values = lsmlist.statusbar,
								order = 109,
							},
							bufftextcolor = {
								type = "color",
								name = L["Text Color"],
								desc = L["Set the color of the text for the buff bars"],
								order = 110,
								width = "full",
								get = getColor,
								set = setColor,
							},
							bufffontsize = {
								type = "range",
								name = L["Font Size"],
								desc = L["Set the font size for the buff bars"],
								min = 3, max = 15, step = 1,
								order = 111,
							},
							buffalpha = {
								type = "range",
								name = L["Alpha"],
								desc = L["Set the alpha of the buff bars"],
								min = 0.05, max = 1, step = 0.05,
								isPercent = true,
								order = 112,
							},
						},
					},
					colors = {
						type = "group",
						name = L["Colors"],
						desc = L["Colors"],
						order = -1,
						args = {
							buffcolor = {
								type = "color",
								name = L["Buff Color"],
								desc = L["Set the color of the bars for buffs"],
								get = getColor,
								set = setColor,
							},
							debuffsbytype = {
								type = "toggle",
								name = L["Debuffs by Type"],
								desc = L["Color debuff bars according to their dispel type"],
								order = 101,
							},
							debuffcolor = {
								type = "color",
								name = L["Debuff Color"],
								desc = L["Set the color of the bars for debuffs"],
								get = getColor,
								set = setColor,
								disabled = hidedebuffsnottype,
								order = 102,
							},
							physcolor = {
								type = "color",
								name = L["Undispellable Color"],
								desc = L["Set the color of the bars for undispellable debuffs"],
								get = getColor,
								set = setColor,
								arg = "debuffcolor",
								disabled = hidedebuffsbytype,
								order = 102,
							},
							Curse = {
								type = "color",
								name = L["Curse Color"],
								desc = L["Set the color of the bars for curses"],
								get = getColor,
								set = setColor,
								disabled = hidedebuffsbytype,
								order = 103,
							},
							Disease = {
								type = "color",
								name = L["Disease Color"],
								desc = L["Set the color of the bars for diseases"],
								get = getColor,
								set = setColor,
								disabled = hidedebuffsbytype,
								order = 104,
							},
							Magic = {
								type = "color",
								name = L["Magic Color"],
								desc = L["Set the color of the bars for magic"],
								get = getColor,
								set = setColor,
								disabled = hidedebuffsbytype,
								order = 105,
							},
							Poison = {
								type = "color",
								name = L["Poison Color"],
								desc = L["Set the color of the bars for poisons"],
								get = getColor,
								set = setColor,
								disabled = hidedebuffsbytype,
								order = 106,
							},
						},
					},
				}
			}
		end
		return options
	end
end

function Buff:OnInitialize()
	self.db = Quartz3.db:RegisterNamespace(MODNAME, defaults)
	db = self.db.profile

	-- fix broken buff text color
	if type(db.bufftextcolor) ~= "table" then
		db.bufftextcolor = {1,1,1}
	end

	self:SetEnabledState(Quartz3:GetModuleEnabled(MODNAME))
	Quartz3:RegisterModuleOptions(MODNAME, getOptions, L["Buff"])
end

function Buff:OnEnable()
	self:RegisterBucketEvent("UNIT_AURA", 0.5)
	self:RegisterEvent("PLAYER_TARGET_CHANGED", "UpdateBars")
	self:RegisterEvent("PLAYER_FOCUS_CHANGED", "UpdateBars")
	media.RegisterCallback(self, "LibSharedMedia_SetGlobal", function(mtype, override)
		if mtype == "statusbar" then
			for i, v in pairs(targetbars) do
				v:SetStatusBarTexture(media:Fetch("statusbar", override))
			end
			for i, v in pairs(focusbars) do
				v:SetStatusBarTexture(media:Fetch("statusbar", override))
			end
		end
	end)

	media.RegisterCallback(self, "LibSharedMedia_Registered", function(mtype, key)
		if mtype == "statusbar" and key == self.config.bufftexture then
			for i, v in pairs(targetbars) do
				v:SetStatusBarTexture(media:Fetch("statusbar", self.config.bufftexture))
			end
			for i, v in pairs(focusbars) do
				v:SetStatusBarTexture(media:Fetch("statusbar", self.config.bufftexture))
			end
		end
	end)

	self:ApplySettings()
end

function Buff:OnDisable()
	targetbars[1].Hide = nil
	targetbars[1]:EnableMouse(false)
	targetbars[1]:SetScript("OnDragStart", nil)
	targetbars[1]:SetScript("OnDragStop", nil)
	for _, v in pairs(targetbars) do
		v:Hide()
	end

	focusbars[1].Hide = nil
	focusbars[1]:EnableMouse(false)
	focusbars[1]:SetScript("OnDragStart", nil)
	focusbars[1]:SetScript("OnDragStop", nil)
	for _, v in pairs(focusbars) do
		v:Hide()
	end

	media.UnregisterCallback(self, "LibSharedMedia_SetGlobal")
	media.UnregisterCallback(self, "LibSharedMedia_Registered")
end

function Buff:UNIT_AURA(units)
	for unit in pairs(units) do
		if unit == "target" then
			self:UpdateTargetBars()
		end
		if unit == "focus" or UnitIsUnit("focus", unit) then
			self:UpdateFocusBars()
		end
	end
end

function Buff:CheckForUpdate()
	if targetbars[1]:IsShown() then
		self:UpdateTargetBars()
	end
	if focusbars[1]:IsShown() then
		self:UpdateFocusBars()
	end
end

function Buff:UpdateBars()
	self:UpdateTargetBars()
	self:UpdateFocusBars()
end

do
	local tblCache = setmetatable({}, {__mode="k"})
	local function new()
		local entry = next(tblCache)
		if entry then tblCache[entry] = nil else entry = {} end
		return entry
	end
	local function del(tbl)
		-- these 2 values are not in every table, clear them
		tbl.isbuff, tbl.dispeltype = nil, nil
		tblCache[tbl] = true
	end
	
	local function mysort(a,b)
		if db.timesort then
			if a.isbuff == b.isbuff then
				return a.remaining < b.remaining
			else
				return a.isbuff
			end
		else
			if a.isbuff == b.isbuff then
				return a.name < b.name
			else
				return a.isbuff
			end
		end
	end
	
	local tmp = {}
	local called = false -- prevent recursive calls when new bars are created.
	function Buff:UpdateTargetBars()
		if called then
			return
		end
		called = true
		if db.target then
			local currentTime = GetTime()
			for k in pairs(tmp) do
				tmp[k] = del(tmp[k])
			end
			if db.targetbuffs then
				for i = 1, 32 do
					local name, rank, texture, applications, _, duration, expirationTime, caster = UnitBuff("target", i)
					local remaining = expirationTime and (expirationTime - GetTime()) or nil
					if not name then
						break
					end
					if (caster=="player" or caster=="pet" or caster=="vehicle") and duration > 0 then
						local t = new()
						tmp[#tmp+1] = t
						t.name = name
						t.texture = texture
						t.duration = duration
						t.remaining = remaining
						t.isbuff = true
						t.applications = applications
					end
				end
			end
			if db.targetdebuffs then
				for i = 1, 40 do
					local name, rank, texture, applications, dispeltype, duration, expirationTime, caster = UnitDebuff("target", i)
					local remaining =  expirationTime and (expirationTime - GetTime()) or nil
					if not name then
						break
					end
					if (caster=="player" or caster=="pet" or caster=="vehicle") and duration > 0 then
						local t = new()
						tmp[#tmp+1] = t
						t.name = name
						t.texture = texture
						t.duration = duration
						t.remaining = remaining
						t.dispeltype = dispeltype
						t.applications = applications
					end
				end
			end
			sort(tmp, mysort)
			local maxindex = 0
			for k=1,#tmp do
				local v = tmp[k]
				maxindex = k
				local bar = targetbars[k]
				if v.applications > 1 then
					bar.text:SetFormattedText("%s (%s)", v.name, v.applications)
				else
					bar.text:SetText(v.name)
				end
				bar.icon:SetTexture(v.texture)
				local elapsed = (v.duration - v.remaining)
				local startTime, endTime = (currentTime - elapsed), (currentTime + v.remaining)
				if db.targetfixedduration > 0 then
					startTime = endTime - db.targetfixedduration
				end
				bar.startTime = startTime
				bar.endTime = endTime
				bar:SetMinMaxValues(startTime, endTime)
				bar:Show()
				if v.isbuff then
					bar:SetStatusBarColor(unpack(db.buffcolor))
				else
					if db.debuffsbytype then
						local dispeltype = v.dispeltype
						if dispeltype then
							bar:SetStatusBarColor(unpack(db[dispeltype]))
						else
							bar:SetStatusBarColor(unpack(db.debuffcolor))
						end
					else
						bar:SetStatusBarColor(unpack(db.debuffcolor))
					end
				end
			end
			for i = maxindex+1, #targetbars do
				targetbars[i]:Hide()
			end
		else
			targetbars[1].Hide = nil
			targetbars[1]:EnableMouse(false)
			targetbars[1]:SetScript("OnDragStart", nil)
			targetbars[1]:SetScript("OnDragStop", nil)
			for i=1,#targetbars do
				targetbars[i]:Hide()
			end
		end
		if targetbars[1]:IsShown() then
			if not self.autoUpdateTimer then
				self.autoUpdateTimer = self:ScheduleRepeatingTimer("CheckForUpdate", 3)
			end
		elseif not focusbars[1]:IsShown() then
			if self.autoUpdateTimer then
				self:CancelTimer(self.autoUpdateTimer)
				self.autoUpdateTimer = nil
			end
		end
		called = false
	end
	function Buff:UpdateFocusBars()
		if called then
			return
		end
		called = true
		if db.focus then
			local currentTime = GetTime()
			for k in pairs(tmp) do
				tmp[k] = del(tmp[k])
			end
			if db.focusbuffs then
				for i = 1, 32 do
					local name, rank, texture, applications, dispeltype, duration, expirationTime, caster = UnitBuff("focus", i)
					local remaining =  expirationTime and (expirationTime - GetTime()) or nil
					if not name then
						break
					end
					if (caster=="player" or caster=="pet" or caster=="vehicle") and duration > 0 then
						local t = new()
						tmp[#tmp+1] = t
						t.name = name
						t.texture = texture
						t.duration = duration
						t.remaining = remaining
						t.isbuff = true
						t.applications = applications
					end
				end
			end
			if db.focusdebuffs then
				for i = 1, 40 do
					local name, rank, texture, applications, dispeltype, duration, expirationTime, caster = UnitDebuff("focus", i)
					local remaining =  expirationTime and (expirationTime - GetTime()) or nil
					if not name then
						break
					end
					if (caster=="player" or caster=="pet" or caster=="vehicle") and duration > 0 then
						local t = new()
						tmp[#tmp+1] = t
						t.name = name
						t.texture = texture
						t.duration = duration
						t.remaining = remaining
						t.dispeltype = dispeltype
						t.applications = applications
					end
				end
			end
			sort(tmp, mysort)
			local maxindex = 0
			for k=1,#tmp do
				local v = tmp[k]
				maxindex = k
				local bar = focusbars[k]
				if v.applications > 1 then
					bar.text:SetFormattedText("%s (%s)", v.name, v.applications)
				else
					bar.text:SetText(v.name)
				end
				bar.icon:SetTexture(v.texture)
				local elapsed = (v.duration - v.remaining)
				local startTime, endTime = (currentTime - elapsed), (currentTime + v.remaining)
				if db.focusfixedduration > 0 then
					startTime = endTime - db.focusfixedduration
				end
				bar.startTime = startTime
				bar.endTime = endTime
				bar:SetMinMaxValues(startTime, endTime)
				bar:Show()
				if v.isbuff then
					bar:SetStatusBarColor(unpack(db.buffcolor))
				else
					if db.debuffsbytype then
						local dispeltype = v.dispeltype
						if dispeltype then
							bar:SetStatusBarColor(unpack(db[dispeltype]))
						else
							bar:SetStatusBarColor(unpack(db.debuffcolor))
						end
					else
						bar:SetStatusBarColor(unpack(db.debuffcolor))
					end
				end
			end
			for i = maxindex+1, #focusbars do
				focusbars[i]:Hide()
			end
		else
			focusbars[1].Hide = nil
			focusbars[1]:EnableMouse(false)
			focusbars[1]:SetScript("OnDragStart", nil)
			focusbars[1]:SetScript("OnDragStop", nil)
			for i=1,#focusbars do
				focusbars[i]:Hide()
			end
		end
		if focusbars[1]:IsShown() then
			if not self.autoUpdateTimer then
				self.autoUpdateTimer = self:ScheduleRepeatingTimer("CheckForUpdate", 3)
			end
		elseif not targetbars[1]:IsShown() then
			if self.autoUpdateTimer then
				self:CancelTimer(self.autoUpdateTimer)
				self.autoUpdateTimer = nil
			end
		end
		called = false
	end
end
do
	local function apply(unit, i, bar, db, direction)
		local bars, position, icons, iconside, gap, spacing, offset, anchor, x, y, grow, height, width
		local qpdb = Player.db.profile
		if unit == "target" then
			bars = targetbars
			position = db.targetposition
			icons = db.targeticons
			iconside = db.targeticonside
			gap = db.targetgap
			spacing = db.targetspacing
			offset = db.targetoffset
			anchor = db.targetanchor
			x = db.targetx
			y = db.targety
			grow = db.targetgrowdirection
			width = db.targetwidth
			height = db.targetheight
		else
			bars = focusbars
			position = db.focusposition
			icons = db.focusicons
			iconside = db.focusiconside
			gap = db.focusgap
			spacing = db.focusspacing
			offset = db.focusoffset
			anchor = db.focusanchor
			x = db.focusx
			y = db.focusy
			grow = db.focusgrowdirection
			width = db.focuswidth
			height = db.focusheight
		end
		bar:ClearAllPoints()
		bar:SetStatusBarTexture(media:Fetch("statusbar", db.bufftexture))
		bar:GetStatusBarTexture():SetHorizTile(false)
		bar:GetStatusBarTexture():SetVertTile(false)
		bar:SetWidth(width)
		bar:SetHeight(height)
		bar:SetScale(qpdb.scale)
		bar:SetAlpha(db.buffalpha)
		
		if anchor == "free" then
			if i == 1 then
				bar:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y)
				if grow == "up" then
					direction = 1
				else --L["Down"]
					direction = -1
				end
			else
				if direction == 1 then
					bar:SetPoint("BOTTOMRIGHT", bars[i-1], "TOPRIGHT", 0, spacing)
				else -- -1
					bar:SetPoint("TOPRIGHT", bars[i-1], "BOTTOMRIGHT", 0, -1 * spacing)
				end
			end
		else
			if i == 1 then
				local anchorframe
				if anchor == "focus" and Focus.Bar then
					anchorframe = Focus.Bar
				elseif anchor == "target" and Target.Bar then
					anchorframe = Target.Bar
				else -- L["Player"]
					anchorframe = Player.Bar
				end
				
				if position == "top" then
					direction = 1
					bar:SetPoint("BOTTOM", anchorframe, "TOP", 0, gap)
				elseif position == "bottom" then
					direction = -1
					bar:SetPoint("TOP", anchorframe, "BOTTOM", 0, -1 * gap)
				elseif position == "topright" then
					direction = 1
					bar:SetPoint("BOTTOMRIGHT", anchorframe, "TOPRIGHT", -1 * offset, gap)
				elseif position == "bottomright" then
					direction = -1
					bar:SetPoint("TOPRIGHT", anchorframe, "BOTTOMRIGHT", -1 * offset, -1 * gap)
				elseif position == "topleft" then
					direction = 1
					bar:SetPoint("BOTTOMLEFT", anchorframe, "TOPLEFT", offset, gap)
				elseif position == "bottomleft" then
					direction = -1
					bar:SetPoint("TOPLEFT", anchorframe, "BOTTOMLEFT", offset, -1 * gap)
				elseif position == "leftup" then
					if iconside == "right" and showicons then
						offset = offset + height
					end
					if qpdb.iconposition == "left" and not qpdb.hideicon then
						offset = offset + qpdb.h
					end
					direction = 1
					bar:SetPoint("BOTTOMRIGHT", anchorframe, "BOTTOMLEFT", -1 * offset, gap)
				elseif position == "leftdown" then
					if iconside == "right" and showicons then
						offset = offset + height
					end
					if qpdb.iconposition == "left" and not qpdb.hideicon then
						offset = offset + qpdb.h
					end
					direction = -1
					bar:SetPoint("TOPRIGHT", anchorframe, "TOPLEFT", -3 * offset, -1 * gap)
				elseif position == "rightup" then
					if iconside == "left" and showicons then
						offset = offset + height
					end
					if qpdb.iconposition == "right" and not qpdb.hideicon then
						offset = offset + qpdb.h
					end
					direction = 1
					bar:SetPoint("BOTTOMLEFT", anchorframe, "BOTTOMRIGHT", offset, gap)
				elseif position == "rightdown" then
					if iconside == "left" and showicons then
						offset = offset + height
					end
					if qpdb.iconposition == "right" and not qpdb.hideicon then
						offset = offset + qpdb.h
					end
					direction = -1
					bar:SetPoint("TOPLEFT", anchorframe, "TOPRIGHT", offset, -1 * gap)
				end
			else
				if direction == 1 then
					bar:SetPoint("BOTTOMRIGHT", bars[i-1], "TOPRIGHT", 0, spacing)
				else -- -1
					bar:SetPoint("TOPRIGHT", bars[i-1], "BOTTOMRIGHT", 0, -1 * spacing)
				end
			end
		end
		
		local timetext = bar.timetext
		if db.bufftimetext then
			timetext:Show()
			timetext:ClearAllPoints()
			timetext:SetWidth(width)
			timetext:SetPoint("RIGHT", bar, "RIGHT", -2, 0)
			timetext:SetJustifyH("RIGHT")
		else
			timetext:Hide()
		end
		timetext:SetFont(media:Fetch("font", db.bufffont), db.bufffontsize)
		timetext:SetShadowColor( 0, 0, 0, 1)
		timetext:SetShadowOffset( 0.8, -0.8 )
		timetext:SetTextColor(unpack(db.bufftextcolor))
		timetext:SetNonSpaceWrap(false)
		timetext:SetHeight(height)
		
		local temptext = timetext:GetText()
		timetext:SetText("10.0")
		local normaltimewidth = timetext:GetStringWidth()
		timetext:SetText(temptext)
		
		local text = bar.text
		if db.buffnametext then
			text:Show()
			text:ClearAllPoints()
			text:SetPoint("LEFT", bar, "LEFT", 2, 0)
			text:SetJustifyH("LEFT")
			if db.bufftimetext then
				text:SetWidth(width - normaltimewidth)
			else
				text:SetWidth(width)
			end
		else
			text:Hide()
		end
		text:SetFont(media:Fetch("font", db.bufffont), db.bufffontsize)
		text:SetShadowColor( 0, 0, 0, 1)
		text:SetShadowOffset( 0.8, -0.8 )
		text:SetTextColor(unpack(db.bufftextcolor))
		text:SetNonSpaceWrap(false)
		text:SetHeight(height)
		
		local icon = bar.icon
		if icons then
			icon:Show()
			icon:SetWidth(height-1)
			icon:SetHeight(height-1)
			icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
			icon:ClearAllPoints()
			if iconside == "left" then
				icon:SetPoint("RIGHT", bar, "LEFT", -1, 0)
			else
				icon:SetPoint("LEFT", bar, "RIGHT", 1, 0)
			end
		else
			icon:Hide()
		end
		
		return direction
	end
	function Buff:ApplySettings()
		db = self.db.profile
		if self:IsEnabled() then
			local direction
			if db.targetanchor ~= "free" then
				targetbars[1].Hide = nil
				targetbars[1]:EnableMouse(false)
				targetbars[1]:SetScript("OnDragStart", nil)
				targetbars[1]:SetScript("OnDragStop", nil)
			end
			if db.focusanchor ~= "free" then
				focusbars[1].Hide = nil
				focusbars[1]:EnableMouse(false)
				focusbars[1]:SetScript("OnDragStart", nil)
				focusbars[1]:SetScript("OnDragStop", nil)
			end
			for i, v in pairs(targetbars) do
				direction = apply("target", i, v, db, direction)
			end
			direction = nil
			for i, v in pairs(focusbars) do
				direction = apply("focus", i, v, db, direction)
			end
			self:UpdateBars()
		end
	end
end

