------------------------------------------------
--                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 function getAuraName(auraIndex)
	return GetPlayerBuffName(auraIndex);
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 addAura(auraIndex, texture, timeLeft)
	local newBuffId = maxBuffId + 1;
	local tblEntry = module:getBuffObject();
	
	timeLeft = timeLeft or GetPlayerBuffTimeLeft(auraIndex);
	tblEntry.name = getAuraName(auraIndex);
	tblEntry.texture = texture or GetPlayerBuffTexture(auraIndex);
	tblEntry.timeleft = timeLeft;
	
	if ( GetPlayerBuff(newBuffId, BUFF) == auraIndex ) then
		if ( timeLeft == 0 ) then
			tblEntry.type = "AURA";
		else
			tblEntry.type = "BUFF";
		end
		maxBuffId = newBuffId;
	else
		tblEntry.type = "DEBUFF";
		maxDebuffId = maxDebuffId + 1
	end
	
	tinsert(auraTable, tblEntry);
	tblEntry:add(auraIndex);
end

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

local function compareAura(auraIndex)
	local tblEntry = auraTable[auraIndex];
	
	if ( not tblEntry ) then
		return true;
	end
	
	local newTexture = GetPlayerBuffTexture(auraIndex);
	return ( tblEntry.texture == newTexture and 
		( isAuraInThreshold(auraIndex, tblEntry) or
			tblEntry.name == getAuraName(auraIndex) ) ),
		tblEntry, newTexture;
end

local function scanRemovedAuras(auraIndex, maxIndex)
	local isIdentical, tblEntry, texture, timeLeft;
	while ( auraIndex <= maxIndex ) do
		isIdentical, tblEntry, texture, timeLeft = compareAura(auraIndex);

		if ( not isIdentical ) then
			removeAura(auraIndex, tblEntry);

			if ( texture and auraIndex == maxIndex ) then
				-- The aura was replaced with something else, add the new one
				addAura(auraIndex, texture, timeLeft);
			else
				-- Need to re-scan this index since an aura was removed
				-- Small hack!
				auraIndex = auraIndex - 1;
			end
		end
		
		auraIndex = auraIndex + 1;
	end
end

local function scanAddedAuras(auraIndex)
	local texture = GetPlayerBuffTexture(auraIndex);
	if ( texture ) then
		addAura(auraIndex, texture)
		return scanAddedAuras(auraIndex + 1);
	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:add(slot, charges);
	
	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);
		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);
		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;
	
	-- Auras
	local oldTime;
	for key, value in ipairs(auraTable) do
		newTime = GetPlayerBuffTimeLeft(key);
		oldTime = value.timeleft;
		value.timeleft = newTime;
		
		if ( newTime > oldTime+difference ) then
			-- Buff was renewed
			value:renew(newTime);
		else
			value:updateTimeDisplay(newTime);
		end
	end
	
	-- Weapon Enchantments
	updateWeaponEnchantments(difference);

end

local function checkAuras()
	local currentTableIndex = #auraTable;
	if ( currentTableIndex > 0 ) then
		scanRemovedAuras(1, currentTableIndex + 1);
		currentTableIndex = #auraTable;
	end
	scanAddedAuras(currentTableIndex + 1);
end

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

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