------------------------------------------------
--                CT_BuffMod                  --
--                                            --
-- Mod that allows you to heavily customize   --
-- the display of buffs to your liking.       --
-- Please do not modify or otherwise          --
-- redistribute this without the consent of   --
-- the CTMod Team. Thank you.                 --
------------------------------------------------

local _G = getfenv(0);
local module = _G.CT_BuffMod;

local BUFF = "HELPFUL|PASSIVE";
local DEBUFF = "HARMFUL";
local TOOLTIP = CreateFrame("GameTooltip", "CT_BuffModTooltip", nil, "GameTooltipTemplate");
local TOOLTIP_TITLE = _G.CT_BuffModTooltipTextLeft1;
TOOLTIP:SetOwner(WorldFrame, "ANCHOR_NONE");

local DELTA_TIMELEFT_THRESHOLD = 0.5;
local AURA_UPDATE_FREQUENCY = 1.0;

--------------------------------------------
-- Local copies

local GetPlayerBuff = GetPlayerBuff;
local GetPlayerBuffName = GetPlayerBuffName;
local GetPlayerBuffApplications = GetPlayerBuffApplications;
local GetPlayerBuffTexture = GetPlayerBuffTexture;
local GetPlayerBuffTimeLeft = GetPlayerBuffTimeLeft;
local GetWeaponEnchantInfo = GetWeaponEnchantInfo;
local strmatch = strmatch;

--------------------------------------------
-- Gather data

local auraTable = { };
local maxBuffId = 0;
local maxDebuffId = 0;
local mainHandEnchant, offHandEnchant;
local showItemBuffs = true;

local auratypeBUFF = 1;
local auratypeDEBUFF = 2;
local auratypeITEM = 3;


local function getAuraApplications(auraIndex)
	local tblEntry = auraTable[auraIndex];
	local _, count;
	if (tblEntry.bdType == auratypeBUFF) then
		_, _, _, count = UnitBuff("player", tblEntry.bdIndex);
	elseif (tblEntry.bdType == auratypeDEBUFF) then
		_, _, _, count = UnitDebuff("player", tblEntry.bdIndex);
	else
		count = 0;
	end
	if (not count) then
		count = 0;
	end
	return count;
end

local function getAuraDispelType(auraIndex)
	local tblEntry = auraTable[auraIndex];
	local _, debuffType;
	if (tblEntry.bdType == auratypeBUFF) then
		_, _, _, _, debuffType = UnitBuff("player", tblEntry.bdIndex);
	elseif (tblEntry.bdType == auratypeDEBUFF) then
		_, _, _, _, debuffType = UnitDebuff("player", tblEntry.bdIndex);
	else
		debuffType = "";
	end
	if (not debuffType) then
		debuffType = "";
	end
	return debuffType;
end

module.getAuraApplications = getAuraApplications;
module.getAuraDispelType = getAuraDispelType;

local function getAuraTimeLeft(bdType, bdIndex)
	local _, duration, expirationTime, timeLeft;
	if (bdType == auratypeBUFF) then
		_, _, _, _, _, duration, expirationTime = UnitBuff("player", bdIndex);
	elseif (bdType == auratypeDEBUFF) then
		_, _, _, _, _, duration, expirationTime = UnitDebuff("player", bdIndex);
	else
		expirationTime = GetTime();
		duration = expirationTime;
	end
	if (not expirationTime) then
		expirationTime = GetTime();
		duration = expirationTime;
	end
	timeLeft = expirationTime - GetTime();
	if (timeLeft < 0) then
		timeLeft = 0;
	end
	return timeLeft, duration;
end

local function removeAura(auraIndex, tblEntry)	
	local type = tblEntry.type;
	if ( type == "BUFF" or type == "AURA" ) then
		maxBuffId = maxBuffId - 1;
	else
		maxDebuffId = maxDebuffId - 1;
	end
	tremove(auraTable, auraIndex);
	tblEntry:drop();
end

--[[ local function getBuffType(auraIndex, newBuffId, timeLeft)
	timeLeft = timeLeft or GetPlayerBuffTimeLeft(auraIndex);
	if ( newBuffId ) then
		if ( GetPlayerBuff(newBuffId, BUFF) == auraIndex ) then
			if ( timeLeft == 0 ) then
				return "AURA";
			else
				return "BUFF";
		else
			return "DEBUFF";
		end
	else
		-- Loop through all buffs
	end
end ]]

local function addAura(auraIndex, bdType, texture, timeLeft, name, bdIndex, duration)
	local tblEntry = module:getBuffObject();
	
	tblEntry.name = name;
	tblEntry.texture = texture;
	tblEntry.timeleft = timeLeft;
	tblEntry.bdIndex = bdIndex;
	tblEntry.bdType = bdType;
	tblEntry.duration = duration;
	
	if (bdType == auratypeBUFF) then
		if ( timeLeft == 0 ) then
			tblEntry.type = "AURA";
		else
			tblEntry.type = "BUFF";
		end
		maxBuffId = maxBuffId + 1;
	else
		tblEntry.type = "DEBUFF";
		maxDebuffId = maxDebuffId + 1
	end
	
	tinsert(auraTable, tblEntry);
	tblEntry:add(auraIndex, bdType, bdIndex, nil, duration);
end

local function isAuraInThreshold(timeLeft, tblEntry)
	return abs(timeLeft - tblEntry.timeleft) <= AURA_UPDATE_FREQUENCY;
end

local function scanAuras2(auraIndex, bdType)
	local isIdentical, tblEntry, timeLeft, readNext;
	local bdIndex = 1;
	local func;
	if (bdType == auratypeBUFF) then
		func = UnitBuff;
	else
		func = UnitDebuff;
	end
	local name, rank, texture, count, debuffType, duration, expirationTime = func("player", bdIndex);
	while (name) do
		readNext = true;
		timeLeft = expirationTime - GetTime();
		if (timeLeft < 0) then
			timeLeft = 0;
		end
		tblEntry = auraTable[auraIndex];
		if (tblEntry) then
			isIdentical = ( tblEntry.texture == texture and ( isAuraInThreshold(timeLeft, tblEntry) or tblEntry.name == name ) );
			if (isIdentical) then
				tblEntry.bdIndex = bdIndex;
				auraIndex = auraIndex + 1;
			else
				removeAura(auraIndex, tblEntry);
				if ( auraIndex > #auraTable ) then
					-- Removed the last one in the list, and it was replaced with something else, so add the new one.
					addAura(auraIndex, bdType, texture, timeLeft, name, bdIndex, duration);
					auraIndex = auraIndex + 1;
				else
					-- Need to re-scan this index since an aura was removed
					readNext = false;
				end
			end
		else
			addAura(auraIndex, bdType, texture, timeLeft, name, bdIndex, duration);
			auraIndex = auraIndex + 1;
		end
		if (readNext) then
			bdIndex = bdIndex + 1;
			name, rank, texture, count, debuffType, duration, expirationTime = func("player", bdIndex);
		end
	end
	return auraIndex, bdIndex;
end

local function scanAuras()
	local tblEntry;
	local auraIndex = 1;
	local buffIndex, debuffIndex;

	auraIndex, buffIndex = scanAuras2(auraIndex, auratypeBUFF);
	auraIndex, debuffIndex = scanAuras2(auraIndex, auratypeDEBUFF);
	while (auraIndex <= #auraTable) do
		tblEntry = auraTable[auraIndex];
		removeAura(auraIndex, tblEntry);
	end
end

local function getWeaponEnchantInfo(isMainHand)
	local slot = ( isMainHand and 16 ) or 17;
	
	TOOLTIP:ClearLines();
	TOOLTIP:SetInventoryItem("player", slot);
	local text, name;
	for i=1, TOOLTIP:NumLines(), 1 do
		text = _G["CT_BuffModTooltipTextLeft"..i]:GetText();
		name = strmatch(text, "^.+ %(%d+%s+.+%)$");
		if ( name ) then
			break;
		end
	end
	
	return name or "Weapon Buff", GetInventoryItemTexture("player", slot), slot;
end

local function addWeaponEnchant(isMainHand, expiration, charges)
	local tblEntry = module:getBuffObject();
	
	local slot;
	tblEntry.name, tblEntry.texture, slot = getWeaponEnchantInfo(isMainHand);
	tblEntry.timeleft = expiration/1000;
	tblEntry.type = "ITEM";
	tblEntry.itemEnchantId = isMainHand and 1 or 2;
	tblEntry.duration = tblEntry.timeleft;
	tblEntry:add(slot, auratypeITEM, 0, charges, tblEntry.timeleft);
	
	return tblEntry;
end

local function updateWeaponEnchantments(difference)
	local hasMainHandEnchant, mainHandExpiration, mainHandCharges, hasOffHandEnchant, offHandExpiration, offHandCharges = GetWeaponEnchantInfo();
	local oldTime;
	
	-- Main Hand
	if ( hasMainHandEnchant and showItemBuffs ) then

		if ( not mainHandEnchant ) then
			mainHandEnchant = addWeaponEnchant(true, mainHandExpiration, mainHandCharges);
		end

		mainHandExpiration = ( mainHandExpiration or 0 )/1000;
		oldTime = mainHandEnchant.timeleft;
		mainHandEnchant.timeleft = mainHandExpiration;
		if ( abs(mainHandExpiration-oldTime) > difference ) then
			-- Weapon Enchantment was renewed
			mainHandEnchant.name, mainHandEnchant.texture = getWeaponEnchantInfo(true);
			mainHandEnchant:renew(mainHandExpiration, mainHandCharges, mainHandExpiration);
		else
			mainHandEnchant:updateTimeDisplay(mainHandExpiration, mainHandCharges);
		end
	elseif ( mainHandEnchant ) then
		mainHandEnchant:drop();
		mainHandEnchant = nil;
	end

	-- Off Hand
	if ( hasOffHandEnchant and showItemBuffs ) then

		if ( not offHandEnchant ) then
			offHandEnchant = addWeaponEnchant(false, offHandExpiration, offHandCharges);
		end

		offHandExpiration = ( offHandExpiration or 0 )/1000;
		oldTime = offHandEnchant.timeleft;
		offHandEnchant.timeleft = offHandExpiration;
		if ( abs(offHandExpiration-oldTime) > difference ) then
			-- Weapon Enchantment was renewed
			offHandEnchant.name, offHandEnchant.texture = getWeaponEnchantInfo(false);
			offHandEnchant:renew(offHandExpiration, offHandCharges, offHandExpiration);
		else
			offHandEnchant:updateTimeDisplay(offHandExpiration, offHandCharges);
		end
	elseif ( offHandEnchant ) then
		offHandEnchant:drop();
		offHandEnchant = nil;
	end
end

local function updateAuraDurations(difference)
	difference = AURA_UPDATE_FREQUENCY - difference + DELTA_TIMELEFT_THRESHOLD;
	local newTime, duration;
	
	-- Auras
	local oldTime;
	for key, value in ipairs(auraTable) do
		newTime, duration = getAuraTimeLeft(value.bdType, value.bdIndex);
		oldTime = value.timeleft;
		value.timeleft = newTime;
		
		if ( newTime > oldTime+difference ) then
			-- Buff was renewed
			value:renew(newTime, nil, duration);
		else
			value:updateTimeDisplay(newTime);
		end
	end
	
	-- Weapon Enchantments
	updateWeaponEnchantments(difference);

end

local function checkAuras()
	local currentTableIndex = #auraTable;
	scanAuras(1, currentTableIndex + 1);
end

function module:showItemBuffs(show)
	showItemBuffs = show;
end

module:regEvent("UNIT_AURA", checkAuras);
module:regEvent("PLAYER_ENTERING_WORLD", checkAuras);
module:schedule(AURA_UPDATE_FREQUENCY, true, updateAuraDurations);
