Skada:AddLoadableModule("Debuffs", function(Skada, L)
	if Skada.db.profile.modulesBlocked.Debuffs then return end

	local debuff = Skada:NewModule(L["Debuff uptimes"], "AceTimer-3.0")
	local debuffSpell = Skada:NewModule(L["Debuff uptimes: Spell list"])
	local buffs = Skada:NewModule(L["Buff uptimes"])
	local buffSpell = Skada:NewModule(L["Buff uptimes: Spell list"])

	debuff.Order = 91
	buffs.Order = 92

	-- This is highly inefficient. Come up with something better.
	local function tick_spells(set)
		for i, player in pairs(set._playeridx) do
			for spellname, spell in pairs(player.auras) do
				if spell.active > 0 and Skada:InCombat() then
					spell.uptime = spell.uptime + 1
				end
			end
		end
	end

	-- Adds 1s to the uptime of all currently active spells.
	-- A spell is considered active if at least 1 instance of it is still active.
	-- We determine this by incrementing or subtracting from a counter.
	-- This is less messy than fooling around with the aura events.
	function debuff:Tick()
		if Skada.current then
			tick_spells(Skada.current)
			tick_spells(Skada.total)
		end
	end

	local function log_auraapply(set, aura)
		if set then

			-- Get the player.
			local player = Skada:get_player(set, aura.playerid, aura.playername, true)
			if player then
				--Skada:Print("applied "..aura.spellname.. " to "..player.shortname.. " - "..aura.auratype)
				-- Add aura to player if it does not exist.
				-- If it does exist, increment our counter of active instances by 1
				if not player.auras[aura.spellname] then
					player.auras[aura.spellname] = {id = aura.spellid, name = aura.spellname, active = 1, uptime = 0, auratype = aura.auratype}
				else
					player.auras[aura.spellname].active = player.auras[aura.spellname].active + 1
				end
			end

		end
	end

	local function log_auraremove(set, aura)
		if set then

			-- Get the player.
			local player = Skada:get_player(set, aura.playerid, aura.playername, true)
			if player then
				--Skada:Print("removed "..aura.spellname.. " to "..player.shortname.. " - "..aura.auraType)
				-- If aura does not exist, we know nothing about it and ignore it.
				-- If it does exist and we know of 1 or more active instances, subtract 1 from our counter.
				if player.auras[aura.spellname] then
					local a = player.auras[aura.spellname]
					if a.active > 0 then
						a.active = 0
					end
				end
			end

		end
	end

	local aura = {}

	local function AuraApplied(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, spellId, spellName, spellSchool, auraType)
		aura.playerid = srcGUID
		aura.playerflags = srcFlags
		aura.playername = srcName
		aura.spellid = spellId
		aura.spellname = spellName
		aura.auratype = auraType

		Skada:FixPets(aura)
		log_auraapply(Skada.current, aura)
		log_auraapply(Skada.total, aura)
	end

	local function AuraRemoved(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, spellId, spellName, spellSchool, auraType)
		aura.playerid = srcGUID
		aura.playerflags = srcFlags
		aura.playername = srcName
		aura.spellid = spellId
		aura.spellname = spellName
		aura.auratype = auraType

		Skada:FixPets(aura)
		log_auraremove(Skada.current, aura)
		log_auraremove(Skada.total, aura)
	end

	-- handle weapon-procced self-buffs that show with a null source
	-- 5/17 02:58:15.156 SPELL_AURA_APPLIED,0x0000000000000000,nil,0x4228,0x0,0x0180000005F37DDE,"Grimbit",0x511,0x0,104993,"Jade Spirit",0x2,BUFF
	local function NullAura(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
		if srcName == nil and #srcGUID == 0 and dstName and #dstGUID > 0 then
			--print(eventtype, ...)
			srcName = dstName
			srcGUID = dstGUID
			srcFlags = dstFlags

			if eventtype == 'SPELL_AURA_APPLIED' then
				AuraApplied(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
			else
				AuraRemoved(timestamp, eventtype, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
			end
		end
	end

	local function len(t)
		local l = 0
		for i,j in pairs(t) do
			l = l + 1
		end
		return l
	end

	local function updatefunc(auratype, win, set)
		local nr = 1
		local max = 0

		for i, player in pairs(set._playeridx) do
			-- Find number of debuffs.
			local auracount = 0
			local aurauptime = 0
			for spellname, spell in pairs(player.auras) do
				if spell.auratype == auratype then
					auracount = auracount + 1
					aurauptime = aurauptime + spell.uptime
				end
			end

			if auracount > 0 then
				-- Calculate player max possible uptime.
				local maxtime = Skada:PlayerActiveTime(set, player)

				-- Now divide by the number of spells to get the average uptime.
				local d = win.dataset[nr] or {}
				win.dataset[nr] = d

				d.id = player.id
				d.value = aurauptime
				d.label = player.shortname
				d.valuetext = ("%d"..L["s"].."/ %s"):format(aurauptime, Skada:FormatHitNumber(auracount))
				d.class = player.class

				if aurauptime > max then
					max = aurauptime
				end

				nr = nr + 1
			end
		end

		win.metadata.maxvalue = max
	end

	-- Detail view of a player.
	local function detailupdatefunc(auratype, win, set, playerid)
		-- View spells for this player.
		local nr = 1
		local max = 0
		local player = Skada:find_player(set, playerid)

		if player then
			-- Calculate player max possible uptime.
			local maxtime = Skada:PlayerActiveTime(set, player)

			win.metadata.maxvalue = maxtime
			if maxtime > 0 then
				for spellname, spell in pairs(player.auras) do
					if spell.auratype == auratype then
						local uptime = min(maxtime, spell.uptime)

						local d = win.dataset[nr] or {}
						win.dataset[nr] = d

						d.id = spell.name
						d.value = uptime
						d.label = spell.name
						local _, _, icon = GetSpellInfo(spell.id)
						d.icon = icon
						d.spellid = spell.id
						d.valuetext = ("%d"..L["s"].."/ %02.1f%%"):format(uptime, uptime / maxtime * 100)

						nr = nr + 1
					end
				end
			end
		end

	end

	function debuff:Update(win, set)
		updatefunc("DEBUFF", win, set)
	end

	function debuffSpell:Enter(win, id, label)
		win.modedata.playerid = id
		self.title = L["Debuffs"]..": "..label
	end

	function debuffSpell:Update(win, set)
		detailupdatefunc("DEBUFF", win, set, win.modedata.playerid)
	end

	function buffs:Update(win, set)
		updatefunc("BUFF", win, set)
	end

	function buffSpell:Enter(win, id, label)
		win.modedata.playerid = id
		self.title = L["Buffs"]..": "..label
	end

	-- Detail view of a player.
	function buffSpell:Update(win, set)
		detailupdatefunc("BUFF", win, set, win.modedata.playerid)
	end

	function debuff:OnEnable()
		debuff.metadata 		= {showspots = 1, click1 = debuffSpell, click2 = buffSpell}
		debuffSpell.metadata 	= {}
		buffs.metadata 			= {showspots = 1, click1 = buffSpell, click2 = debuffSpell}
		buffSpell.metadata 		= {}

		Skada:RegisterForCL(AuraApplied, 'SPELL_AURA_APPLIED', {src_is_interesting = true})
		Skada:RegisterForCL(AuraRemoved, 'SPELL_AURA_REMOVED', {src_is_interesting = true})

		-- ticket 307: some weapon-procced self buffs (eg Jade Spirit) have a null src
		Skada:RegisterForCL(NullAura, 'SPELL_AURA_APPLIED', {dst_is_interesting_nopets = true, src_is_not_interesting = true})
		Skada:RegisterForCL(NullAura, 'SPELL_AURA_REMOVED', {dst_is_interesting_nopets = true, src_is_not_interesting = true})

		self:ScheduleRepeatingTimer("Tick", 1)

		Skada:AddMode(self)
		Skada:AddMode(buffs)
	end

	function debuff:OnDisable()
		Skada:RemoveMode(self)
		Skada:RemoveMode(buffs)
	end

	-- Called by Skada when a new player is added to a set.
	function debuff:AddPlayerAttributes(player)
		if not player.auras then
			player.auras = {}
		end
	end

	-- Called by Skada when a new set is created.
	function debuff:AddSetAttributes(set)
	end
end)
