--[[
	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 = "Mirror"
local Mirror = Quartz3:NewModule(MODNAME, "AceHook-3.0", "AceEvent-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: MIRRORTIMER_NUMTIMERS
local _G = _G
local CreateFrame, GetTime, UIParent, StaticPopupDialogs = CreateFrame, GetTime, UIParent, StaticPopupDialogs
local GetMirrorTimerProgress, GetMirrorTimerInfo, GetCorpseRecoveryDelay = GetMirrorTimerProgress, GetMirrorTimerInfo, GetCorpseRecoveryDelay
local UnitHealth = UnitHealth
local pairs, unpack, next, wipe, error = pairs, unpack, next, wipe, error
local table_sort = table.sort


local gametimebase, gametimetostart
local lfgshowbase, readycheckshowbase, readycheckshowduration
local locked = true

local db, getOptions

local new, del

local defaults = {
	profile = {
		mirroricons = true,
		mirroriconside = "left",
		
		mirroranchor = "player",--L["Free"], L["Target"], L["Focus"]
		
		mirrorx = 500,
		mirrory = 700,
		mirrorgrowdirection = "up", --L["Down"]
		
		mirrorposition = "topleft",
		
		mirrorgap = 1,
		mirrorspacing = 1,
		mirroroffset = 3,
		
		mirrornametext = true,
		mirrortimetext = true,
		
		mirrortexture = "LiteStep",
		mirrorwidth = 120,
		mirrorheight = 12,
		mirrorfont = "Friz Quadrata TT",
		mirrorfontsize = 9,
		mirroralpha = 1,
		
		mirrortextcolor = {1, 1, 1},
		BREATH = {0, 0.5, 1},
		EXHAUSTION = {1.00, 0.9, 0},
		FEIGNDEATH = {1, 0.7, 0},
		CAMP = {1, 0.7, 0},
		DEATH = {1, 0.1, 0.1},
		QUIT = {1, 0.7, 0},
		DUEL_OUTOFBOUNDS = {0.2, 0.8, 0.2},
		INSTANCE_BOOT = {1, 0, 0},
		CONFIRM_SUMMON = {1, 0.3, 1},
		AREA_SPIRIT_HEAL = {0, 0.22, 1},
		REZTIMER = {1, 0, 0.5},
		RESURRECT_NO_SICKNESS = {0.47, 1, 0},
		RESURRECT_NO_TIMER = {0.47, 1, 0},
		PARTY_INVITE = {1, 0.9, 0},
		DUEL_REQUESTED = {1, 0.13, 0},
		GAMESTART = {0,1,0},
		READYCHECK = {0,1,0},

		hideblizzmirrors = true,

		showmirror = true,
		showstatic = true,
		showpvp = true,
		showreadycheck = true,
	}
}

local icons = {
	BREATH = "Interface\\Icons\\Spell_Shadow_DemonBreath",
	EXHAUSTION = "Interface\\Icons\\Ability_Suffocate",
	FEIGNDEATH = "Interface\\Icons\\Ability_Rogue_FeignDeath",
	CAMP = "Interface\\Icons\\INV_Misc_GroupLooking",
	DEATH = "Interface\\Icons\\Ability_Vanish",
	QUIT = "Interface\\Icons\\INV_Misc_GroupLooking",
	DUEL_OUTOFBOUNDS = "Interface\\Icons\\Ability_Rogue_Sprint",
	INSTANCE_BOOT = "Interface\\Icons\\INV_Misc_Rune_01",
	CONFIRM_SUMMON = "Interface\\Icons\\Spell_Shadow_Twilight",
	AREA_SPIRIT_HEAL = "Interface\\Icons\\Spell_Holy_Resurrection",
	REZTIMER = "",
	RESURRECT_NO_SICKNESS = "",
	PARTY_INVITE = "",
	DUEL_REQUESTED = "",
	GAMESTART = "",
	READYCHECK = "",
}

local popups = {
	CAMP = L["Logout"],
	DEATH = L["Release"],
	QUIT = L["Quit"],
	DUEL_OUTOFBOUNDS = L["Forfeit Duel"],
	INSTANCE_BOOT = L["Instance Boot"],
	CONFIRM_SUMMON = L["Summon"],
	AREA_SPIRIT_HEAL = L["AOE Rez"],
	REZTIMER = L["Resurrect Timer"], --GetCorpseRecoveryDelay
	RESURRECT_NO_SICKNESS = L["Resurrect"], --only show if timeleft < delay
	RESURRECT_NO_TIMER = L["Resurrect"],
	PARTY_INVITE = L["Party Invite"],
	DUEL_REQUESTED = L["Duel Request"],
	GAMESTART = L["Game Start"],
	READYCHECK = READY_CHECK,
}

local timeoutoverrides = {
	DEATH = 360,
	AREA_SPIRIT_HEAL = 30,
	INSTANCE_BOOT = 60,
	CONFIRM_SUMMON = 120,
	GAMESTART = 60,
	READYCHECK = 40,
}

Mirror.ExternalTimers = setmetatable({}, {__index = function(t,k)
	local v = new()
	t[k] = v
	return v
	--[[
	startTime
	endTime
	icon
	color
	]]
end})

local mirrorOnUpdate, fakeOnUpdate
do
	function mirrorOnUpdate(frame)
		local progress = GetMirrorTimerProgress(frame.mode) / 1000
		progress = progress > frame.duration and frame.duration or progress
		frame:SetValue(progress)
		frame.TimeText:SetFormattedText(TimeFmt(progress))
	end

	function fakeOnUpdate(frame)
		local currentTime = GetTime()
		local endTime = frame.endTime

		if frame.framenum > 0 then
			local popup = _G["StaticPopup"..frame.framenum] -- hate to do this, but I can"t think of a better way.
			if popup.which ~= frame.which or not popup:IsVisible() then
				return Mirror:UpdateBars()
			end
		end

		if currentTime > endTime then
			Mirror:UpdateBars()
		else
			local remaining = currentTime - frame.startTime
			frame:SetValue(endTime - remaining)
			frame.TimeText:SetFormattedText(TimeFmt(endTime - currentTime))
		end
	end
end
local function OnHide(frame)
	frame:SetScript("OnUpdate", nil)
end
local mirrorbars = setmetatable({}, {
	__index = function(t,k)
		local bar = CreateFrame("StatusBar", nil, UIParent)
		t[k] = bar
		bar:SetFrameStrata("MEDIUM")
		bar:Hide()
		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
		Mirror:ApplySettings()
		return bar
	end
})

function Mirror:OnInitialize()
	self.db = Quartz3.db:RegisterNamespace(MODNAME, defaults)
	db = self.db.profile
	
	self:SetEnabledState(Quartz3:GetModuleEnabled(MODNAME))
	Quartz3:RegisterModuleOptions(MODNAME, getOptions, L["Mirror"])

end

function Mirror:OnEnable()
	self:RegisterEvent("MIRROR_TIMER_PAUSE", "UpdateBars")
	self:RegisterEvent("MIRROR_TIMER_START", "UpdateBars")
	self:RegisterEvent("MIRROR_TIMER_STOP", "UpdateBars")
	self:RegisterEvent("PLAYER_UNGHOST", "UpdateBars")
	self:RegisterEvent("PLAYER_ALIVE", "UpdateBars")
	self:RegisterMessage("Quartz3Mirror_UpdateCustom", "UpdateBars")
	self:RegisterEvent("CHAT_MSG_BG_SYSTEM_NEUTRAL")
	self:RegisterEvent("LFG_PROPOSAL_SHOW")
	self:RegisterEvent("READY_CHECK")
	self:RegisterEvent("READY_CHECK_FINISHED")
	self:RegisterEvent("LFG_PROPOSAL_FAILED", "LFG_PROPOSAL_End")
	self:RegisterEvent("LFG_PROPOSAL_SUCCEEDED", "LFG_PROPOSAL_End")
	self:SecureHook("StaticPopup_Show", "UpdateBars")
	media.RegisterCallback(self, "LibSharedMedia_SetGlobal", function(mtype, override)
		if mtype == "statusbar" then
			for i, v in pairs(mirrorbars) 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.mirrortexture then
			for i, v in pairs(mirrorbars) do
				v:SetStatusBarTexture(media:Fetch("statusbar", self.config.mirrortexture))
			end
		end
	end)

	self:ApplySettings()
end

function Mirror:OnDisable()
	mirrorbars[1].Hide = nil
	mirrorbars[1]:EnableMouse(false)
	mirrorbars[1]:SetScript("OnDragStart", nil)
	mirrorbars[1]:SetScript("OnDragStop", nil)

	for _, v in pairs(mirrorbars) do
		v:Hide()
	end

	for i = 1, 3 do
		_G["MirrorTimer"..i]:RegisterEvent("MIRROR_TIMER_PAUSE")
		_G["MirrorTimer"..i]:RegisterEvent("MIRROR_TIMER_STOP")
	end
	UIParent:RegisterEvent("MIRROR_TIMER_START")

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

do
	local tblCache = setmetatable({}, {__mode="k"})
	function new()
		local entry = next(tblCache)
		if entry then tblCache[entry] = nil else entry = {} end
		return entry
	end
	function del(tbl)
		wipe(tbl)
		tblCache[tbl] = true
	end
	
	local function sort(a,b)
		return a.name < b.name
	end
	
	local tmp = {}
	local reztimermax = 0
	local function update()
		Mirror.updateMirrorBar = nil
		local currentTime = GetTime()
		for k in pairs(tmp) do
			tmp[k] = del(tmp[k])
		end
		
		if db.showpvp then
			if gametimebase then
				local endTime = gametimebase + gametimetostart
				if endTime > currentTime then
					local which = "GAMESTART"
					local t = new()
					tmp[#tmp+1] = t
					t.name = popups[which]
					t.texture = icons[which]
					t.mode = which
					t.startTime = endTime - timeoutoverrides[which]
					t.endTime = endTime
					t.isfake = true
					t.framenum = 0
				else
					gametimebase = nil
					gametimetostart = nil
				end
			end
		end

		if db.showreadycheck then
			if lfgshowbase or readycheckshowbase then
				local which = "READYCHECK"
				local endTime
				if readycheckshowbase then
					endTime = readycheckshowbase + readycheckshowduration
				else
					endTime = lfgshowbase + timeoutoverrides[which]
				end
				if endTime > currentTime then
					local t = new()
					tmp[#tmp+1] = t
					t.name = popups[which]
					t.texture = icons[which]
					t.mode = which
					t.startTime = lfgshowbase or readycheckshowbase
					t.endTime = endTime
					t.isfake = true
					t.framenum = 0
				else
					lfgshowbase = nil
					readycheckshowbase = nil
					readycheckshowduration = nil
				end
			end
		end

		if db.showmirror then
			for i = 1, MIRRORTIMER_NUMTIMERS do
				local timer, value, maxvalue, scale, paused, label = GetMirrorTimerInfo(i)
				if timer ~= "UNKNOWN" then
					local t = new()
					tmp[#tmp+1] = t
					t.name = label
					t.texture = icons[timer]
					t.mode = timer
					t.duration = maxvalue / 1000
					t.isfake = false
				end
			end
		end
		
		if db.showstatic then
			local recoverydelay = GetCorpseRecoveryDelay()
			if recoverydelay > 0 and UnitHealth("player") < 2 then
				if reztimermax == 0 then
					reztimermax = recoverydelay
				end
				local which = "REZTIMER"
				local t = new()
				tmp[#tmp+1] = t
				t.name = popups[which]
				t.texture = icons[which]
				t.mode = which
				t.startTime = currentTime - (reztimermax - recoverydelay)
				t.endTime = currentTime + recoverydelay
				t.isfake = true
				t.framenum = 0
			else
				reztimermax = 0
			end
			
			for i = 1, 4 do
				local popup = _G["StaticPopup"..i]
				local which = popup.which
				local timeleft = popup.timeleft
				local name = popups[which]
				
				--special case for a timered rez
				if which == "RESURRECT_NO_SICKNESS" then
					if timeleft > 60 then
						name = nil
					end
				end
				
				if popup:IsVisible() and name and timeleft and timeleft > 0 then
					local t = new()
					tmp[#tmp+1] = t
					t.name = name
					t.texture = icons[which]
					t.mode = which
					local timeout = StaticPopupDialogs[which].timeout
					if not timeout or timeout == 0 then
						timeout = timeoutoverrides[which]
					end
					t.startTime = currentTime - (timeout - timeleft)
					t.endTime = currentTime + timeleft
					t.isfake = true
					t.framenum = i
				end
			end
		end
		
		local external = Mirror.ExternalTimers
		for name, v in pairs(external) do
			local endTime = v.endTime
			if not v.startTime or not endTime then
				error("bad custom table")
			end
			if endTime > currentTime then
				local t = new()
				tmp[#tmp+1] = t
				t.name = name
				t.texture = v.icon or icons[name]
				t.startTime = v.startTime
				t.endTime = v.endTime
				t.isfake = true
				t.framenum = 0
				if v.color then
					t.color = v.color
				end
			else
				external[name] = del(v)
			end
		end
		
		table_sort(tmp, sort)
		local maxindex = 0
		for k=1,#tmp do
			local v = tmp[k]
			maxindex = k
			local bar = mirrorbars[k]
			bar.Text:SetText(v.name)
			bar.Icon:SetTexture(v.texture)
			bar.mode = v.mode
			if v.isfake then
				local startTime, endTime = v.startTime, v.endTime
				bar:SetMinMaxValues(startTime, endTime)
				bar.startTime = startTime
				bar.endTime = endTime
				bar.framenum = v.framenum
				bar.which = v.mode
				bar:Show()
				bar:SetScript("OnUpdate", fakeOnUpdate)
				if v.mode then
					bar:SetStatusBarColor(unpack(db[v.mode]))
				elseif v.color then
					bar:SetStatusBarColor(unpack(v.color))
				else
					bar:SetStatusBarColor(1,1,1) --!! add option
				end
			else
				local duration = v.duration
				bar:SetMinMaxValues(0, duration)
				bar.duration = duration
				bar:Show()
				bar:SetScript("OnUpdate", mirrorOnUpdate)
				bar:SetStatusBarColor(unpack(db[v.mode]))
			end
		end
		for i = maxindex+1, #mirrorbars do
			mirrorbars[i]:Hide()
		end
	end
	
	function Mirror:UpdateBars()
		if not self.updateMirrorBar then
			self.updateMirrorBar = self:ScheduleTimer(update, 0) -- API funcs dont return helpful crap until after the event.
		end
	end
end
function Mirror:CHAT_MSG_BG_SYSTEM_NEUTRAL(event, msg)
	if msg:match(L["1 minute"]) or msg:match(L["One minute until"]) then
		gametimebase = GetTime()
		gametimetostart = 60
	elseif msg:match(L["30 seconds"]) or msg:match(L["Thirty seconds until"]) then
		gametimebase = GetTime()
		gametimetostart = 30
	elseif msg:match(L["15 seconds"]) or msg:match(L["Fifteen seconds until"]) then
		gametimebase = GetTime()
		gametimetostart = 15
	end
	self:UpdateBars()
end

function Mirror:LFG_PROPOSAL_SHOW(event, msg)
	lfgshowbase = GetTime()
	self:UpdateBars()
end
function Mirror:LFG_PROPOSAL_End(event, msg)
	lfgshowbase = nil
	self:UpdateBars()
end
function Mirror:READY_CHECK(event, msg, duration)
	readycheckshowbase = GetTime()
	readycheckshowduration = duration
	self:UpdateBars()
end
function Mirror:READY_CHECK_FINISHED(event, msg)
	readycheckshowbase = nil
	self:UpdateBars()
end
do
	local function apply(i, bar, db, direction)
		local position, showicons, iconside, gap, spacing, offset
		local qpdb = Player.db.profile 
		
		position = db.mirrorposition
		showicons = db.mirroricons
		iconside = db.mirroriconside
		gap = db.mirrorgap
		spacing = db.mirrorspacing
		offset = db.mirroroffset
		
		bar:ClearAllPoints()
		bar:SetStatusBarTexture(media:Fetch("statusbar", db.mirrortexture))
		bar:GetStatusBarTexture():SetHorizTile(false)
		bar:GetStatusBarTexture():SetVertTile(false)
		bar:SetWidth(db.mirrorwidth)
		bar:SetHeight(db.mirrorheight)
		bar:SetScale(qpdb.scale)
		bar:SetAlpha(db.mirroralpha)
		
		if db.mirroranchor == "free" then
			if i == 1 then
				bar:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", db.mirrorx, db.mirrory)
				if db.mirrorgrowdirection == "up" then
					direction = 1
				else --L["Down"]
					direction = -1
				end
			else
				if direction == 1 then
					bar:SetPoint("BOTTOMRIGHT", mirrorbars[i-1], "TOPRIGHT", 0, spacing)
				else -- -1
					bar:SetPoint("TOPRIGHT", mirrorbars[i-1], "BOTTOMRIGHT", 0, -1 * spacing)
				end
			end
		else
			if i == 1 then
				local anchorframe
				local anchor = db.mirroranchor
				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 + db.mirrorheight
					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 + db.mirrorheight
					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 + db.mirrorheight
					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 + db.mirrorheight
					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", mirrorbars[i-1], "TOPRIGHT", 0, spacing)
				else -- -1
					bar:SetPoint("TOPRIGHT", mirrorbars[i-1], "BOTTOMRIGHT", 0, -1 * spacing)
				end
			end
		end
		
		local timetext = bar.TimeText
		if db.mirrortimetext then
			timetext:Show()
			timetext:ClearAllPoints()
			timetext:SetWidth(db.mirrorwidth)
			timetext:SetPoint("RIGHT", bar, "RIGHT", -2, 0)
			timetext:SetJustifyH("RIGHT")
		else
			timetext:Hide()
		end
		timetext:SetFont(media:Fetch("font", db.mirrorfont), db.mirrorfontsize)
		timetext:SetShadowColor( 0, 0, 0, 1)
		timetext:SetShadowOffset( 0.8, -0.8 )
		timetext:SetTextColor(unpack(db.mirrortextcolor))
		timetext:SetNonSpaceWrap(false)
		timetext:SetHeight(db.mirrorheight)
		
		local temptext = timetext:GetText()
		timetext:SetText("10.0")
		local normaltimewidth = timetext:GetStringWidth()
		timetext:SetText(temptext)
		
		local text = bar.Text
		if db.mirrornametext then
			text:Show()
			text:ClearAllPoints()
			text:SetPoint("LEFT", bar, "LEFT", 2, 0)
			text:SetJustifyH("LEFT")
			if db.mirrortimetext then
				text:SetWidth(db.mirrorwidth - normaltimewidth)
			else
				text:SetWidth(db.mirrorwidth)
			end
		else
			text:Hide()
		end
		text:SetFont(media:Fetch("font", db.mirrorfont), db.mirrorfontsize)
		text:SetShadowColor( 0, 0, 0, 1)
		text:SetShadowOffset( 0.8, -0.8 )
		text:SetTextColor(unpack(db.mirrortextcolor))
		text:SetNonSpaceWrap(false)
		text:SetHeight(db.mirrorheight)
		
		local icon = bar.Icon
		if showicons then
			icon:Show()
			icon:SetWidth(db.mirrorheight-1)
			icon:SetHeight(db.mirrorheight-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 Mirror:ApplySettings()
		db = self.db.profile
		if self:IsEnabled() then
			local direction
			if db.mirroranchor ~= "free" then
				mirrorbars[1].Hide = nil
				mirrorbars[1]:EnableMouse(false)
				mirrorbars[1]:SetScript("OnDragStart", nil)
				mirrorbars[1]:SetScript("OnDragStop", nil)
			end
			for i, v in pairs(mirrorbars) do
				direction = apply(i, v, db, direction)
			end
			if db.hideblizzmirrors then
				for i = 1, 3 do
					_G["MirrorTimer"..i]:UnregisterAllEvents()
					_G["MirrorTimer"..i]:Hide()
				end
				UIParent:UnregisterEvent("MIRROR_TIMER_START")
			else
				for i = 1, 3 do
					_G["MirrorTimer"..i]:RegisterEvent("MIRROR_TIMER_PAUSE")
					_G["MirrorTimer"..i]:RegisterEvent("MIRROR_TIMER_STOP")
				end
				UIParent:RegisterEvent("MIRROR_TIMER_START")
			end
			db.RESURRECT_NO_TIMER = db.RESURRECT_NO_SICKNESS
			self:UpdateBars()
		end
	end
end

do
	local function getmirrorhidden()
		return not db.showmirror
	end
	
	local function getstatichidden()
		return not db.showstatic
	end
	
	local function getpvphidden()
		return not db.showpvp
	end

	local function getreadycheckhidden()
		return not db.showreadycheck
	end

	local function getfreeoptionshidden()
		return db.mirroranchor ~= "free"
	end
	
	local function getnotfreeoptionshidden()
		return db.mirroranchor == "free"
	end
	
	local function dragstart()
		mirrorbars[1]:StartMoving()
	end
	
	local function dragstop()
		db.mirrorx = mirrorbars[1]:GetLeft()
		db.mirrory = mirrorbars[1]:GetBottom()
		mirrorbars[1]:StopMovingOrSizing()
	end
	
	local function nothing()
		mirrorbars[1]:SetAlpha(db.mirroralpha)
	end
	
	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 setOpt(info, value)
		db[info[#info]] = value
		Mirror:ApplySettings()
	end

	local function getOpt(info)
		return db[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["Mirror"],
				order = 600,
				set = setOpt,
				get = getOpt,
				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 = 96,
						width = "full",
					},
					settings = {
						type = "group",
						name = L["Settings"],
						args = {
							mirroranchor = {
								type = "select",
								name = L["Anchor Frame"],
								desc = L["Select where to anchor the mirror bars"],
								values = {["player"] = L["Player"], ["free"] = L["Free"], ["target"] = L["Target"], ["focus"] = L["Focus"]},
							},
							-- free
							mirrorlock = {
								type = "toggle",
								name = L["Lock"],
								desc = L["Toggle mirror bar lock"],
								get = function()
									return locked
								end,
								set = function(info, v)
									if v then
										mirrorbars[1].Hide = nil
										mirrorbars[1]:EnableMouse(false)
										mirrorbars[1]:SetScript("OnDragStart", nil)
										mirrorbars[1]:SetScript("OnDragStop", nil)
										Mirror:UpdateBars()
									else
										mirrorbars[1]:Show()
										mirrorbars[1]:EnableMouse(true)
										mirrorbars[1]:SetScript("OnDragStart", dragstart)
										mirrorbars[1]:SetScript("OnDragStop", dragstop)
										mirrorbars[1]:SetAlpha(1)
										mirrorbars[1].Hide = nothing
									end
									locked = v
								end,
								hidden = getfreeoptionshidden,
								order = 98,
							},
							mirrorgrowdirection = {
								type = "select",
								name = L["Grow Direction"],
								desc = L["Set the grow direction of the mirror bars"],
								values = {["up"] = L["Up"], ["down"] = L["Down"]},
								hidden = getfreeoptionshidden,
								order = 102,
							},
							mirrorx = {
								type = "range",
								name = L["X"],
								desc = L["Set an exact X value for this bar's position."],
								min = 0, max = 2560, bigStep = 1,
								order = 103,
								hidden = getfreeoptionshidden,
							},
							mirrory = {
								type = "range",
								name = L["Y"],
								desc = L["Set an exact Y value for this bar's position."],
								min = 0, max = 1600, bigStep = 1,
								order = 103,
								hidden = getfreeoptionshidden,
							},
							-- anchored to a cast bar
							mirrorposition = {
								type = "select",
								name = L["Position"],
								desc = L["Position the mirror bars"],
								values = positions,
								hidden = getnotfreeoptionshidden,
								order = 101,
							},
							mirrorgap = {
								type = "range",
								name = L["Gap"],
								desc = L["Tweak the vertical position of the mirror bars"],
								min = -35, max = 35, step = 1,
								hidden = getnotfreeoptionshidden,
								order = 102,
							},
							mirroroffset = {
								type = "range",
								name = L["Offset"],
								desc = L["Tweak the horizontal position of the mirror bars"],
								min = -35, max = 35, step = 1,
								hidden = getnotfreeoptionshidden,
								order = 103,
							},
							mirrorspacing = {
								type = "range",
								name = L["Spacing"],
								desc = L["Tweak the space between mirror bars"],
								min = -35, max = 35, step = 1,
								order = 104,
							},
							nl4 = {
								type = "description",
								name = "",
								order = 109,
							},
							mirroricons = {
								type = "toggle",
								name = L["Show Icons"],
								desc = L["Show icons on mirror bars"],
								order = 110,
							},
							mirroriconside = {
								type = "select",
								name = L["Icon Position"],
								desc = L["Set the side of the mirror bar that the icon appears on"],
								values = {["left"] = L["Left"], ["right"] = L["Right"]},
								order = 111,
							},
							mirrortexture = {
								type = "select",
								dialogControl = "LSM30_Statusbar",
								name = L["Texture"],
								desc = L["Set the mirror bar Texture"],
								values = lsmlist.statusbar,
								order = 112,
							},
							nl5 = {
								type = "description",
								name = "",
								order = 113,
							},
							mirrorwidth = {
								type = "range",
								name = L["Mirror Bar Width"],
								desc = L["Set the width of the mirror bars"],
								min = 50, max = 300, step = 1,
								order = 114,
							},
							mirrorheight = {
								type = "range",
								name = L["Mirror Bar Height"],
								desc = L["Set the height of the mirror bars"],
								min = 4, max = 25, step = 1,
								order = 115,
							},
							mirroralpha = {
								type = "range",
								name = L["Alpha"],
								desc = L["Set the alpha of the mirror bars"],
								min = 0.05, max = 1, bigStep = 0.05,
								isPercent = true,
								order = 116,
							},
							nl6 = {
								type = "description",
								name = "",
								order = 119,
							},
							mirrornametext = {
								type = "toggle",
								name = L["Mirror Name Text"],
								desc = L["Display the names of Mirror Bar Types on their bars"],
								order = 120,
							},
							mirrortimetext = {
								type = "toggle",
								name = L["Mirror Time Text"],
								desc = L["Display the time remaining on mirror bars"],
								order = 121,
							},
							mirrorfont = {
								type = "select",
								dialogControl = "LSM30_Font",
								name = L["Font"],
								desc = L["Set the font used in the mirror bars"],
								values = lsmlist.font,
								order = 122,
							},
							mirrorfontsize = {
								type = "range",
								name = L["Font Size"],
								desc = L["Set the font size for the mirror bars"],
								min = 3, max = 15, step = 1,
								order = 123,
							},
							mirrortextcolor = {
								type = "color",
								name = L["Text Color"],
								desc = L["Set the color of the text for the mirror bars"],
								get = getColor,
								set = setColor,
								order = 124,
							},
							hideblizzmirrors = {
								type = "toggle",
								name = L["Hide Blizz Mirror Bars"],
								desc = L["Hide Blizzard's mirror bars"],
								order = 130,
							},
						}
					},
					bars = {
						type = "group",
						name = L["Bars and Colors"],
						order = -1,
						args = {
							-- mirror
							showmirror = {
								type = "toggle",
								name = L["Show Mirror"],
								desc = L["Show mirror bars such as breath and feign death"],
								order = 97,
								width = "full",
							},
							BREATH = {
								type = "color",
								name = L["%s Color"]:format(L["Breath"]),
								desc = L["Set the color of the bars for %s"]:format(L["Breath"]),
								get = getColor,
								set = setColor,
								disabled = getmirrorhidden,
								order = 101,
							},
							EXHAUSTION = {
								type = "color",
								name = L["%s Color"]:format(L["Exhaustion"]),
								desc = L["Set the color of the bars for %s"]:format(L["Exhaustion"]),
								get = getColor,
								set = setColor,
								disabled = getmirrorhidden,
								order = 101,
							},
							FEIGNDEATH = {
								type = "color",
								name = L["%s Color"]:format(L["Feign Death"]),
								desc = L["Set the color of the bars for %s"]:format(L["Feign Death"]),
								get = getColor,
								set = setColor,
								disabled = getmirrorhidden,
								order = 101,
							},
							-- static
							showstatic = {
								type = "toggle",
								name = L["Show Static"],
								desc = L["Show bars for static popup items such as rez and summon timers"],
								order = 200,
								width = "full",
							},
							CAMP = {
								type = "color",
								name = L["%s Color"]:format(L["Logout"]),
								desc = L["Set the color of the bars for %s"]:format(L["Logout"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							DEATH = {
								type = "color",
								name = L["%s Color"]:format(L["Release"]),
								desc = L["Set the color of the bars for %s"]:format(L["Release"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							QUIT = {
								type = "color",
								name = L["%s Color"]:format(L["Quit"]),
								desc = L["Set the color of the bars for %s"]:format(L["Quit"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							DUEL_OUTOFBOUNDS = {
								type = "color",
								name = L["%s Color"]:format(L["Forfeit Duel"]),
								desc = L["Set the color of the bars for %s"]:format(L["Forfeit Duel"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							INSTANCE_BOOT = {
								type = "color",
								name = L["%s Color"]:format(L["Instance Boot"]),
								desc = L["Set the color of the bars for %s"]:format(L["Instance Boot"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							CONFIRM_SUMMON = {
								type = "color",
								name = L["%s Color"]:format(L["Summon"]),
								desc = L["Set the color of the bars for %s"]:format(L["Summon"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							AREA_SPIRIT_HEAL = {
								type = "color",
								name = L["%s Color"]:format(L["AOE Rez"]),
								desc = L["Set the color of the bars for %s"]:format(L["AOE Rez"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							REZTIMER = {
								type = "color",
								name = L["%s Color"]:format(L["Resurrect Timer"]),
								desc = L["Set the color of the bars for %s"]:format(L["Resurrect Timer"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							RESURRECT_NO_SICKNESS = {
								type = "color",
								name = L["%s Color"]:format(L["Resurrect"]),
								desc = L["Set the color of the bars for %s"]:format(L["Resurrect"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							PARTY_INVITE = {
								type = "color",
								name = L["%s Color"]:format(L["Party Invite"]),
								desc = L["Set the color of the bars for %s"]:format(L["Party Invite"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							DUEL_REQUESTED = {
								type = "color",
								name = L["%s Color"]:format(L["Duel Request"]),
								desc = L["Set the color of the bars for %s"]:format(L["Duel Request"]),
								get = getColor,
								set = setColor,
								disabled = getstatichidden,
								order = 201,
							},
							--pvp
							showpvp = {
								type = "toggle",
								name = L["Show PvP"],
								desc = L["Show bar for start of arena and battleground games"],
								order = 300,
								width = "full",
							},
							GAMESTART = {
								type = "color",
								name = L["%s Color"]:format(L["Game Start"]),
								desc = L["Set the color of the bars for %s"]:format(L["Game Start"]),
								get = getColor,
								set = setColor,
								disabled = getpvphidden,
								order = 301,
							},
							--ready check
							showreadycheck = {
								type = "toggle",
								name = L["Show Ready Check"],
								desc = L["Show bar for Ready Checks"],
								order = 500,
								width = "full",
							},
							READYCHECK = {
								type = "color",
								name = L["%s Color"]:format(READY_CHECK),
								desc = L["Set the color of the bars for %s"]:format(READY_CHECK),
								get = getColor,
								set = setColor,
								disabled = getreadycheckhidden,
								order = 501,
							},
						},
					},
				},
			}
		end
		return options
	end
end
