--<< ====================================================================== >>--
-- Constants                                                                  --
--<< ====================================================================== >>--
ACETIMER.COLOR = {
	AQUA    = {0.0,1.0,1.0},
	BLUE    = {0.0,0.0,1.0},
	FUCHSIA = {1.0,0.0,1.0},
	GRAY    = {0.5,0.5,0.5},
	GREEN   = {0.0,0.5,0.0},
	LIME    = {0.0,1.0,0.0},
	MAROON  = {0.5,0.0,0.0},
	NAVY    = {0.0,0.0,0.5},
	OLIVE   = {0.5,0.5,0.0},
	PURPLE  = {0.5,0.0,0.5},
	RED     = {1.0,0.0,0.0},
	TEAL    = {0.0,0.5,0.5},
	WHITE   = {1.0,1.0,1.0},
	YELLOW  = {1.0,1.0,0.0},
}

ACETIMER.COLOR_MAP = {
	[0]="OLIVE",[1]="TEAL",[2]="PURPLE",[3]="GREEN",
}

ACETIMER.COMMANDS = { 
	ACETIMER.CMD_SHORT, 
	ACETIMER.CMD_LONG, 
}

ACETIMER.CMD_OPTIONS = {
	{ option = ACETIMER.OPT_ANCHOR, method = "CareOptAnchor", desc = ACETIMER.OPT_ANCHOR_DESC,},
	{ option = ACETIMER.OPT_SCALE,  method = "CareOptScale",  desc = ACETIMER.OPT_SCALE_DESC,},
	{ option = ACETIMER.OPT_GROW,   method = "CareOptGrow",   desc = ACETIMER.OPT_GROW_DESC,},
}

ACETIMER.SPELL = 1
ACETIMER.EVENT = 2
ACETIMER.SKILL = 3

--<< ====================================================================== >>--
-- Class Setup                                                                --
--<< ====================================================================== >>--
AceTimer = AceAddon:new({
    name          = ACETIMER.NAME,
    description   = ACETIMER.DESCRIPTION, -- Optional; Ace will use the .toc Notes text
    version       = "R4",
    releaseDate   = "",
    aceCompatible = "102", -- Check ACE_COMP_VERSION in Ace.lua for current.
    author        = "Rasmus Kromann-Larsen",
    email         = "rasmus@kromann-larsen.dk",
    website       = "http://www.rasmuskl.dk",
    category      = "combat",
    --optionsFrame  = "AceTimerOptionsFrame",
    db            = AceDatabase:new("AceTimerDB"),
    --defaults      = DEFAULT_OPTIONS,
    cmd           = AceChatCmd:new(ACETIMER.COMMANDS, ACETIMER.CMD_OPTIONS),
	modules       = {},
})

function AceTimer:Initialize()
    -- Helpful closures for accessing the addon's currently loaded profile.
    self.GetOpt = function(var) return self.db:get(self.profilePath,var)    end
    self.SetOpt = function(var,val) self.db:set(self.profilePath,var,val)   end
    self.TogOpt = function(var) return self.db:toggle(self.profilePath,var) end

    self:InitModules()
end

function AceTimer:Enable()
	self.groups = {}
	self.timers = {}
	self.bars = {}
	for i = 1, 20 do self.bars[i] = {} end
	self.gs_patterns = {}
	self.gs_captured = {}
	self:InitAnchor()
	
	self:EnableModules()
end

function AceTimer:Disable()
	self:DisableModules()
	if self.gs_patterns then self.gs_patterns = nil end
	if self.gs_captured then self.gs_captured = nil end
	if self.bars then self:KillBars(); self.bars = nil end
	if self.timers then self.timers = nil end
	if self.groups then self.groups = nil end

	self:UnregisterAllEvents(self)
	self:UnhookAllScripts()
end

--<< ====================================================================== >>--
-- Timer Processing                                                           --
--<< ====================================================================== >>--
function AceTimer:AddGroup(id, forall, color)
	if color then 
		self.groups[id] = { fa=forall, cr=ACETIMER.COLOR[color] }
	else
		self.groups[id] = { fa=forall }
	end
end

function AceTimer:AddTimer(kind, name, duration, targeted, isgain, selforselect, extra)
	if not self.timers[kind]       then self.timers[kind] = {}       end
	if not self.timers[kind][name] then self.timers[kind][name] = {} end
	if not extra then extra = {} end
	targeted = (targeted > 0 ) and 1 or nil
	isgain = (isgain > 0 ) and 1 or nil
	selforselect = (selforselect > 0 ) and 1 or nil
	if extra.cr then extra.cr = ACETIMER.COLOR[extra.cr] 
	elseif extra.gr and self.groups[extra.gr].cr then extra.cr = self.groups[extra.gr].cr
	else
		local ccode = (targeted and 2 or 0) + (isgain and 1 or 0)
		extra.cr = ACETIMER.COLOR[ACETIMER.COLOR_MAP[ccode]]
	end
	self.timers[kind][name] = { d=duration, k={t=targeted,g=isgain,s=selforselect}, x=extra }
end

function AceTimer:StartTimer(timer, name, target, rank)
	if not target then target = "none" end
	if not rank then rank = timer.r or 0 end
	if timer.x.gr then self:CleanGroup(timer.x.gr, target) end
	if timer.x.ea then
		for name, valid in timer.x.ea do 
			local event = self.timers[ACETIMER.EVENT][name]
			event.r = rank
			event.v = GetTime() + valid
			if target ~= "none" then event.t = target end
		end
	end
	if timer.d == 0 then return end

	local id, slot = name.."-"..target 
	for i = 1, 20 do 
		if self.bars[i].id == id then 
			TimexBar:StopBar(self.bars[i].id)
			self:StopBar(self.bars[i].id)
			break 
		end
	end
	for i = 1, 20 do if not self.bars[i].id then slot = i; break end end
	if not slot or not TimexBar:Get(id) then return end

	self.bars[slot].id     = id
	self.bars[slot].timer  = timer
	self.bars[slot].name   = name
	self.bars[slot].rank   = rank
	self.bars[slot].target = target
	self.bars[slot].group  = timer.x.gr
	self:SetPoint(id, slot)
	TimexBar:SetColor(id, unpack(timer.x.cr or ACETIMER.COLOR.GRAY))
	TimexBar:SetScale(id, self.GetOpt("Scale"))
	TimexBar:SetText(id, target == "none" and name or target)
	TimexBar:SetTexture(id, timer.x.tx or self:GetTexture(name, timer.x))
	TimexBar:SetFunction(id, self.StopBar, self, id)
	TimexBar:SetClickFunction(id, self.OnClick, self, id, timer.x.rc)
	TimexBar:Start(id, timer.x.d and self:GetDuration(timer.d, timer.x.d, rank) or timer.d)
end

function AceTimer:GetDuration(duration, record, rank)
	if record.rt then duration = record.rt[rank] or duration end
	if record.rs then duration = duration + (rank-1) * record.rs end
	if record.cp then duration = duration + (GetComboPoints() - 1) * record.cp end
	if record.tn then
		if type(record.tn) == "string" then record.tn = self:GetTalentPosition(record.tn) end
		local _,_,_,_, tr = GetTalentInfo(unpack(record.tn))
		if tr > 0 then
			local gain = record.tt and record.tt[tr] or (record.tb + (tr-1) * (record.ts or record.tb))
			duration = duration + (record.tp and (duration/100) * gain or gain)
		end
	end
	return duration
end

function AceTimer:GetTexture(name, record)
	if record.xn then name = record.xn end
	local i = 1
	while true do
		local nm = GetSpellName(i, BOOKTYPE_SPELL)
		if not nm then break
		elseif nm == name then
			record.tx = GetSpellTexture(i, BOOKTYPE_SPELL)
			return record.tx
		end
		i = i + 1
	end
	local tp = self:GetTalentPosition(name)
	if tp then 
		local _, tx = GetTalentInfo(unpack(tp))
		record.tx = tx
		return record.tx
	end
	if HasPetSpells() then
		for i =1, NUM_PET_ACTION_SLOTS do
			local nm, _, tx = GetPetActionInfo(i)
			if nm == name then record.tx = tx; return record.tx end
		end
	end
end

function AceTimer:GetTalentPosition(name)
	for i = 1, GetNumTalentTabs() do
		for j = 1, GetNumTalents(i) do
			if GetTalentInfo(i, j) == name then return {i, j} end
		end
	end
end

--<< ====================================================================== >>--
-- Bar Processing                                                             --
--<< ====================================================================== >>--
function AceTimer:CleanGroup(group, target)
	local forall = self.groups[group].fa
	for i = 1, 20 do
		if self.bars[i].group and self.bars[i].group == group then
			if forall then 
				TimexBar:StopBar(self.bars[i].id)
				self:StopBar(self.bars[i].id)
			elseif self.bars[i].target == target then 
				TimexBar:StopBar(self.bars[i].id)
				self:StopBar(self.bars[i].id)
				break
			end
		end
	end
end

function AceTimer:SetPointTOP(id, slot)
	return TimexBar:SetPoint(id, "TOP", "AceTimerAnchor", "TOP", 0, (slot * 20))
end

function AceTimer:SetPointBOTTOM(id, slot)
	return TimexBar:SetPoint(id, "BOTTOM", "AceTimerAnchor", "BOTTOM", 0, (-5 - (slot * 20)))
end

function AceTimer:KillBar(name, unit)
	for i = 1, 20 do
		if self.bars[i].id then
			if self.bars[i].name == name then
				if not unit then 
					if self.bars[i].timer.k.t then unit = UnitName("player") else unit = "none" end
				end
				if self.bars[i].target == unit then
					TimexBar:StopBar(self.bars[i].id)
					return self:StopBar(self.bars[i].id)
				end
			end
		else 
			break
		end
	end
end

function AceTimer:KillBars(unit)
	if unit and UnitExists("target") and UnitName("target") == unit and not UnitIsDeadOrGhost("target") then return end
	for i = 1, 20 do
		if self.bars[i].id then
			if not unit or self.bars[i].target == unit then
				TimexBar:StopBar(self.bars[i].id)
				self:StopBar(self.bars[i].id)
			end
		else
			break
		end
	end
end

function AceTimer:StopBar(id)
	for i = 1, 20 do 
		if self.bars[i].id == id then 
			for k in self.bars[i] do self.bars[i][k] = nil end
		end
	end
	for i = 1, 19 do
		if not self.bars[i].id then
			local temp = self.bars[i]
			for j = i + 1, 20 do
				if self.bars[j].id then
					self.bars[i] = self.bars[j]; self.bars[j] = temp; temp = nil
					self:SetPoint(self.bars[i].id, i)
					break
				end
			end
			if temp then break end
		end
	end
end

function AceTimer:OnClick(id, reactive)
	if arg1 == "RightButton" then
		TimexBar:StopBar(id) 
		self:StopBar(id)
	elseif arg1 == "LeftButton" and reactive then
		for i = 1, 20 do 
			if self.bars[i].id == id then
				if self.bars[i].rank > 0 then
					return CastSpellByName(string.format(ACETIMER.FMT_CAST, self.bars[i].name, self.bars[i].rank))
				else
					return CastSpellByName(self.bars[i].name)
				end
			end
		end
	end
end

--<< ====================================================================== >>--
-- Globalstrings Processing                                                   --
--<< ====================================================================== >>--
-- self.gs_patterns
-- self.gs_captured
function AceTimer:FindGlobal(text, global)
	local gp = self.gs_patterns[global]
	if not gp then 
		self.gs_patterns[global] = {n=0,}
		gp = self.gs_patterns[global]
		gp.p = string.gsub(global, "([%[%]%(%)])", "%%%1")
		gp.p = string.gsub(gp.p, "%.$", "%%%.")
		gp.p = string.gsub(gp.p, "%%(%S-)([sd])", function (a1, a2) 
			gp.n = gp.n + 1
			local _,_, found = string.find(a1, "(%d)%$")
			if found then gp[gp.n] = tonumber(found) else gp[gp.n] = gp.n end
			if a2 == "s" then return "(.+)" 
			elseif a2 == "d" then return "(%d+)" 
			end
		end)
	end
	for k in self.gs_captured do self.gs_captured[k] = nil end
	return self:OrderCaptured(gp, string.find(text, gp.p))
end

function AceTimer:OrderCaptured(gp, s, e, ...)
	if s then 
		for i = 1, gp.n do self.gs_captured[gp[i]] = arg[i] end
		return TRUE
	end
end

--<< ====================================================================== >>--
-- Anchor Processing                                                          --
--<< ====================================================================== >>--
function AceTimer:InitAnchor()
	local test = {d=10, x={cr=ACETIMER.COLOR.GRAY, tx="Interface\\Icons\\INV_Misc_QuestionMark"} }
	AceTimerAnchorText:SetText(ACETIMER.NAME) 
	AceTimerAnchorTest:SetText(ACETIMER.WORD_TEST)
	AceTimerAnchorTest:SetScript("OnClick", function () return self:StartTimer(test, ACETIMER.WORD_TEST, "none", 0) end)
	AceTimerAnchorHide:SetText(ACETIMER.WORD_HIDE)
	AceTimerAnchorHide:SetScript("OnClick", function () return self:CareOptAnchor() end)
	if not self.GetOpt("Scale") then self.SetOpt("Scale", 0.80) end
	AceTimerAnchorScaleLow:SetText("0.5")
	AceTimerAnchorScaleHigh:SetText("1.5")
	AceTimerAnchorScale:SetScript("OnValueChanged", function() return self:OnScaleChanged() end)
	AceTimerAnchorScale:SetValue(self.GetOpt("Scale"))
	self:SetBarGrowth()
	if not self.GetOpt("AnchorHidden") then AceTimerAnchor:Show() end
end

function AceTimer:OnScaleChanged()
	local scale = AceTimerAnchorScale:GetValue()
	AceTimerAnchorScaleText:SetText(string.format("%.2f", scale))
	self.SetOpt("Scale", scale)
	for i = 1, 20 do
		if self.bars[i].id and TimexBar:CheckBar(self.bars[i].id) then 
			TimexBar:SetScale(self.bars[i].id, scale) 
		end
	end
end

function AceTimer:OnTestClicked()
	self:StartBar(ACETIMER.WORD_TEST.." 1", "none", 0, nil, 15, nil, ACETIMER.COLOR.RED)
	self:StartBar(ACETIMER.WORD_TEST.." 2", "none", 0, nil, 10, nil, ACETIMER.COLOR.GREEN)
	self:StartBar(ACETIMER.WORD_TEST.." 3", "none", 0, nil,  5, nil, ACETIMER.COLOR.BLUE)
end

function AceTimer:SetBarGrowth()
	if self.GetOpt("GrowUp") then 
		self.SetPoint = self.SetPointTOP
	else
		self.SetPoint = self.SetPointBOTTOM
	end
end

--<< ====================================================================== >>--
-- Command Handlers                                                           --
--<< ====================================================================== >>--
function AceTimer:CareOptAnchor()
	if self.TogOpt("AnchorHidden") then 
		AceTimerAnchor:Hide() else AceTimerAnchor:Show() 
	end
end

function AceTimer:CareOptScale(scale)
	scale = tonumber(scale)
	if not scale then scale = 0.80 else 
		if scale < 0.50 then scale = 0.50 elseif scale > 1.50 then scale = 1.50 end
	end
	AceTimerAnchorScale:SetValue(tonumber(string.format("%.2f", scale)))
	self.cmd:result(ACETIMER.OPT_SCALE_TEXT, "|cfff5f530" .. string.format("%.2f", self.GetOpt("Scale")) .. "|r")
end

function AceTimer:CareOptGrow()
	self.cmd:result(ACETIMER.OPT_GROW_TEXT, ACETIMER.MAP_DOWNUP[self.TogOpt("GrowUp") or 0])
	self:SetBarGrowth()
end

function AceTimer:Report()
	self.cmd:report({})
	self.cmd:result(ACETIMER.OPT_SCALE_TEXT, "|cfff5f530" .. string.format("%.2f", self.GetOpt("Scale")) .. "|r")
	self.cmd:result(ACETIMER.OPT_GROW_TEXT, ACETIMER.MAP_DOWNUP[self.GetOpt("GrowUp") or 0])
	self:ReportModules()
end

--<< ====================================================================== >>--
-- Register the Addon                                                         --
--<< ====================================================================== >>--
AceTimer:RegisterForLoad()
