Quantcast
--[[-------------------------------------------------------------------------
  Copyright (c) 2006, Jim Whitehead II <cladhaire@gmail.com>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above
        copyright notice, this list of conditions and the following
        disclaimer in the documentation and/or other materials provided
        with the distribution.
      * Neither the name of PerfectRaid nor the names of its contributors
        may be used to endorse or promote products derived from this software
        without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------]]

local Buffs = PerfectRaid:NewModule("PerfectRaid-Buffs")
local L = PerfectRaidLocals
local utils, frames
local timerFrame

-- Speed up this module as much as possible
local genv = _G
local PerfectRaid = genv.PerfectRaid
local setmetatable = genv.setmetatable
local pairs = genv.pairs
local table = genv.table
local ipairs = genv.ipairs
local select = genv.select
local UnitClass = genv.UnitClass
local UnitRaid = genv.UnitRace
local GetNumGroupMembers = genv.GetNumGroupMembers
local CreateFrame = genv.CreateFrame
local UIParent = genv.UIParent
local GetRaidRosterInfo = genv.GetRaidRosterInfo
local string = genv.string
local rawset = genv.rawset
local UnitBuff = genv.UnitBuff
local UnitDebuff = genv.UnitDebuff
local unpack = genv.unpack
local GetTime = genv.GetTime
local strjoin = genv.strjoin

function Buffs:Initialize()
	PerfectRaid.defaults.profile.buffs = {}

	frames = PerfectRaid.frames
	utils = PerfectRaid.utils
	self:RegisterMessage("DONGLE_PROFILE_CREATED")
	self:RegisterMessage("PERFECTRAID_TAB_CHANGED")
end

function Buffs:Enable()
	self:RegisterEvent("UNIT_AURA")
	self:RegisterEvent("GROUP_ROSTER_UPDATE")

	if GetNumGroupMembers() > 0 then
		self:GROUP_ROSTER_UPDATE()
	end

	self:UpdateBuffTable()

	for unit in pairs(frames) do
		self:UNIT_AURA(nil, unit)
	end

    -- Create a simple OnUpdate timer that triggers an update every second
    local updateInterval = 1.0  -- How often the OnUpdate code will run (in seconds)
    local elapsedCount = 0
	timerFrame = CreateFrame("Frame", "PraidBuffTimerFrame", UIParent);
	timerFrame:SetScript("OnUpdate", function(frame, elapsed)
        elapsedCount = elapsedCount + elapsed
        if elapsedCount > updateInterval then
            self:FullUpdate()
            timerFrame.TimeSinceLastUpdate = elapsedCount - updateInterval;
        end
    end)
end

function Buffs:FullUpdate()
    for unit in pairs(frames) do
        self:UNIT_AURA(nil, unit)
    end
end

function Buffs:DONGLE_PROFILE_CREATED(event, db, addon, svname, profileKey)
	if svname ~= "PerfectRaidDB" then return end
	self:PrintF(L["Adding default buffs to new profile \"%s\""], profileKey)
	local buffs = db.profile.buffs
	for k,v in ipairs(self.defaults) do
		if not v.disabled then
			self:AddDefaultBuff(buffs, v)
		end
	end
end

function Buffs:PERFECTRAID_TAB_CHANGED(old,new)
	if PROptions_Buffs_Edit and PROptions_Buffs_Edit:IsVisible() then
		self.options:FadeOut(PROptions_Buffs_Edit)
	end
end

local raidLookup = {}
for i=1,40 do
	raidLookup[i] = "raid"..i
end
function Buffs:GROUP_ROSTER_UPDATE()
	if GetNumGroupMembers() > 0 then
		for i=1,GetNumGroupMembers() do
			local group = select(3, GetRaidRosterInfo(i))
			local unit = raidLookup[i]
			if UnitIsUnit(unit, "player") then raidLookup.player = group end
			raidLookup[unit] = group
		end
	end
end

function Buffs:ConfigureButton(button)
	local font = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	button.aura = font
end

function Buffs:UpdateButtonLayout(button, options)
	button.aura:ClearAllPoints()

	if options.alignright then
		button.aura:SetPoint("RIGHT", button.leftbox, "RIGHT", -2, 0)
	else
		button.aura:SetPoint("LEFT", button.rightbox, "LEFT", 2, 0)
	end
end

function Buffs:ShowButton(button)
	local unit = button:GetAttribute("unit")
	self:UNIT_AURA(nil, unit)
end

local band = bit.band
local buffs = {}
local buffcache = {}
local mybuffs = {}
local mymask = {}
local buffexpiry = {}

local BIT_CURSE = 1
local BIT_MAGIC = 2
local BIT_POISON = 4
local BIT_DISEASE = 8

local debuffstatus = setmetatable({}, {__index=function(t,k) rawset(t,k,0); return 0 end})

local work = {}
local patterns = setmetatable({}, {__index=function(t,k)
											   local str = string.rep("%s ", k):sub(1,-2)
											   rawset(t,k,str)
											   return str
										   end})

function Buffs:UNIT_AURA(event, unit)
	if not frames[unit] then return end

	for k,v in pairs(buffcache) do buffcache[k] = nil end
	for k,v in pairs(mybuffs) do mybuffs[k] = nil end

	for i=1,40 do
		local name,rank,texture,count,dispelType,duration,expires,caster,stealable = UnitBuff(unit, i)
		if not name then break end
		if caster == "player" then
			mybuffs[name] = true
			buffexpiry[name] = expires
		end

		buffcache[name] = (buffcache[name] or 0) + 1
	end

	local debuffType
	for i=1,40 do
		local name,rank,texture,count,dispelType,duration,expires,caster = UnitDebuff(unit, i)
		if not name and not dispelType then break end

		buffcache[name] = (buffcache[name] or 0) + 1
		buffexpiry[name] = expires

		if dispelType and name ~= dispelType then
			buffcache[dispelType] = (buffcache[dispelType] or 0) + 1
			debuffType = dispelType
		end
	end

	-- Raise any relevant debuff gained/lost messages
	local status = debuffstatus[unit]

	-- Disease
	if buffcache.Disease and band(status, BIT_DISEASE) == 0 then
		status = status + BIT_DISEASE
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_GAINED", unit, "Disease")
	elseif not buffcache.Disease and band(status, BIT_DISEASE) > 0 then
		status = status - BIT_DISEASE
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_LOST", unit, "Disease")
	end

	if buffcache.Poison and band(status, BIT_POISON) == 0 then
		status = status + BIT_POISON
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_GAINED", unit, "Poison")
	elseif not buffcache.Poison and band(status, BIT_POISON) > 0 then
		status = status - BIT_POISON
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_LOST", unit, "Poison")
	end

	if buffcache.Magic and band(status, BIT_MAGIC) == 0 then
		status = status + BIT_MAGIC
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_GAINED", unit, "Magic")
	elseif not buffcache.Magic and band(status, BIT_MAGIC) > 0 then
		status = status - BIT_MAGIC
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_LOST", unit, "Magic")
	end

	if buffcache.Curse and band(status, BIT_CURSE) == 0 then
		status = status + BIT_CURSE
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_GAINED", unit, "Curse")
	elseif not buffcache.Curse and band(status, BIT_CURSE) > 0 then
		status = status - BIT_CURSE
		self:TriggerMessage("PERFECTRAID_DEBUFFTYPE_LOST", unit, "Curse")
	end

	-- Update the status
	debuffstatus[unit] = status
	for k,v in pairs(work) do work[k] = nil end

	for i,entry in ipairs(buffs) do
		local checkcond = false

		-- Determine which spell name we matched
		local buffname
		if buffcache[entry.buffname] then
			buffname = entry.buffname
		elseif buffcache[entry.groupname or "nil"] then
			buffname = entry.groupname
		end

		-- How many stacks are there?
		local class = select(2, UnitClass(unit))
		local conds = self.conditions
		local group = raidLookup[unit]
		local mine = mybuffs[buffname]

		if entry.missing then
			if not buffname then
				checkcond = true
			end
		else
			if buffname then
				if (entry.mine and mine) or not entry.mine then
					checkcond = true
				end
			end
		end

		-- Handle the condition checking for the buff, taking strictness into account
		if checkcond then
			if entry.strict then
				-- Set up a boolean value so we can trip it if a filter fails
				local pass = true

				for i,name in pairs(entry.cond) do
					if conds[name] and not conds[name](unit, class, group, mine) then
						pass = false
					end
				end

				if pass then
					self:CreateBuffEntry(buffname, entry)
				end
			else
				-- Simply iterate each of the conditions, and break when we match
				for i,name in pairs(entry.cond) do
					if conds[name] and conds[name](unit, class, group, mine) then
						self:CreateBuffEntry(buffname, entry)
						break
					end
				end
			end
		end
	end

	for frame in pairs(frames[unit]) do
		frame.aura:SetFormattedText(patterns[#work], unpack(work))
	end
end

function Buffs:CreateBuffEntry(buffname, entry)
	local expires = buffexpiry[buffname]
	local num = buffcache[buffname]
	local mine = mybuffs[buffname]

	if num and num > 1 and not entry.onlymine then
		local num = num
		-- If the buff is mine, and it should be masked
		-- A buff is masked when there is a "mine" filter set for it
		if mine and not mymask[buffname] then
			num = num - 1
		end

		if (expires and entry.showexpiry) then
			work[#work + 1] = string.format("%s(%d)(%d)", entry.colortext, num, -1 * (GetTime() - expires))
		else
			work[#work + 1] = string.format("%s(%d)", entry.colortext, num)
		end
	else
		if (entry.onlymine and not mine) or (not entry.onlymine and mymask[buffname]) then
			-- Don't show
		else
			if (expires and entry.showexpiry) then
				work[#work + 1] = string.format("%s(%d)", entry.colortext, -1 * (GetTime()-expires))
			else
				work[#work + 1] = entry.colortext
			end
		end
	end
end

function Buffs:CreateOptions(opt)
	self.options = opt
	local options = CreateFrame("Frame", "PROptions_Buffs", PROptions)
	opt:AddOptionsTab(L["Buffs/Debuffs"], options)

	local num_entries = 19
	local scrollframe = opt:CreateListFrame(options, num_entries)

	-- Set up double-click handlers
	local function OnDoubleClick(button)
		button:Click()
		self:EditEntry()
	end

	for i=1,num_entries do
		local button = scrollframe.entries[i]
		button:SetScript("OnDoubleClick", OnDoubleClick)
	end

	local update = function()
		local list = PerfectRaid.db.profile.buffs

		local idx,button
		local offset = FauxScrollFrame_GetOffset(scrollframe)
		FauxScrollFrame_Update(scrollframe, #list, num_entries, 20)
		for i=1,num_entries do
			idx = offset + i
			local button = scrollframe.entries[i]
			if idx <= #list then
				local entry = list[idx]
				local display = entry.buffname

				if entry.groupname then
					display = display .. "/"..entry.groupname
				end
				display = display .. " (|cFF"..entry.color..entry.disptext.."|r)"

				if entry.disabled then
					display = display .. L[" *** DISABLED ***"]
				end

				button.line1:SetText(display)
				button:Show()
				if options.selected == idx then
					button:SetChecked(true)
				else
					button:SetChecked(false)
				end
			else
				button:Hide()
			end
		end
	end

	scrollframe.update = update

	scrollframe:SetScript("OnVerticalScroll", function(frame, value)
		FauxScrollFrame_OnVerticalScroll(frame, value, 20, update)
		self:EnableButtons()
	end)
	scrollframe:SetScript("OnShow", function()
		scrollframe.update()
		self:EnableButtons()
	end)

	local postClick = function()
		self:EnableButtons()
	end

	for i,frame in ipairs(scrollframe.entries) do
		frame:SetScript("PostClick", postClick)
	end

	local delete = CreateFrame("Button", "PRBuffs_Delete", options, "PRButtonTemplate")
	delete:SetText(L["Delete"])
	delete:SetPoint("BOTTOMRIGHT", 0, 5)
	delete:SetScript("OnClick", function() self:DeleteEntry() end)
	delete:Show()

	local edit = CreateFrame("Button", "PRBuffs_Edit", options, "PRButtonTemplate")
	edit:SetText(L["Edit"])
	edit:SetPoint("BOTTOMRIGHT", delete, "BOTTOMLEFT", -10, 0)
	edit:SetScript("OnClick", function() self:EditEntry() end)
	edit:Show()

	local add = CreateFrame("Button", "PRBuffs_Add", options, "PRButtonTemplate")
	add:SetText(L["Add"])
	add:SetPoint("BOTTOMRIGHT", edit, "BOTTOMLEFT", -10, 0)
	add:SetScript("OnClick", function() self:AddEntry() end)
	add:Show()

	local disable = CreateFrame("Button", "PRBuffs_Disable", options, "PRButtonTemplate")
	disable:SetText(L["Disable"])
	disable:SetPoint("BOTTOMRIGHT", add, "BOTTOMLEFT", -10, 0)
	disable:SetScript("OnClick", function() self:DisableEntry() end)
	disable:Show()

	local up = CreateFrame("Button", "PRBuffs_Up", options, "PRButtonTemplate")
	up:SetWidth(70)
	up:SetText(L["Up"])
	up:SetPoint("BOTTOMRIGHT", disable, "BOTTOMLEFT", -10, 0)
	up:SetScript("OnClick", function() self:MoveEntry("UP") end)
	up:Show()

	local down = CreateFrame("Button", "PRBuffs_Down", options, "PRButtonTemplate")
	down:SetWidth(70)
	down:SetText(L["Down"])
	down:SetPoint("BOTTOMRIGHT", up, "BOTTOMLEFT", -10, 0)
	down:SetScript("OnClick", function() self:MoveEntry("DOWN") end)
	down:Show()

	self:CreateEditFrame(options)
end

function Buffs:CreateEditFrame(parent)
	local frame = CreateFrame("Frame", "PROptions_Buffs_Edit", PROptions)
	local name = "PROptions_Buffs_Edit"
--	frame:SetPoint("TOPLEFT", 15, -25)
--	frame:SetPoint("BOTTOMRIGHT", -15, 15)
	frame:SetAllPoints(PROptions_Buffs)
	frame:SetFrameLevel(frame:GetFrameLevel() + 1)
	frame:Hide()

	local buffname = CreateFrame("EditBox", name.."BuffName", frame, "InputBoxTemplate")
	local font = buffname:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	font:SetText(L["Buff Name:"])
	font:SetPoint("BOTTOMLEFT", buffname, "TOPLEFT", 0, 3)
	buffname:SetPoint("TOPLEFT", 0, -10)
	buffname:SetAutoFocus(nil)
	buffname:SetWidth(180)
	buffname:SetHeight(20)
	buffname:Show()
	frame.buffname = buffname

	local groupname = CreateFrame("EditBox", name.."GroupName", frame, "InputBoxTemplate")
	local font = groupname:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	font:SetText(L["Group Buff Name:"])
	font:SetPoint("BOTTOMLEFT", groupname, "TOPLEFT", 0, 3)
	groupname:SetPoint("TOPLEFT", 220, -10)
	groupname:SetAutoFocus(nil)
	groupname:SetWidth(180)
	groupname:SetHeight(20)
	groupname:Show()
	frame.groupname = groupname

	local disptext = CreateFrame("EditBox", name.."DispText", frame, "InputBoxTemplate")
	local font = disptext:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	font:SetText(L["Display Text:"])
	font:SetPoint("BOTTOMLEFT", disptext, "TOPLEFT", 0, 3)
	disptext:SetPoint("TOPLEFT", 0, -50)
	disptext:SetAutoFocus(nil)
	disptext:SetWidth(180)
	disptext:SetHeight(20)
	disptext:Show()
	frame.disptext = disptext

	-- Color Swatch
	local function colorSwatchCancel()
		local self = ColorPickerFrame.object;
		local r, g, b = self.r, self.g, self.b;
		self.normalTexture:SetVertexColor(r, g, b);
	end

	local function colorSwatchColor()
		local self = ColorPickerFrame.object;
		local r, g, b = ColorPickerFrame:GetColorRGB();
		self.normalTexture:SetVertexColor(r, g, b);
		self.color[1] = r
		self.color[2] = g
		self.color[3] = b
		disptext:SetTextColor(r,g,b)
	end

	local function colorSwatchOpacity()
		local self = ColorPickerFrame.object;
		local a = OpacitySliderFrame:GetValue();
	end

	local function colorSwatchShow(self)
		local r, g, b, a;
		self.color = self.color or {1,1,1}

		local color = self.color;
		if ( color ) then
			r, g, b, a = unpack(color);
		end

		self.r, self.g, self.b = r, g, b
		self.opacityFunc = colorSwatchOpacity;
		self.swatchFunc = colorSwatchColor;
		self.cancelFunc = colorSwatchCancel;

		ColorPickerFrame.object = self;
		UIDropDownMenuButton_OpenColorPicker(self);
		ColorPickerFrame:SetFrameStrata("TOOLTIP");
		ColorPickerFrame:Raise();
	end

	local function colorSwatchOnClick(self)
		CloseMenus();
		colorSwatchShow(self);
	end

	local function colorSwatchOnEnter(self)
		self.bg:SetVertexColor(1, 0.82, 0);
	end

	local function colorSwatchOnLeave(self)
		self.bg:SetVertexColor(1, 1, 1);
	end

	local swatch = CreateFrame("Button", "PRColorSelect", frame);
	swatch:SetHeight(20) swatch:SetWidth(20)
	swatch:SetPoint("LEFT", disptext, "RIGHT", 5, 0)
	local bg = swatch:CreateTexture(nil, "BACKGROUND");
	local normalTexture = swatch:CreateTexture(nil, "ARTWORK");
	frame.color = swatch

	normalTexture:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch");
	normalTexture:SetAllPoints(swatch);
	swatch:SetNormalTexture(normalTexture);
	bg:SetTexture(.2,.2,.2);
	bg:SetPoint("TOPLEFT", swatch, 1, -1);
	bg:SetPoint("BOTTOMRIGHT", swatch, 0, 1);

	normalTexture:SetVertexColor(1,1,1)

	swatch.bg, swatch.normalTexture = bg, normalTexture;
	swatch.object, swatch.hasAlpha = self, 1

	swatch:SetScript("OnLeave", colorSwatchOnLeave);
	swatch:SetScript("OnEnter", colorSwatchOnEnter);
	swatch:SetScript("OnClick", colorSwatchOnClick);

	buffname:SetScript("OnTabPressed", function() if IsShiftKeyDown() then disptext:SetFocus() else groupname:SetFocus() end end)
	groupname:SetScript("OnTabPressed", function() if IsShiftKeyDown() then buffname:SetFocus() else disptext:SetFocus() end end)
	disptext:SetScript("OnTabPressed", function() if IsShiftKeyDown() then groupname:SetFocus() else buffname:SetFocus() end end)

	local function makeCheck(value)
		local button = CreateFrame("CheckButton", "PRBuffCond_"..value, PROptions_Buffs_Edit, "PRCheckTemplate")
		button.Label:SetText(value)
		button.value = value
		return button
	end

	PROptions_Buffs_Edit.checks = {}
	local checks = PROptions_Buffs_Edit.checks

	-- Build condition checkboxes

	checks[1] = makeCheck(self.conditions[1])
	checks[1]:SetPoint("TOPLEFT", disptext, "BOTTOMLEFT", 0, -20)
	checks[1]:Show()

	for i=2,6 do
		checks[i] = makeCheck(self.conditions[i])
		checks[i]:SetPoint("TOPLEFT", checks[i-1], "TOPRIGHT", 60, 0)
		checks[i]:Show()
	end

	checks[7] = makeCheck(self.conditions[7])
	checks[7]:SetPoint("TOPLEFT", checks[1], "BOTTOMLEFT", 0, -10)
	checks[7]:Show()

	for i=8,12 do
		checks[i] = makeCheck(self.conditions[i])
		checks[i]:SetPoint("TOPLEFT", checks[i-1], "TOPRIGHT", 60, 0)
		checks[i]:Show()
	end

	checks[13] = makeCheck(self.conditions[13])
	checks[13]:SetPoint("TOPLEFT", checks[7], "BOTTOMLEFT", 0, -10)
	checks[13]:Show()

	for i=14,18 do
		checks[i] = makeCheck(self.conditions[i])
		checks[i]:SetPoint("TOPLEFT", checks[i-1], "TOPRIGHT", 60, 0)
		checks[i]:Show()
	end

	checks[19] = makeCheck(self.conditions[19])
	checks[19]:SetPoint("TOPLEFT", checks[13], "BOTTOMLEFT", 0, -10)
	checks[19]:Show()

	for i=20,22 do
		checks[i] = makeCheck(self.conditions[i])
		checks[i]:SetPoint("TOPLEFT", checks[i-1], "TOPRIGHT", 60, 0)
		checks[i]:Show()
	end

	local cancel = CreateFrame("Button", "PRBuffs_EditCancel", PROptions_Buffs_Edit, "PRButtonTemplate")
	cancel:SetText(L["Cancel"])
	cancel:SetPoint("BOTTOMRIGHT", 0, 5)
	cancel:SetScript("OnClick", function() self:CancelEntry() end)
	cancel:Show()

	local save = CreateFrame("Button", "PRBuffs_EditSave", PROptions_Buffs_Edit, "PRButtonTemplate")
	save:SetText(L["Save"])
	save:SetPoint("BOTTOMRIGHT", cancel, "BOTTOMLEFT", -10, 0)
	save:SetScript("OnClick", function() self:SaveEntry() end)
	save:Show()

	local missing = CreateFrame("CheckButton", "PRBuffs_Missing", PROptions_Buffs_Edit, "PRCheckTemplate")
	missing.Label:SetText(L["Only show if this buff is missing"])
	missing:SetPoint("BOTTOMLEFT", 0, 10)
	missing:Show()
	frame.missing = missing

	local disabled = CreateFrame("CheckButton", "PRBuffs_Disabled", PROptions_Buffs_Edit, "PRCheckTemplate")
	disabled.Label:SetText(L["Do not check this buff (Disable)"])
	disabled:SetPoint("BOTTOMLEFT", missing, "TOPLEFT", 0, 10)
	disabled:Show()
	frame.disabled = disabled

	local strict = CreateFrame("CheckButton", "PRBuffs_Strict", PROptions_Buffs_Edit, "PRCheckTemplate")
	strict.Label:SetText(L["Make filters strict"])
	strict:SetPoint("TOPLEFT", disabled, "TOPRIGHT", 200, 0)
	strict:Show()
	frame.strict = strict

	local onlymine = CreateFrame("CheckButton", "PRBuffs_OnlyMine", PROptions_Buffs_Edit, "PRCheckTemplate")
	onlymine.Label:SetText(L["Only show my buffs"])
	onlymine:SetPoint("TOPLEFT", strict, "BOTTOMLEFT", 0, -10)
	onlymine:Show()
	frame.onlymine = onlymine

	local showexpiry = CreateFrame("CheckButton", "PRBuffs_ShowExpiry", PROptions_Buffs_Edit, "PRCheckTemplate")
	showexpiry.Label:SetText(L["Show expiry"])
	showexpiry:SetPoint("TOPLEFT", strict, "TOPRIGHT", 150, 0)
	showexpiry:Show()
	frame.showexpiry = showexpiry

	local dropdown = CreateFrame("Frame", "PRBuffs_Dropdown", PROptions_Buffs_Edit, "UIDropDownMenuTemplate")
	dropdown:SetID(1)
	dropdown:SetPoint("BOTTOMRIGHT", -115, 80)
	dropdown:SetScript("OnShow", function() self:DropDown_OnShow() end)

	PRBuffs_DropdownButton:SetScript("OnClick", function() ToggleDropDownMenu(nil, nil, PRBuffs_Dropdown, "cursor") end)
	UIDropDownMenu_SetText(dropdown, L["Auto-fill Default"])
end

function Buffs:FillEntry(entry)
	local options = PROptions_Buffs_Edit
	options.buffname:SetText(entry.buffname)
	options.groupname:SetText(entry.groupname or "")
	options.disptext:SetText(entry.disptext)
	options.disptext:SetTextColor(utils.HexToRGBPerc(entry.color))
	PRColorSelect.normalTexture:SetVertexColor(utils.HexToRGBPerc(entry.color))

	local cond = {string.split(",", entry.conds)}
	for k,v in ipairs(cond) do
		cond[v] = true
	end
	for idx,button in ipairs(PROptions_Buffs_Edit.checks) do
		if cond[button.value] then
			button:SetChecked(true)
		else
			button:SetChecked(false)
		end
	end

	options.missing:SetChecked(entry.missing)
	options.disabled:SetChecked(entry.disabled)
	options.strict:SetChecked(entry.strict)
	options.onlymine:SetChecked(entry.onlymine)
	options.showexpiry:SetChecked(entry.showexpiry)
end

function Buffs:EditEntry()
	local scrollframe = PROptions_BuffsScrollFrame
	local offset = FauxScrollFrame_GetOffset(scrollframe)
	local selected = PROptions_Buffs.selected
	local entry = PerfectRaid.db.profile.buffs[selected]

	local options = PROptions_Buffs_Edit
	options.editEntry = entry
	self.options:FadeOut(PROptions_Buffs)
	self.options:FadeIn(options)
	self:FillEntry(entry)
end

function Buffs:MoveEntry(direction)
	local selected = PROptions_Buffs.selected
	local destination

	if direction == "UP" then
		destination = selected - 1
	else
		destination = selected + 1
	end

	local tmp = PerfectRaid.db.profile.buffs[destination]
	PerfectRaid.db.profile.buffs[destination] = PerfectRaid.db.profile.buffs[selected]
	PerfectRaid.db.profile.buffs[selected] = tmp

	PROptions_Buffs.selected = destination
	PROptions_BuffsScrollFrame.update()
	self:EnableButtons()
	self:UpdateBuffTable()
end

function Buffs:DisableEntry()
	local scrollframe = PROptions_BuffsScrollFrame
	local offset = FauxScrollFrame_GetOffset(scrollframe)
	local selected = PROptions_Buffs.selected
	local entry = PerfectRaid.db.profile.buffs[selected]
	entry.disabled = not entry.disabled

	scrollframe.update()
	self:EnableButtons()
	self:UpdateBuffTable()
end

function Buffs:AddEntry()
	local options = PROptions_Buffs_Edit
	self.options:FadeOut(PROptions_Buffs)
	self.options:FadeIn(options)
	options.buffname:SetText("")
	options.groupname:SetText("")
	options.disptext:SetText("")
	options.disptext:SetTextColor(1,1,1)
	PRColorSelect.normalTexture:SetVertexColor(1,1,1)

	for idx,button in ipairs(PROptions_Buffs_Edit.checks) do
		button:SetChecked(false)
	end
	options.missing:SetChecked(false)
	options.disabled:SetChecked(false)
	options.strict:SetChecked(false)
	options.onlymine:SetChecked(false)
	options.showexpiry:SetChecked(false)
	options.buffname:SetFocus()
end

function Buffs:DeleteEntry()
	local scrollframe = PROptions_BuffsScrollFrame
	local offset = FauxScrollFrame_GetOffset(scrollframe)
	local selected = PROptions_Buffs.selected

	table.remove(PerfectRaid.db.profile.buffs, selected)
	PROptions_Buffs.selected = nil

	scrollframe.update()
	self:EnableButtons()
	self:UpdateBuffTable()
end

function Buffs:UpdateBuffTable()
	for k,v in pairs(buffs) do buffs[k] = nil end
	for k,v in pairs(mymask) do mymask[k] = nil end

	for idx,entry in ipairs(PerfectRaid.db.profile.buffs) do
		if not entry.disabled then
			local tbl = {}
			tbl.buffname = entry.buffname
			tbl.groupname = entry.groupname
			tbl.colortext = "|cFF"..entry.color..tostring(entry.disptext).."|r"
			tbl.missing = entry.missing
			tbl.strict = entry.strict
			tbl.cond = {string.split(",", entry.conds)}
			tbl.onlymine = entry.onlymine
			tbl.showexpiry = entry.showexpiry

			if tbl.onlymine then
				mymask[tbl.buffname] = true
				if entry.groupname then
					mymask[tbl.groupname] = true
				end
			end

			table.insert(buffs, tbl)
		end
	end

	for unit in pairs(frames) do
		self:UNIT_AURA(nil, unit)
	end
end

function Buffs:SaveEntry()
	local frame = PROptions_Buffs_Edit

	local buffname = frame.buffname:GetText()
	local groupname = frame.groupname:GetText()
	local disptext = frame.disptext:GetText()

	local err
	if buffname == "" then
		err = "You must supply a buff name"
	elseif disptext == "" then
		err = "You must supply a display text"
	end

	if err then
		StaticPopupDialogs["PR_BUFF_SAVE_ERROR"].text = err
		StaticPopup_Show("PR_BUFF_SAVE_ERROR")
		return
	end

	if groupname == "" then
		groupname = nil
	end

	local options = PROptions_Buffs_Edit
	local entry = options.editEntry or {}

	entry.buffname = buffname
	entry.groupname = groupname
	entry.disptext = disptext

	local work = {}
	for idx,button in ipairs(PROptions_Buffs_Edit.checks) do
		if button:GetChecked() then
			table.insert(work, button.value)
		end
	end

	local conds = strjoin(",", unpack(work))
	entry.conds = conds
	entry.missing = frame.missing:GetChecked()
	entry.disabled = frame.disabled:GetChecked()
	entry.strict = frame.strict:GetChecked()
	entry.onlymine = frame.onlymine:GetChecked()
	entry.showexpiry = frame.showexpiry:GetChecked()

	local color = utils.RGBPercToHex(frame.disptext:GetTextColor())
	entry.color = color
	if not options.editEntry then
		table.insert(PerfectRaid.db.profile.buffs, entry)
	end

	options.editEntry = nil

	self:CancelEntry()
	PROptions_BuffsScrollFrame.update()
	self:UpdateBuffTable()
end

function Buffs:CancelEntry()
	local options = PROptions_Buffs_Edit
	self.options:FadeOut(options)
	options.editEntry = nil
	self.options:FadeIn(PROptions_Buffs)
end

function Buffs:EnableButtons()
	local selected = PROptions_Buffs.selected
	if selected then
		PRBuffs_Edit:Enable()
		PRBuffs_Delete:Enable()
		PRBuffs_Disable:Enable()
		PRBuffs_Up:Enable()
		PRBuffs_Down:Enable()

		-- Change enable/disable
		local scrollframe = PROptions_BuffsScrollFrame
		local offset = FauxScrollFrame_GetOffset(scrollframe)
		local selected = PROptions_Buffs.selected
		local entry = PerfectRaid.db.profile.buffs[selected]

		if selected == 1 then
			PRBuffs_Up:Disable()
		end

		if selected == #PerfectRaid.db.profile.buffs then
			PRBuffs_Down:Disable()
		end

		if entry.disabled then
			PRBuffs_Disable:SetText(L["Enable"])
		else
			PRBuffs_Disable:SetText(L["Disable"])
		end
	else
		PRBuffs_Edit:Disable()
		PRBuffs_Delete:Disable()
		PRBuffs_Disable:Disable()
		PRBuffs_Up:Disable()
		PRBuffs_Down:Disable()
	end
end

Buffs.conditions = {
	["All"] = function(u,c,g) return true end,
	["Warrior"] = function(u,c,g) return c == "WARRIOR" end,
	["Death Knight"] = function(u,c,g) return c == "DEATHKNIGHT" end,
	["Monk"] = function(u,c,g) return c == "MONK" end,
	["Priest"] = function(u,c,g) return c == "PRIEST" end,
	["Druid"] = function(u,c,g) return c == "DRUID" end,
	["Paladin"] = function(u,c,g) return c == "PALADIN" end,
	["Shaman"] = function(u,c,g) return c == "SHAMAN" end,
	["Hunter"] = function(u,c,g) return c == "HUNTER" end,
	["Rogue"] = function(u,c,g) return c == "ROGUE" end,
	["Warlock"] = function(u,c,g) return c == "WARLOCK" end,
	["Mage"] = function(u,c,g) return c == "MAGE" end,
	["Mana"] = function(u,c,g) return c == "DRUID" or c == "PRIEST" or c == "PALADIN" or c == "SHAMAN" or c == "MAGE" or c == "WARLOCK" or c == "HUNTER" end,
	["Healer"] = function(u,c,g) return c == "PRIEST" or c == "SHAMAN" or c == "DRUID" or c == "PALADIN" end,
	["Group 1"] = function(u,c,g) return g == 1 end,
	["Group 2"] = function(u,c,g) return g == 2 end,
	["Group 3"] = function(u,c,g) return g == 3 end,
	["Group 4"] = function(u,c,g) return g == 4 end,
	["Group 5"] = function(u,c,g) return g == 5 end,
	["Group 6"] = function(u,c,g) return g == 6 end,
	["Group 7"] = function(u,c,g) return g == 7 end,
	["Group 8"] = function(u,c,g) return g == 8 end,
	["MyGroup"] = function(u,c,g) return g == raidLookup.player end,
}

local work = {}
for k,v in pairs(Buffs.conditions) do
	table.insert(work, k)
end
table.sort(work)

for k,v in ipairs(work) do
	Buffs.conditions[k] = v
end

StaticPopupDialogs["PR_BUFF_SAVE_ERROR"] = {
	text = "",
	button1 = TEXT(OKAY),
	OnAccept = function()
	end,
	timeout = 0,
	hideOnEscape = 1
}

local click_func = function(frame) Buffs:DropDown_OnClick(frame) end
local init_func = function() Buffs:DropDown_Initialize() end

function Buffs:DropDown_Initialize()
    local info = {}

    for k,v in pairs(self.defaults) do
        info = {}
        info.text = string.format("%s (|cFF%s%s|r)", v.buffname, v.color, v.disptext)
        info.value = v
        info.func = click_func
        UIDropDownMenu_AddButton(info)
	end
end

function Buffs:DropDown_OnClick(frame)
	local entry = frame.value
	self:FillEntry(entry)
end

function Buffs:DropDown_OnShow(this)
	table.sort(self.defaults, function(a,b) return a.buffname < b.buffname end)
	UIDropDownMenu_Initialize(PRBuffs_Dropdown, init_func);
end

function Buffs:AddDefaultBuff(profile, entry)
	local tbl = {}
	for k,v in pairs(entry) do tbl[k] = v end
	tbl.disabled = nil
	table.insert(profile, tbl)
end

local class = select(2, UnitClass("player"))
local race = select(2, UnitRace("player"))

Buffs.defaults = {
	{
		conds = "All",
		buffname = L["Power Word: Fortitude"],
		groupname = L["Prayer of Fortitude"],
		disptext = L["STATUS_FORT"],
		color = "FFFFFF",
		missing = true,
		disabled = (class ~= "PRIEST"),
	},
	{
		conds = "All",
		buffname = L["Power Word: Shield"],
		disptext = L["STATUS_PWS"],
		color = "FFD800",
		disabled = (class ~= "PRIEST"),
	},
	{
		conds = "All",
		buffname = L["Weakened Soul"],
		disptext = L["STATUS_WEAKENEDSOUL"],
		color = "FF5500",
		disabled = (class ~= "PRIEST"),
	},
	{
		conds = "All",
		buffname = L["Renew"],
		disptext = L["STATUS_RENEW"],
		color = "00FF10",
        showexpiry = true,
		disabled = (class ~= "PRIEST"),
	},
	{
		conds = "All",
		buffname = L["Shadow Protection"],
		groupname = L["Prayer of Shadow Protection"],
		disptext = L["STATUS_SHADOWPROT"],
		missing = true,
		color = "9900FF",
		disabled = (class ~= "PRIEST"),
	},
	{
		conds = "All",
		buffname = L["Divine Spirit"],
		groupname = L["Prayer of Spirit"],
		disptext = L["STATUS_DIVINESPIRIT"],
		missing = true,
		color = "9900FF",
		disabled = (class ~= "PRIEST"),
	},
	{
		conds = "All",
		buffname = L["Power Infusion"],
		disptext = L["STATUS_POWERINFUSION"],
		color = "00FF33",
	},
	{
		conds = "All",
		buffname = L["Fear Ward"],
		disptext = L["STATUS_FEARWARD"],
		color = "9900FF",
		disabled = (class ~= "PRIEST" and race == "Dwarf"),
	},
	{
		conds = "All",
		buffname = L["Prayer of Mending"],
		disptext = L["STATUS_PRAYERMEND"],
		color = "FFCF7F",
		disabled = (class ~= "PRIEST"),
	},
	-- Druid specific spells
	{
		conds = "All",
		buffname = L["Mark of the Wild"],
		groupname = L["Gift of the Wild"],
		disptext = L["STATUS_MOTW"],
		color = "BC64AA",
		missing = true,
		disabled = (class ~= "DRUID"),
	},
	{
		conds = "All",
		buffname = L["Rejuvenation"],
		disptext = L["STATUS_REJUV"],
		color = "bc64aa",
        showexpiry = true,
		disabled = (class ~= "DRUID"),
	},
	{
		conds = "All",
		buffname = L["Lifebloom"],
		disptext = L["STATUS_LIFEBLOOM"],
		color = "50fe37",
		disabled = (class ~= "DRUID"),
	},
	{
		conds = "All",
		buffname = L["Regrowth"],
		disptext = L["STATUS_REGROWTH"],
		color = "00FF10",
        showexpiry = true,
		disabled = (class ~= "DRUID"),
	},
	{
		conds = "All",
		buffname = L["Thorns"],
		disptext = L["STATUS_THORNS"],
		color = "aa6644",
		disabled = (class ~= "DRUID"),
	},
	{
		conds = "Mana",
		buffname = L["Innervate"],
		disptext = L["STATUS_INNERVATE"],
		color = "00FF33",
	},
	{
		conds = "All",
		buffname = L["Wild Growth"],
		disptext = L["STATUS_WILD_GROWTH"],
		color = "01796F",
	},
	-- Paladin Buffs
	{
		conds = "Melee",
		buffname = L["Blessing of Might"],
		groupname = L["Greater Blessing of Might"],
		disptext = L["STATUS_BLESSINGMIGHT"],
		color = "F48CBA",
		missing = true,
		disabled = (class ~= "PALADIN"),
	},
	{
		conds = "Mana",
		buffname = L["Blessing of Wisdom"],
		groupname = L["Greater Blessing of Wisdom"],
		disptext = L["STATUS_BLESSINGWISDOM"],
		color = "F48CBA",
		missing = true,
		disabled = (class ~= "PALADIN"),
	},
	{
		conds = "NonTank",
		buffname = L["Blessing of Salvation"],
		groupname = L["Greater Blessing of Salvation"],
		disptext = L["STATUS_BLESSINGSALVATION"],
		color = "F48CBA",
		missing = true,
		disabled = (class ~= "PALADIN"),
	},
	{
		conds = "All",
		buffname = L["Blessing of Light"],
		groupname = L["Greater Blessing of Light"],
		disptext = L["STATUS_BLESSINGLIGHT"],
		color = "F48CBA",
		missing = true,
		disabled = (class ~= "PALADIN"),
	},
	{
		conds = "All",
		buffname = L["Blessing of Sanctuary"],
		groupname = L["Greater Blessing of Sanctuary"],
		disptext = L["STATUS_BLESSINGSANCTUARY"],
		color = "F48CBA",
		missing = true,
		disabled = (class ~= "PALADIN"),
	},
	{
		conds = "All",
		buffname = L["Blessing of Kings"],
		groupname = L["Greater Blessing of Kings"],
		disptext = L["STATUS_BLESSINGKINGS"],
		color = "F48CBA",
		missing = true,
		disabled = (class ~= "PALADIN"),
	},
	-- Mage Buffs
	{
		conds = "Mana",
		buffname = L["Arcane Intellect"],
		groupname = L["Arcane Brilliance"],
		disptext = L["STATUS_ARCANEINT"],
		color = "3399FF",
		missing = true,
		disabled = (class ~= "MAGE"),
	},
	-- Warlock Buffs
	{
		conds = "All",
		buffname = L["Soulstone Resurrection"],
		disptext = L["STATUS_SOULSTONE"],
		color = "CA21FF",
	},
	-- Misc Debuffs
	{
		conds = "All",
		buffname = L["Mortal Strike"],
		disptext = L["STATUS_MORTALSTRIKE"],
		color = "FF1111",
	},
	-- Debuffs.  These should not be localized.
	{
		conds = "All",
		buffname = "Curse",
		disptext = L["STATUS_CURSE"],
		color = "9900FF",
		disabled = (class ~= "MAGE" and class ~= "DRUID")
	},
	{
		conds = "All",
		buffname = "Poison",
		disptext = L["STATUS_POISON"],
		color = "009900",
		disabled = (class ~= "PALADIN" and class ~= "DRUID" and class ~= "SHAMAN")
	},
	{
		conds = "All",
		buffname = "Disease",
		disptext = L["STATUS_DISEASE"],
		color = "996600",
		disabled = (class ~= "PRIEST" and class ~= "PALADIN" and class ~= "SHAMAN")
	},
	{
		conds = "All",
		buffname = "Magic",
		disptext = L["STATUS_MAGIC"],
		color = "3399FF",
		disabled = (class ~= "PRIEST" and class ~= "PALADIN")
	},
	{
		conds = "All",
		buffname = L["Food"],
		disptext = L["STATUS_FOOD"],
		color = "d79a6d",
	},
	{
		conds = "All",
		buffname = L["Drink"],
		disptext = L["STATUS_DRINK"],
		color = "00a1de",
	},
	-- WOTLK Battle Elixirs
	{
		conds = "All",
		buffname = L["Elixir of Accuracy"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Armor Piercing"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Deadly Strikes"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Expertise"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Lightning Speed"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Mighty Agility"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Mighty Strength"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Guru's Elixir"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Spellpower Elixir"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Wrath Elixir"],
		disptext = L["STATUS_BATTLE_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	-- WOTLK Guardian Elixirs
	{
		conds = "All",
		buffname = L["Elixir of Mighty Defense"],
		disptext = L["STATUS_GUARDIAN_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Spirit"],
		disptext = L["STATUS_GUARDIAN_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Protection"],
		disptext = L["STATUS_GUARDIAN_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Mighty Thoughts"],
		disptext = L["STATUS_GUARDIAN_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Mighty Mageblood"],
		disptext = L["STATUS_GUARDIAN_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Elixir of Mighty Fortitude"],
		disptext = L["STATUS_GUARDIAN_ELIXIR"],
		color = "FF0000",
        disabled = true,
	},
	-- WOTLK Flasks
	{
		conds = "All",
		buffname = L["Flask of the Frost Wyrm"],
		disptext = L["STATUS_FLASK"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Flask of Stoneblood"],
		disptext = L["STATUS_FLASK"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Flask of Pure Mojo"],
		disptext = L["STATUS_FLASK"],
		color = "FF0000",
        disabled = true,
	},
	{
		conds = "All",
		buffname = L["Flask of Endless Rage"],
		disptext = L["STATUS_FLASK"],
		color = "FF0000",
        disabled = true,
	},
}