local _G = getfenv(0)
local CompactRunes = LibStub("AceAddon-3.0"):GetAddon("Compact Runes")
local ADDON_NAME, addon = ...
local L = LibStub("AceLocale-3.0"):GetLocale(addon.addonName)
local LSM = _G.LibStub:GetLibrary("LibSharedMedia-3.0")

local GetSpellCooldown = _G.GetSpellCooldown
local GetTime = _G.GetTime
local ceil = _G.math.ceil
local floor = _G.math.floor
local UnitDebuff = _G.UnitDebuff
local tostring = _G.tostring
local SpellIds = addon.SpellIds
local SpellNames = addon.SpellNames

local BlockDisplay = {}

local module = {}
module.name = "Necroticblight"
module.displayName = "Necroticblight"
addon:RegisterModule(module.name, module)

local MIN_UPDATE_TIME = addon.MIN_UPDATE_TIME
local FesExt = 6  -- Seconds added by FeS

function module:SetProfile()
	self.profile = CompactRunes.db.profile.blocks.NecroticBlight
end

function module:OnInitialize()
	if CompactRunes.isDK then
		addon:RegisterCallback("TalentUpdate", module.name, module.TalentUpdate)
	end
	addon:RegisterCallback("ProfileUpdate", module.name, module.ProfileUpdate)
	self:SetProfile()
end

function module:Enable()
	self:Toggle()
end

function module:Disable()
	self:OnDisable()
end

function module:Toggle()
	if not CompactRunes.isDK then return end
	if self.profile.enabled and CompactRunes.currentSpec == "Unholy" 
		and addon.HasActiveTalent("Necrotic Plague") and 
		addon.HasActiveTalent("Unholy Blight") then
		self:OnEnable()
	else
		self:OnDisable()
	end
end

local WatchedEvents = {
	"SPELL_UPDATE_COOLDOWN",
	"PLAYER_TARGET_CHANGED",
}

function module:OnEnable()
	self.eventFrame = self.eventFrame or _G.CreateFrame("frame")
	self.eventFrame:SetScript("OnEvent", self.EventHandler)
	for i, event in _G.ipairs(WatchedEvents) do
		self.eventFrame:RegisterEvent(event)
	end
	self.eventFrame:RegisterUnitEvent("UNIT_AURA", "target")
	if not self.display then self:CreateDisplay() end
	addon:RegisterCallback("CombatStart", module.name, module.CombatStart)
	addon:RegisterCallback("CombatEnd", module.name, module.CombatEnd)
	if self.display then
		self.display:Show()
		self.display:SetAlpha()
	end
	self.enabled = true
	if CompactRunes.db.profile.debug then
		CompactRunes:Print("Necroticblight module loaded.")
	end
end

function module:OnDisable()
	if self.eventFrame then
		self.eventFrame:UnregisterAllEvents()
		self.eventFrame:SetScript("OnEvent", nil)
	end
	if self.display then self.display:Hide() end
	addon:UnregisterCallback("CombatStart", module.name)
	addon:UnregisterCallback("CombatEnd", module.name)
	self.enabled = false
	if CompactRunes.db.profile.debug then
		CompactRunes:Print("Necroticblight module unloaded.")
	end
end

function module:CreateDisplay()
	self.display = BlockDisplay:Create("NecroticBlight", _G.UIParent)
end

function module:UpdateDisplay()
end

function module.CombatStart()
	local display = module.display
	if display then
		module:UpdateCooldown()
		display:SetAlpha()
	end
end

function module.CombatEnd()
	local display = module.display
	if display then
		display:SetAlpha()
		display.frame.active = false
		display.value:Hide()
		display.frame:SetScript("OnUpdate", nil)
	end
end

function module.TalentUpdate()
	module:Toggle()
end

function module.ProfileUpdate()
	module:SetProfile()
end

function module.OnUpdate(self, elapsed)
	self.lastUpdate = (self.lastUpdate or 0) + elapsed
	self.timer = self.timer - elapsed
	module.ubcd = module.ubcd - elapsed
	if self.lastUpdate >= MIN_UPDATE_TIME then
		self.lastUpdate = 0
		if self.active then
			if module.ubcd < 0 then module.ubcd = 0 end
			if self.timer < 0 then
				self.timer = 0
				self.active = false
			end
			local status = self.timer < module.profile.threshold and "Low" or "Good"
			if module.indicator ~= status then
				module.display:SetTextColor(status)
				module.indicator = status
			end

			local gap = module.ubcd - self.timer
			local fes = self.timer > 0 and (ceil((gap > 0 and gap or 0)/FesExt)) or 0
			self.obj.value:SetText(tostring(fes))
			if fes == 0 and not module.profile.alwaysShowCount then
				self.obj.value:Hide()
			else
				self.obj.value:Show()
			end
		else
			self.obj.value:SetText("0")
			self.obj.value:Hide()
			self:SetScript("OnUpdate", nil)
		end
	end
end

function module:UpdateCooldown()
	local start, duration, enabled = 
		GetSpellCooldown(SpellIds["Unholy Blight"])
	module.ubcd = (duration > 1.5) and (start + duration - GetTime()) or 0
end

function module:UpdateDisease()
	local frame = self.display.frame
	if not frame then return end

	local name, rank, icon, count, dispelType, duration, expires, 
		caster, isStealable, shouldConsolidate, spellId, canApplyAura, 
		isBossDebuff, castByPlayer, value1, value2, value3 

	name, rank, icon, count, dispelType, duration, expires, 
	caster, isStealable, shouldConsolidate, spellId, canApplyAura, 
	isBossDebuff, castByPlayer, value1, value2, value3 
	= UnitDebuff("target", SpellNames["Necrotic Plague"], nil, "PLAYER")

	local changed = false
	if name then
		if not frame.active then changed = true end
		frame.timer = expires - GetTime()		
		frame.active = true
		frame:SetScript("OnUpdate", module.OnUpdate)
		self.display.value:Show()
	else
		if frame.active then changed = true end
		frame.timer = 0
		frame.active = false
		frame:SetScript("OnUpdate", nil)
		self.display.value:Hide()
	end

	if changed then self.display:SetAlpha() end
end

function module.EventHandler(frame, event, ...)
	if event == "SPELL_UPDATE_COOLDOWN" then
		module:UpdateCooldown()
	elseif event == "UNIT_AURA" then
		module:UpdateDisease()
	elseif event == "PLAYER_TARGET_CHANGED" then
		module:UpdateDisease()
	end
end

function module:GetOptions()
	return "necroticBlightOpts", self:GetModuleOptions()
end

function module:AddOptions()
	return "Necroticblight", L["Necroticblight"], "necroticBlightOpts"
end

function module:GetModuleOptions()
	local necroticBlightOpts = {
		order = 2,
		type = "group",
		name = L["Necroticblight"],
		desc = L["Necroticblight"],
		args = {
			description = {
				order = 1,
				type = "description",
				name = L["Necroticblight_Desc"],
			},
			generalOptions = {
				order = 2,
				type = "header",
				name = L["General Options"],
			},
			enable = {
				name = L["Enabled"],
				desc = L["Enabled"],
				type = "toggle",
				order = 10,
				set = function(info, val)
					self.profile.enabled = val
					self:Toggle()
				end,
				get = function(info)
					return self.profile.enabled
				end,
			},
			showZero = {
				name = L["Always Show Count"],
				desc = L["AlwaysShowCount_Desc"],
				type = "toggle",
				order = 20,
				set = function(info, val)
					self.profile.alwaysShowCount = val
				end,
				get = function(info)
					return self.profile.alwaysShowCount
				end,
			},
			threshold = {
				order = 30,
				name = L["Threshold"],
				desc = L["Threshold"],	
				type = "range",
				min = 0,
				max = 30,
				step = 1,
				set = function(info, val)
					self.profile.threshold = val
				end,
				get = function(info, val)
					return self.profile.threshold
				end,
			},
	    displayAlphas = {
				order = 50,
				type = "header",
				name = L["Display Alpha"],
	    },
			oocAlpha = {
				order = 52,
				name = L["Out of Combat"],
				desc = L["Out of Combat"],
				type = "range",
				min = 0,
				max = 1,
				step = 0.05,
				set = function(info, val)
					self.profile.oocAlpha = val
					if self.display and not _G.InCombatLockdown() then
						self.display:SetAlpha()
					end
				end,
				get = function(info, val)
					return self.profile.oocAlpha
				end,					
			},
			combatAlpha = {
				order = 54,
				name = L["In Combat"],
				desc = L["In Combat"],
				type = "range",
				min = 0,
				max = 1,
				step = 0.05,
				set = function(info, val)
					self.profile.combatAlpha = val
				end,
				get = function(info, val)
					return self.profile.combatAlpha
				end,					
			},
			diseasedAlpha = {
				order = 56,
				name = L["Diseased"],
				desc = L["DiseasedAlpha_Desc"],
				type = "range",
				min = 0,
				max = 1,
				step = 0.05,
				set = function(info, val)
					self.profile.diseasedAlpha = val
				end,
				get = function(info, val)
					return self.profile.diseasedAlpha
				end,					
			},
	    layoutHdr = {
	        order = 100,
	        type = "header",
	        name = L["Layout"],
	    },
			xoffset = {
				order = 110,
				name = L["X Offset"],
				desc = L["XOffset_Desc"],	
				type = "range",
				softMin = -floor(_G.GetScreenWidth()),
				softMax = floor(_G.GetScreenWidth()),
				bigStep = 1,
				set = function(info, val)
					self.profile.x = val
					if self.display then
						self.display:UpdateLayout()
					end
				end,
				get = function(info, val)
					return self.profile.x
				end,
			},
			yoffset = {
				order = 120,
				name = L["Y Offset"],
				desc = L["YOffset_Desc"],	
				type = "range",
				softMin = -floor(_G.GetScreenHeight()),
				softMax = floor(_G.GetScreenHeight()),
				bigStep = 1,
				set = function(info, val)
					self.profile.y = val
					if self.display then
						self.display:UpdateLayout()
					end
				end,
				get = function(info, val)
					return self.profile.y
				end,
			},
			dimensions = {
				order = 200,
				type = "header",
				name = L["Dimensions"],
			},
			width = {
				order = 220,
				name = L["Width"],
				desc = L["BarWidth_Desc"],	
				type = "range",
				min = 10,
				max = 100,
				step = 1,
				set = function(info, val)
					self.profile.width = val
					if self.display then self.display:UpdateLayout() end
				end,
				get = function(info, val)
					return self.profile.width
				end,
			},
			height = {
				order = 230,
				name = L["Height"],
				desc = L["BarHeight_Desc"],
				type = "range",
				min = 10,
				max = 100,
				step = 1,
				set = function(info, val)
					self.profile.height = val
					if self.display then self.display:UpdateLayout() end
				end,
				get = function(info, val)
					return self.profile.height
				end,
			},
			fontSize = {
				order = 310,
				name = L["Font size"],
				desc = L["Font size"],
				type = "range",
				min = 8,
				max = 30,
				step = 1,
				set = function(info, val) 
					self.profile.fontSize = val
					if self.display then self.display:ResetFonts() end
				end,
				get = function(info,val) return self.profile.fontSize end,
			},
		},
	}
	return necroticBlightOpts
end

BlockDisplay.__index = BlockDisplay

function BlockDisplay:Create(name, parent)
	local object = _G.setmetatable({}, BlockDisplay)
	object.name = name
	object.parent = parent
	object.db = CompactRunes.db.profile.blocks[name]
	object:Initialize()
	return object
end

function BlockDisplay:Initialize()
	local baseName = "CompactRunes"
	self.varName = self.name:gsub(" ", "")
	local frame = _G.CreateFrame("Frame", 
		baseName.."_"..self.name.."_Block", self.parent)
	self.frame = frame
	frame.obj = self
	frame.parentFrame = self.parent

	frame.texture = frame:CreateTexture(nil, "ARTWORK")
	frame.texture:SetAllPoints(frame)
	frame.texture:SetVertexColor(1, 1, 1)
	frame.texture:SetTexture(addon.SpellIcons["Festering Strike"])

	self.value = frame:CreateFontString(nil, "OVERLAY")
	self.value:SetPoint("CENTER")
	self.value:SetJustifyH("CENTER")
	self.value:Show()

	frame.lock = CompactRunes.db.profile.locked
	frame:SetMovable(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart",
		function(self, button)
			if not self.lock then
				self:StartMoving()
			end
		end)
	frame:SetScript("OnDragStop",
		function(self)
			self:StopMovingOrSizing()
			local scale = self:GetEffectiveScale() / _G.UIParent:GetEffectiveScale()
			local x, y = self:GetCenter()
			x, y = x * scale, y * scale
			x = x - _G.GetScreenWidth()/2
			y = y - _G.GetScreenHeight()/2
			x = x / self:GetScale()
			y = y / self:GetScale()
			self.obj.db.x, self.obj.db.y = x, y
			self:SetUserPlaced(false);
		end)
	frame:EnableMouse(self.db.attached and not frame.lock)
	self:UpdateLayout()
	self:ResetFonts()
end

function BlockDisplay:UpdateColor()
	local c = self.db.bkgcolor
	self.frame:SetBackdropColor(c.r, c.g, c.b, c.a)
end

function BlockDisplay:UpdateLayout()
	local frame = self.frame
	frame:EnableMouse(not self.db.attached and not frame.lock)
	frame:SetWidth(self.db.width)
	frame:SetHeight(self.db.height)

	frame:ClearAllPoints()
	frame:SetPoint("CENTER", _G.UIParent, "CENTER", self.db.x, self.db.y)

	self:SetAlpha()

--[[
	if self.db.background then
		frame:SetBackdrop({
			bgFile = "Interface\\Buttons\\WHITE8X8", 
			edgeFile = "",
			tile = true, tileSize = 16, edgeSize = 16,
			insets = { left = 0, right = 0, top = 0, bottom = 0 }
			})
		self:UpdateColor()
	else
		frame:SetBackdrop(nil)
	end
]]--
end

function BlockDisplay:SetTextColor(indicator)
	if not indicator then return end
	local c = self.db.indicators[indicator]
	self.value:SetTextColor(c.r, c.g, c.b, c.a)
end

function BlockDisplay:ResetFonts()
	local ff, fh, fflags = CompactRunes:GetFontSettings()
	local font = LSM:Fetch("font", ff)
	if self.value then
		self.value:SetFont(font, self.db.fontSize or fh, fflags)
		self.value:SetText(self.value:GetText())
		local c = self.db.textcolor
		self.value:SetTextColor(c.r, c.g, c.b, c.a)
	end
end

function BlockDisplay:Show()
	self.frame:Show()
end

function BlockDisplay:Hide()
	self.frame:Hide()
end

function BlockDisplay:SetAlpha()
	if not module.enabled then return end

	local alpha = 0
	local diseased = self.frame.active

	if _G.UnitAffectingCombat("player") then
		alpha = diseased and module.profile.diseasedAlpha or module.profile.combatAlpha
	else
		alpha = module.profile.oocAlpha
	end

	self.frame:SetAlpha(alpha)
end
