-- *********************************************************
-- **               Deadly Boss Mods - Core               **
-- **            http://www.deadlybossmods.com            **
-- *********************************************************
--
-- This addon is written and copyrighted by:
--    * Paul Emmerich (Tandanu @ EU-Aegwynn) (DBM-Core)
--    * Martin Verges (Nitram @ EU-Azshara) (DBM-GUI)
--
-- The localizations are written by:
--    * deDE: Tandanu/Nitram
--    * enGB: Nitram/Tandanu
--    * zhCN: yleaf(yaroot@gmail.com)
--    * zhTW: yleaf(yaroot@gmail.com)
--    * (add your names here!)
--
--
-- The code of this addon is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License. (see license.txt)
-- All included textures and sounds are copyrighted by their respective owners.
--
--
--  You are free:
--    * to Share — to copy, distribute, display, and perform the work
--    * to Remix — to make derivative works
--  Under the following conditions:
--    * Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
--    * Noncommercial. You may not use this work for commercial purposes.
--    * Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one.


---------------
--  Globals  --
---------------
DBMBurningCrusade_SavedModOptions = {}
DBMBC = {}
DBM_SBT = {}


--------------
--  Locals  --
--------------
local frame = CreateFrame("Frame")

local function checkEntry(t, val)
	for i, v in ipairs(t) do
		if v == val then
			return true
		end
	end
	return false
end

----------------------
--  Event Handling  --
----------------------
local function registerEvents(...)
	for i = 1, select("#", ...) do
		frame:RegisterEvent(select(i, ...))
	end
end

frame:SetScript("OnEvent", function(self, event, ...) if DBMBC[event] then DBMBC[event](DBMBC, ...) end end)

registerEvents("ADDON_LOADED")


---------------
--  Options  --
---------------
local function loadModOptions(mod)
end


--------------
--  OnLoad  --
--------------
local bannedMods = { -- a list of "banned" (meaning they are replaced by another mod like DBM-Battlegrounds (replaced by DBM-PvP)) boss mods, these mods will not be loaded by DBM (and they wont show up in the GUI)
	"DBM_Auchindoun",	-- replaced by DBM-Party-BC
	"DBM_Coilfang",		-- replaced by DBM-Party-BC
	"DBM_CoT",			-- replaced by DBM-Party-BC
	"DBM_TempestKeep",	-- replaced by DBM-Party-BC
	"DBM_Terrace",		-- replaced by DBM-Party-BC
}

function DBMBC:ADDON_LOADED(mod)
	if mod ~= "DBM-BurningCrusade" then return end
	for i = 1, GetNumAddOns() do
		if GetAddOnMetadata(i, "X-DBM-BC-AddOn") and not checkEntry(bannedMods, GetAddOnInfo(i)) then
			table.insert(DBM.AddOns, {
				sort		= tonumber(GetAddOnMetadata(i, "X-DBM-Tab-Sort") or math.huge) or math.huge,
				category	= "BC",
				name		= GetAddOnMetadata(i, "X-DBM-Name") or "",
				zone		= {strsplit(",", GetAddOnMetadata(i, "X-DBM-Tab-LoadZone") or "")},
				zoneId		= {strsplit(",", GetAddOnMetadata(i, "X-DBM-Mod-LoadZoneID") or "")},
				v3Tab		= GetAddOnMetadata(i, "X-DBM-Tab-ID"),
				modId		= GetAddOnInfo(i),
			})
		end
		for i = #DBM.AddOns[#DBM.AddOns].zoneId, 1, -1 do
			local id = tonumber(DBM.AddOns[#DBM.AddOns].zoneId[i])
			if id then
				DBM.AddOns[#DBM.AddOns].zoneId[i] = id
			else
				table.remove(DBM.AddOns[#DBM.AddOns].zoneId, i)
			end
		end
	end
	table.sort(DBM.AddOns, function(v1, v2) return v1.sort < v2.sort end)
	registerEvents("RAID_ROSTER_UPDATE")
	DBM:ZONE_CHANGED_NEW_AREA()
end


-------------------------------
--  (Faked) Boss Mod Object  --
-------------------------------
local proxy = {}


-----------------------
--  General Methods  --
-----------------------
function proxy:RegisterEvents(...)
	self.mod:RegisterEvents(...)
	for i = 1, select("#", ...) do
		local event = select(i, ...)
		self.mod[event] = function(self, ...)
			if self.proxy.OnEvent then self.proxy:OnEvent(event, ...) end
		end
	end
end

function proxy:SendSync(...)
	self.mod:SendSync(...)
end

function proxy:AddMsg(...)
	self.mod:AddMsg(...)
end

function proxy:AddOption(id, default, name, func)
	self.mod:AddBoolOption(id, default, "announce", func)
	DBM:GetModLocalization(self.mod.id):SetOptionLocalization({
		[id] = name
	})
end

function proxy:AddBarOption(bar, default)
	self.mod:AddBoolOption(bar:gsub("%(%.%*%)", DBM_BC_TARGET), default, "timer")
	if DBM_SBT[bar] then
		DBM:GetModLocalization(self.mod.id):SetOptionLocalization({
			[bar] = DBM_SBT[bar]
		})
	end
end

function proxy:IsWipe()
	local dead = 0
	for i = 1, GetNumRaidMembers() do
		dead = dead + ((UnitIsDeadOrGhost("raid"..i) and 1) or 0)
	end
	return dead >= 18
end

function proxy:SetBossHealthInfo(...)
	self.mod:SetBossHealthInfo(...)
end


--------------
--  Combat  --
--------------
function proxy:RegisterCombat(type, ...)
	self.mod:RegisterCombat(((type and type:lower()) or "combat"), ...)
end

function proxy:SetMinCombatTime(...)
	self.mod:SetMinCombatTime(...)
end

function proxy:SetCreatureID(...)
	self.mod:SetCreatureID(...)
end


-------------------
--  Range Check  --
-------------------
function DBM_Gui_DistanceFrame_Show()
	DBM.RangeCheck:Show()
end

function DBM_Gui_DistanceFrame_Hide()
	DBM.RangeCheck:Hide()
end

function DBM_Gui_DistanceFrame_SetDistance(distance)
	DBM.RangeCheck:Show(distance)
end

-----------------
--  Announces  --
-----------------
local lastWarning
local lastWarningTime = 0
function proxy:Announce(msg, color, noBroadcast)
	local warning = self["warning"..(color or 1)]
	if not warning then return end
	msg = msg:gsub("%s*%*%*%*%s*", "")
	if msg ~= lastWarning or GetTime() - lastWarningTime > 1.5 then
		lastWarning = msg
		lastWarningTime = GetTime()
		warning:Show(msg)
	end
end

function proxy:ScheduleAnnounce(timer, msg, color)
	local warning = self["warning"..(color or 1)]
	if not warning then return end
	warning:Schedule(timer, msg)
end

function proxy:UnScheduleAnnounce(msg, color)
	local warning = self["warning"..(color or 1)]
	if not warning then return end
	warning:Cancel(msg)
end

function proxy:AddSpecialWarning(text)
	self.specWarning:Show(text)
end

function proxy:SendHiddenWhisper(msg, player)
	self.mod:SendWhisper(msg, player)
end


----------------
--  Schedule  --
----------------
function proxy:Schedule(timer, func, ...)
	self.mod:Schedule(timer, func, ...)
end

function proxy:ScheduleEvent(timer, ...)
	self.mod:Schedule(timer, self.OnEvent, self, ...)
end
proxy.ScheduleSelf = proxy.ScheduleEvent


function proxy:ScheduleMethod(timer, method, ...)
	self.mod:Schedule(timer, self[method], self, ...)
end

function proxy:UnSchedule(...)
	self.mod:Unschedule(...)
end

function proxy:UnScheduleEvent(...)
	self.mod:Unschedule(self.OnEvent, self, ...)
end
proxy.UnScheduleSelf = proxy.UnScheduleEvent

function proxy:UnScheduleMethod(method, ...)
	self.mod:Unschedule(self[method], self, ...)
end

function proxy:UnScheduleAll()
	self.mod:Unschedule()
end

function proxy:GetEventScheduleTimeLeft()
	return 0
end
proxy.GetSelfScheduleTimeLeft = proxy.GetEventScheduleTimeLeft

function proxy:GetAnnounceScheduleTimeLeft()
	return 0
end

function proxy:GetMethodScheduleTimeLeft()
	return 0
end



------------
--  Bars  --
------------
function proxy:StartStatusBarTimer(timer, name, icon, noBroadcast, repetitions, colorR, colorG, colorB, colorA)
	if self.Options[name] == false then return end
	self.timer:Start(timer, name)
	self.timer:UpdateIcon(icon, name)
end

function proxy:UpdateStatusBarTimer(name, elapsed, timer, newName, newIcon, noBroadcast)
	self.timer:Update(elapsed, timer, name)
	if newIcon then self.timer:UpdateIcon(newIcon, name) end
	if newName then self.timer:UpdateName(newName, name) end
end

function proxy:GetStatusBarTimerTimeLeft(name)
	self.timer:GetTime(name)
end

function proxy:EndStatusBarTimer(name)
	self.timer:Stop(name)
end

-------------
--  Icons  --
-------------
function proxy:SetIcon(target, timer, icon)
	self.mod:SetIcon(target, icon, timer)
end

function proxy:RemoveIcon(...)
	self.mod:RemoveIcon(...)
end

function proxy:RemoveAllIcons()
	return 0
end


--------------------------
--  Callback Functions  --
--------------------------
local function onSync(self, ...)
	if self.proxy.OnSync then self.proxy:OnSync(...) end
end

local function onCombatStart(self, delay, ...)
	self.proxy.InCombat = true
	if self.proxy.OnCombatStart then self.proxy:OnCombatStart(delay or 0, ...) end
end

local function onCombatEnd(self, ...)
	if self.proxy.OnCombatEnd then self.proxy:OnCombatEnd(...) end
	self.proxy.InCombat = false
end

local function onUpdate(self, elapsed)
	if self.proxy.OnUpdate then self.proxy:OnUpdate(elapsed) end
end

local function onInitialize(self)
	if self.proxy.OnUpdate then	self:RegisterOnUpdateHandler(onUpdate, self.proxy.UpdateInterval or 0.2) end
	self.proxy.Options = self.Options
end

-------------------
--  Constructor  --
-------------------
do
	local function getAddOnIDByTab(tab)
		for i, v in ipairs(DBM.AddOns) do
			if tab == v.v3Tab then
				return v.modId
			end
		end
	end
	local mt = {__index = proxy}

	function DBM:NewBossMod(id, name, _, zone, tab)
		local obj = setmetatable({}, mt)
		local mod = DBM:NewMod(id, getAddOnIDByTab(tab))
		mod:SetRevision(1)
		mod:SetZone(zone)
		obj.timer = mod:NewTimer(10, "%s", nil, nil, false)
		obj.warning1 = mod:NewAnnounce("%s", 1, nil, nil, false)
		obj.warning2 = mod:NewAnnounce("%s", 2, nil, nil, false)
		obj.warning3 = mod:NewAnnounce("%s", 3, nil, nil, false)
		obj.warning4 = mod:NewAnnounce("%s", 4, nil, nil, false)
		obj.specWarning = mod:NewSpecialWarning("%s", nil, false)
		DBM:GetModLocalization(id):SetGeneralLocalization({
			name = name
		})
		obj.mod = mod
		obj.Options = mod.Options
		mod.proxy = obj
		mod.OnSync = onSync
		mod.OnCombatStart = onCombatStart
		mod.OnCombatEnd = onCombatEnd
		mod.OnInitialize = onInitialize
		return obj
	end
end

----------------------
--  Misc functions  --
----------------------

function DBM:GetMod(id)
	return DBM:GetModByName(id) and DBM:GetModByName(id).proxy
end

function DBM.SecondsToTime(t)
	if t <= 60 then
		return ("%.1f"):format(t)
	else
		return ("%d:%0.2d"):format(t/60, math.fmod(t, 60))
	end
end

function DBM.GetBuff(unitID, buff)
	local i = 1
	while UnitBuff(unitID, i) do
		if UnitBuff(unitID, i) == buff then
			return i
		end
		i = i + 1
	end
end

function DBM.GetDebuff(unitID, buff)
	local i = 1
	while UnitDebuff(unitID, i) do
		if UnitDebuff(unitID, i) == buff then
			return i
		end
		i = i + 1
	end
end

DBM.Rank = 0
do
	local function updateRank()
		DBM.Rank = DBM:GetRaidRank() or 0
	end

	function DBMBC:RAID_ROSTER_UPDATE()
		DBM:Schedule(0, updateRank)
	end
end


-------------------------------
--  Schedule function hooks  --
-------------------------------

do
	local old = DBM.Schedule
	function DBM:Schedule(t, ...)
		if type(self) == "number" then
			return old(DBM, self, t, ...)
		else
			return old(self, t, ...)
		end
	end
end

do
	local old = DBM.Unschedule
	function DBM:Unschedule(...)
		if type(self) == "function" then
			return old(DBM, self, ...)
		else
			return old(self, ...)
		end
	end
end

function DBM.UnSchedule(...)
	return DBM:Unschedule(...)
end
