-- DebuffCaster r30 - oblind
-- r30 - 2011-1-21
-- r1 - 2007-10-1

local ct, buffs, debuffs = {}
do
	local _, c = UnitClass("player")
	if c == "DRUID" then
		buffs = {
			774,	-- 回春术
			8936,	-- 愈合
			33763,	-- 生命绽放
			52610,	--野蛮咆哮
		}
		debuffs = {
			99,		--Demoralizing Roar 挫志咆哮
			339,	--Entangling Roots 根须缠绕
			5570,	--Insect Swarm 虫群
			8921,	--Moonfire 月火术
			33745, --Lacerate 割伤
			1079,	--Rip 割裂
			1822,	--Rake 斜掠
			33876,	--Mangle (Cat) 裂伤 - 豹
			33878,	--Mangle (Bear) 裂伤 - 熊
		}
	elseif c == "HUNTER" then
		debuffs = {
			1130,	--Hunter's Mark 猎人印记
			1978,	--Serpent Sting 毒蛇钉刺
			2974,	--Wing Clip 摔拌
			3043,	--Scorpid Sting 毒蝎钉刺
		}
	elseif c == "MAGE" then
		debuffs = {
			31589,	--Slow 减速术
			44457,	--Living Bomb 活动炸弹
		}
	elseif c == "PALADIN" then
		buffs = {
			53563,	--圣光道标
		}
		debuffs = {

		}
	elseif c == "PRIEST" then
		buffs = {
			139,	--Renew 恢复
		}
		debuffs = {
			589,	--ShadowWordPain 暗言术：痛
			2944, --Devouring Plague 噬灵瘟疫
			15286,	--Vampiric Embrace 吸血鬼的拥抱
			34914,	--Vampiric Touch 吸血鬼之触
		}
	elseif c == "SHAMAN" then
		debuffs = {
			8050,	--烈焰震击
		}
	elseif c == "WARLOCK" then
		buffs = {
			63321,	--Life Tap (Glyph) 生命分流(雕文)
		}
		debuffs = {
			172,	--Corruption 腐蚀术
			980,	--Curse of Agony 痛苦诅咒
			18223,	--Curse of Exhaustion 疲劳诅咒
			1490,	--Curse of Elements 元素诅咒
			1714,	--Curse of Tongues 语言诅咒
			702,	--Curse of Weakness 虚弱诅咒
			348,	--Immolate 献祭
			710,	--Banish 放逐术
			5782,	--Fear 恐惧术
			6358,	--Seduction 诱惑
			27243,	--Seed of Corruption 腐蚀之种
			30108,	--Unstable Affliction 痛苦无常
			48181,	--Haunt 鬼影缠身
		}
	elseif c == "WARRIOR" then
		buffs = {
			6673,	--Battle Shout 战斗怒吼
			469,	--Commanding Shout 命令怒吼
		}
		debuffs = {
			772,	--Rend 撕裂
			1160,	--Demoralizing Shout 挫志怒吼
			12323,	--Piercing Howl 刺耳怒吼
			1715,	--Hamstring 断筋
		}
	else return
	end
end

local tinsert = tinsert
local tremove = tremove
local strfind = strfind
local CreateFrame = CreateFrame
local UnitGUID = UnitGUID
local UnitBuff = UnitBuff
local UnitDebuff = UnitDebuff
local GetSpellInfo = GetSpellInfo
local GetActionTexture = GetActionTexture
local IsMouseOverKeyDown

local AppName, version, NewVersion = "DebuffCaster", 30
local slots, cds, ab, ad = {}, {}, {}, {}
local sBuffs, sDebuffs = {}, {}
local defaults, profile, args, handler = {	profile = {enable = true,
	castfix = false, mouseover = false, mouseoverkey = "none",
	show = {target = true, player = true},
	buff = {}, debuff = {}}}
local L = setmetatable({}, {__index = function(t, i) return i end})
if GetLocale() == "zhCN" then
	L["Debuff"] = "负面效果"
	L["Cast time fix"] = "施法时间修正"
	L["Mouse over"] = "鼠标指向"
	L["Mouse over key"] = "鼠标指向键"
	L["None"] = "无"
	L["New version found"] = "发现新版本"
	L["Current version"] = "当前版本"
elseif GetLocale() == "zhTW" then
	L["Debuff"] = "負面效果"
	L["Cast time fix"] = "施法時間修正"
	L["Mouse over"] = "滑鼠指向"
	L["Mouse over key"] = "滑鼠指向鍵"
	L["None"] = "無"
	L["New version found"] = "發現新版本"
	L["Current version"] = "當前版本"
end

local occ = oCC
local dc = LibStub("AceAddon-3.0"):NewAddon(AppName, "AceEvent-3.0", "AceTimer-3.0", "LibConfig-1.0")

local function SetBuff(b, e)
	local n, n1, id, sid, _
	n	= GetSpellInfo(b)
	ab[n] = e and {s = 0, d = 0, e = 0, c = b} or nil
	for i = 1, 132 do
		t, id, _, sid = GetActionInfo(i)
		if t == "macro" then n1 = GetMacroSpell(id)
		elseif t == "spell" then n1 = GetSpellInfo(sid)
		else n1 = nil
		end
		if n == n1 then
			if e then
				tinsert(ab[n], i) slots[i].s = n
			else slots[i].s = nil
			end
		end
	end
end

local function SetDebuff(d, e)
	local c, n, n1, id, sid, _
	n, _, _, _, _, _, c	= GetSpellInfo(d)
	if c < 0 then d = nil end
	ad[n] = e and {s = 0, d = 0, e = 0, c = d} or nil
	for i = 1, 132 do
		t, id, _, sid = GetActionInfo(i)
		if t == "macro" then n1 = GetMacroSpell(id)
		elseif t == "spell" then n1 = GetSpellInfo(sid)
		else n1 = nil
		end
		if n == n1 then
			if e then
				tinsert(ad[n], i) slots[i].s = n
			else slots[i].s = nil
			end
		end
	end
end

handler = {
	enable = function(v)
		if v then dc:Enable() else dc:Disable() end
	end,
	mouseover = function(v)
		if v then
			dc:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
			dc:RegisterEvent("MODIFIER_STATE_CHANGED")
		else
			dc:UnregisterEvent("UPDATE_MOUSEOVER_UNIT")
			dc:UnregisterEvent("MODIFIER_STATE_CHANGED")
		end
	end,
	mouseoverkey = function(v)
		local f = {none = function() return true end,
			ctrl = IsCtrlKeyDown, alt = IsAltKeyDown, shift = IsShiftKeyDown}
		IsMouseOverKeyDown = f[v]
	end,
	buff = buffs and SetBuff or nil,
	debuff = SetDebuff,
}

args = {
	desc = {
		name = AppName.." r"..version,
		type = "description",
		order = 0,
	},
	enable = {
		name = ENABLE,
		type = "toggle",
		order = 10,
	},
	mouseover = {
		name = L["Mouse over"],
		type = "toggle",
		order = 20,
	},
	mouseoverkey = {
		name = L["Mouse over key"],
		type = "select",
		values = {none = L["None"], ctrl = "Ctrl",
			alt = "Alt", shift = "Shift",
		},
		order = 30,
	},
	castfix = {
		name = L["Cast time fix"],
		type = "toggle",
		order = 40,
	},
	buffs = buffs and {
		name = BUFFERING,
		type = "group",
		order = 50,
		args = {
			buff = buffs and {
				name = BUFFERING,
				type = "multiselect",
				values = sBuffs,
				order = 10,
			},
		},
	} or nil,
	debuffs = {
		name = L["Debuff"],
		type = "group",
		order = 50,
		args = {
			debuff = {
				name = L["Debuff"],
				type = "multiselect",
				values = sDebuffs,
				order = 10,
			},
		},
	},
	--[[list = {
		name = "List",
		type = "execute",
		order = 70,
		func = function()	--for debug
			local lt, n
			if ab then
				print("Buffs")
				for s, a in pairs(ab) do
					print(s, #a)
					for i = 1, #a do
						print("slot "..a[i])
						for j = 1, #slots[a[i] ] do
							print("   "..j..": "..slots[a[i] ][j]:GetName())
						end
					end
				end
			end
			print("Debuffs")
			for t, a in pairs(ad) do
				print(t, #a)
				for i = 1, #a do
					print("slot "..a[i])
					for j = 1, #slots[a[i] ] do
						print("   "..j..": "..slots[a[i] ][j]:GetName())
					end
				end
			end
		end
	},]]
}

local function UpdateSlot(slot)
	local a, e
	if slots[slot].s then a = ad[slots[slot].s] or ab[slots[slot].s]
	else a = {	s = 0, d = 0}
	end
	e = a.s > 0 and 1 or 0
	for i = 1, #slots[slot] do
		local c, _ = 0
		if profile.castfix and a.c then
			_, _, _, _, _, _, c	= GetSpellInfo(a.c)
		end
		if c < 0 then c = 0 end
		CooldownFrame_SetTimer(slots[slot][i], a.s - c / 1000, a.d, e)
	end
end

local function UpdateMainBar()
	for i = 1, 132 do
		local slot, j, f = slots[i], 1
		while j <= #slot do
			a = slot[j]:GetParent().action
			if a ~= i then
				tinsert(slots[a], slot[j])
				tremove(slot, j)
				f = true
			else j = j + 1
			end
		end
		if f then UpdateSlot(a) end
	end
end

local function UpdateSpell(a)
	for i = 1, #a do UpdateSlot(a[i]) end
end

local function UpdateBuff(unit)
	for n, a in pairs(ab) do
		local f
		for i = 1, 40 do
			local c, d, et, n1, r, m, _
			n1, r, _, _, _, d, et, m = UnitBuff(unit, i, "PLAYER")
			if not n1 then break end
			if n == n1 and m then
				c = tonumber(GetSpellLink(n, r):match("spell:(%d+)"))
				a.s, a.d, a.c, f = et - d, d, c, true
				break
			end
		end
		if not f and a.s > 0 then
			a.s, a.d, f = 0, 0, true
		end
		if f then UpdateSpell(a) end
	end
end

local function UpdateUnit(unit)
	if ab then
		UpdateBuff("player")
		if UnitIsFriend("player", unit) and not UnitIsUnit("player", unit) then
			UpdateBuff(unit)
		end
	end
	if UnitCanAttack("player", unit) then
		for n, a in pairs(ad) do
			local f
			for i = 1, 40 do
				local c, d, et, n1, r, m, _
				n1, r, _, _, _, d, et, m = UnitDebuff(unit, i, "PLAYER")
				if not n1 then break end
				if n == n1 and m then
					c = tonumber(GetSpellLink(n, r):match("spell:(%d+)"))
					a.s, a.d, a.c, f = et - d, d, c, true
					break
				end
			end
			if not f and a.s > 0 then
				a.s, a.d, f = 0, 0, true
			end
			if f then UpdateSpell(a) end
		end
	end
end

local function SetTimer(this, start, duration, enable)
	if this.DCFlag then return end
	local p, a = this:GetParent()
	if p then
		a = p.action
		if a and a > 0 and a <= 132 then
			this.DCFlag = true
			if not cds[this] then
				local lt = CreateFrame("cooldown", p:GetName().."LeftTime", p)
				lt.DCFlag = true
				lt:SetReverse(true)
				lt:SetFrameLevel(this:GetFrameLevel())
				lt:SetAllPoints(this)
				cds[this] = lt
			end
			tinsert(slots[a], cds[this])
		end
	end
end

local function UpdateActionBar()
	if buffs then
		wipe(ab)
		for i, v in pairs(buffs) do
			SetBuff(v, profile.buff[v])
		end
	end
	wipe(ad)
	for i, v in pairs(debuffs) do
		SetDebuff(v, profile.debuff[v])
	end
	for i = 1, 132 do UpdateSlot(i) end
end

local function OnVersion(sender, addon, v)
	v = tonumber(v:match("%d+"))
	if v > NewVersion then
		print("|cff00ff00DebuffCaster "..L["New version found"]..": r"..v)
		print("|cffff0000DebuffCaster "..L["Current version"]..": r"..version)
		NewVersion = v
	end
end

function dc:OnProfileChanged(event, db, name)
	profile = db.profile
end

function dc:OnInitialize()
	if buffs then
		for i, v in ipairs(buffs) do
			sBuffs[v] = GetSpellInfo(v)
			defaults.profile.buff[v] = true
		end
	end
	for i, v in ipairs(debuffs) do
		sDebuffs[v] = GetSpellInfo(v)
		defaults.profile.debuff[v] = true
	end
	for i = 1, 132 do
		slots[i] = {}
	end
	self:CreateOptions(AppName, args, handler, "dc", "DebuffCasterDB", defaults)
	NewVersion = version
	LibStub("LibVersionCheck-1.0"):RegisterVersion(AppName, tostring(version), OnVersion)
	self:ScheduleTimer(function()
		UpdateActionBar()
		dc:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
		dc:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
		dc:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
		dc:RegisterEvent("CONFIRM_TALENT_WIPE")
		local _, c = UnitClass("player")
		if c == "DRUID" then
			dc:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
		end
		hooksecurefunc("CooldownFrame_SetTimer", SetTimer)
	end, 2)
end

function dc:OnEnable()
	dc:RegisterEvent("PLAYER_TARGET_CHANGED")
	if profile.mouseover then
		dc:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
		dc:RegisterEvent("MODIFIER_STATE_CHANGED")
	end
	dc:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
end

function dc:OnDisable()
	dc:UnregisterEvent("UNIT_TARGET")
	if profile.mouseover then
		dc:UnregisterEvent("UPDATE_MOUSEOVER_UNIT")
		dc:UnregisterEvent("MODIFIER_STATE_CHANGED")
	end
	dc:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
end

function dc:ACTIVE_TALENT_GROUP_CHANGED()
	self:ScheduleTimer(function()
		for cd, _ in pairs(cds) do
			cd.DCFlag = nil
		end
		for i = 1, 132 do
			slots[i].s = nil
			for j = 1, #slots[i] do tremove(slots[i]) end
		end
		UpdateActionBar()
	end, 0.2)
end

function dc:CONFIRM_TALENT_WIPE()
	self:ACTIVE_TALENT_GROUP_CHANGED()
end

function dc:ACTIONBAR_SLOT_CHANGED()
	if type(arg1) ~= "number" or arg1 <= 0 or arg1 > 132 then return end
	local t, id, _, sid, n = GetActionInfo(arg1)
	if t == "spell" then n = GetSpellInfo(sid)
	elseif t == "macro" then n = GetMacroSpell(id)
	end
	if slots[arg1].s then
		local a = ad[slots[arg1].s] or ab[slots[arg1].s]
		if a.s > 0 then f = true end
		for i = 1, #a do
			if a[i] == arg1 then tremove(a, i) break end
		end
		slots[arg1].s = nil
	end
	if n then
		local a = ad[n] or ab and ab[n]
		if a then
			tinsert(a, arg1)
			slots[arg1].s = n
			if a.s > 0 then f = true end
		end
	end
	if f then UpdateSlot(arg1) end
end

function dc:ACTIONBAR_PAGE_CHANGED()
	self:ScheduleTimer(UpdateMainBar, 0.02)
end

function dc:UPDATE_SHAPESHIFT_FORM()
	self:ScheduleTimer(UpdateMainBar, 0.02)
end

function dc:PLAYER_TARGET_CHANGED()
	if not UnitExists("target") and not ab then
		for t, a in pairs(ad) do
			if a.s > 0 then
				a.s, a.d = 0, 0
				UpdateSpell(a)
			end
		end
	else UpdateUnit("target")
	end
end

local tmrMouseOver
local function OnUpdateMouseOver()
	if not UnitExists("mouseover") then
		dc:CancelTimer(tmrMouseOver)
		dc:PLAYER_TARGET_CHANGED()
		tmrMouseOver = nil
	end
end

function dc:UPDATE_MOUSEOVER_UNIT()
	if IsMouseOverKeyDown() then
		if not tmrMouseOver then
			tmrMouseOver = self:ScheduleRepeatingTimer(OnUpdateMouseOver, 0.2)
		end
		UpdateUnit("mouseover")
	end
end

function dc:MODIFIER_STATE_CHANGED(event, key, state)
	if key:lower():find(profile.mouseoverkey) and UnitExists("mouseover") then
		if state == 1 then
			if not tmrMouseOver then
				tmrMouseOver = self:ScheduleRepeatingTimer(OnUpdateMouseOver, 0.2)
			end
			UpdateUnit("mouseover")
		elseif tmrMouseOver then
			dc:CancelTimer(tmrMouseOver)
			dc:PLAYER_TARGET_CHANGED()
			tmrMouseOver = nil
		end
	end
end

function dc:COMBAT_LOG_EVENT_UNFILTERED(_, _, eventType, srcGUID, _, _,
	dstGUID, _, _, spellID, spellName, _, spellType)
	local pid, tid, mid = UnitGUID("player"), UnitGUID("target"), UnitGUID("mouseover")
	if srcGUID ~= pid or dstGUID ~= pid	and dstGUID ~= tid and dstGUID ~= mid then return end
	local a, n, t, d, et, m
	if eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" or
		eventType == "SPELL_AURA_APPLIED_DOSE" or eventType == "SPELL_AURA_REMOVED_DOSE" then
		if spellType == "DEBUFF" then
			if dstGUID == mid then
				n, _, _, _, _, d, et, m = UnitDebuff("mouseover", spellName, nil, "PLAYER")
				if m then
					a = ad[n]
					if a then
						a.s, a.d, a.c = et - d, d, spellID
						UpdateSpell(a)
						if not tmrMouseOver and IsMouseOverKeyDown() then
							tmrMouseOver = self:ScheduleRepeatingTimer(OnUpdateMouseOver, 0.2)
						end
					end
				end
			elseif dstGUID == tid then
				n, _, _, _, _, d, et, m = UnitDebuff("target", spellName, nil, "PLAYER")
				if m then
					a = ad[n]
					if a then
						a.s, a.d, a.c = et - d, d, spellID
						UpdateSpell(a)
					end
				end
			end
		elseif ab and spellType == "BUFF" then
			local tar = dstGUID == UnitGUID("target") and "target" or "player"
			n, _, _, _, _, d, et, m = UnitBuff(tar, spellName, nil, "PLAYER")
			if m and spellID ~= 57669 then
				a = ab[n]
				if a then
					a.s, a.d, a.c = et - d, d, spellID
					UpdateSpell(a)
				end
			end
		end
	elseif eventType == "SPELL_AURA_REMOVED" then
		n, _, t = GetSpellInfo(spellID)
		a = ad[n] or ab and ab[n]
		if a and a.s > 0 and spellID ~= 57669 then
			a.s, a.d = 0, 0
			UpdateSpell(a)
			if occ then occ:ShowShine(t) end
		end
	end
end

DebuffCaster = dc
