local quiverSpeed = 1.13;
local aimedCastTime = 3500;
local multiCastTime = 500;

local bagSlots = {20, 21, 22, 23};
local updateRequired = false;
local previousId = {}

local tooltipFrame = CreateFrame("GameTooltip", "QuiverScanner", nil, "GameTooltipTemplate");
tooltipFrame:SetOwner(UIParent, "ANCHOR_NONE");

local pushbackEvents = {
	["SWING_DAMAGE"] = true,
	["ENVIRONMENTAL_DAMAGE"] = true,
	["RANGE_DAMAGE"] = true,
	["SPELL_DAMAGE"] = true
};

--SpellId 26635 Berserking
local function GetBerserkingHaste()
	return math.min((1.30 - (UnitHealth("player") / UnitHealthMax("player"))) / 3, 0.3) + 1;
end

local helpfulRangeModifiers = {
	[3045] = 1.4, -- rapid fire
	[6150] = 1.3, -- quick shots
	[28866] = 1.2, -- naxx trinket
};

local harmfulRangeModifiers = {
	[89] = 1.45, -- cripple
	[19365] = 2.0, -- MC core hound debuff
	[17331] = 1.1, -- LBRS dagger proc
};

local sAimedShot = GetSpellInfo(19434);
local sMultiShot = GetSpellInfo(2643);

local castBar = CastingBarFrame;
local spellPushback = 1;
local castTime = 0;
local icon = 0;

local startTime = 0
local endTime = 0;

local debugA = false;
local dStart = 0;
local dPred = 0;

--could use GetRangedHaste() but it updates too slowly
local function GetCurrentRangeHaste()
	if(debugA) then
		print(quiverSpeed);
	end
	
	local speed = quiverSpeed;
	
	local stop = 0;
	for i = 1, 40 do
		if(stop ~= 1) then
			local spellId = select(10, UnitAura("player", i, "HELPFUL"))
			if(spellId == nil) then
				stop = stop + 1;
			else
				local coef = spellId == 26635 and GetBerserkingHaste() or helpfulRangeModifiers[spellId];
				if(coef) then
					speed = speed * coef;
				end
			end
		end
		
		if(stop < 2) then
			local spellId = select(10, UnitAura("player", i, "HARMFUL"))
			if(spellId == nil) then
				stop = stop + 2;
			else
				local div = harmfulRangeModifiers[spellId]
				if(div) then
					speed = speed / div;
				end
			end
		end
	
		if(stop == 3) then
			break;
		end
	end
	
	return speed;
end

local function GetQuiverInfo()
	local quiver = 0;

	for _, value in ipairs(bagSlots) do
		local bagLink = GetInventoryItemLink("player", value)
		
		if(bagLink ~= nil) then
			tooltipFrame:ClearLines()
			tooltipFrame:SetHyperlink(bagLink);

			for i = 3, tooltipFrame:NumLines() do
				local text = _G["QuiverScannerTextLeft" .. i]:GetText()
				
				if text and text ~= "" and string.find(text, ".(%d+)%%%.$") then
					local value = tonumber(strmatch(text, ".(%d+)%%%.$"));
					
					if(value > quiver) then
						quiver = value;
					end
				end
			end
		end
	end
	
	if(quiver ~= 0) then
		quiver = quiver / 100.0 + 1;
		
		quiverSpeed = quiver;
	else
		quiverSpeed = 1.0;
	end
	
	updateRequired = false;
end

local function StartCastingBar(spellName)
	local startColor = CastingBarFrame_GetEffectiveStartColor(castBar, false, false);
	castBar:SetStatusBarColor(startColor:GetRGB());
	if castBar.flashColorSameAsStart then
		castBar.Flash:SetVertexColor(startColor:GetRGB());
	else
		castBar.Flash:SetVertexColor(1, 1, 1);
	end
	
	if ( castBar.Spark ) then
		castBar.Spark:Show();
	end

	castBar.value = (GetTime() - (startTime / 1000));
	castBar.maxValue = (endTime - startTime) / 1000;
	castBar:SetMinMaxValues(0, castBar.maxValue);
	castBar:SetValue(castBar.value);

	if ( castBar.Text ) then
		castBar.Text:SetText(spellName);
	end
	
	if ( castBar.Icon ) then
		castBar.Icon:SetTexture(icon);
		if ( castBar.iconWhenNoninterruptible ) then
			castBar.Icon:SetShown(true);
		end
	end
	
	CastingBarFrame_ApplyAlpha(castBar, 1.0);
	castBar.holdTime = 0;
	castBar.casting = true;
	castBar.castID = nil;
	castBar.channeling = nil;
	castBar.fadeOut = nil;
	
	castBar:Show();
end

local function FinishCastingBar()
	castBar.maxValue = castBar.value;
	castBar:SetMinMaxValues(0, castBar.maxValue);
	
	if(debugA) then
		local real = GetTime() - dStart;
		local predicted = (dPred - dStart) / 1000.0;
		local difference = math.abs(real - predicted);
					
		if(difference > 0.1) then
			print(format("|cffff0000Cast took %.2f seconds to cast. Predicted %.2f seconds. Difference: %.2f|r", real, predicted, difference));
		else
			--print(format("Cast took %.2f seconds to cast. Predicted %.2f seconds. Difference: %.2f", real, predicted, difference));
		end
	end
end

local function InterruptCastingBar()
	castBar:SetValue(castBar.maxValue);
	castBar:SetStatusBarColor(castBar.failedCastColor:GetRGB());

	if(castBar.Spark) then
		castBar.Spark:Hide();
	end
	
	if(castBar.Text) then
		castBar.Text:SetText(FAILED);
	end
	
	castBar.casting = nil;
	castBar.channeling = nil;
	castBar.fadeOut = true;
	castBar.holdTime = GetTime() + CASTING_BAR_HOLD_TIME;
end

local function PushbackCastingBar()
	local pushAmount = math.min(castBar:GetValue(), spellPushback) * 1000.0;
	startTime = startTime + pushAmount;
	endTime = endTime + pushAmount;
	
	dPred = dPred + pushAmount;
	
	castBar.value = (GetTime() - (startTime / 1000));
	castBar.maxValue = (endTime - startTime) / 1000;
	castBar:SetMinMaxValues(0, castBar.maxValue);
	
	spellPushback = math.max(spellPushback - 0.2, 0.2);
end

local function CombatLogEvent(...)
	local timeStamp, subEvent, _, sourceID, _, _, _, targetID = ...;
	
	--for spell pushback
	if(pushbackEvents[subEvent]) then
		if(targetID ~= UnitGUID("player")) then return end

		if(castBar.casting and castBar.Text:GetText() == sAimedShot) then
			PushbackCastingBar();
		end
	
	elseif(subEvent == "SPELL_CAST_START") then
		if(sourceID ~= UnitGUID("player")) then return end

		local spellName = select(13, ...);
		
		if(spellName == sAimedShot) then
			icon = 135130;	
			castTime = aimedCastTime;
		elseif(spellName == sMultiShot) then
			icon = 132330;
			castTime = multiCastTime;
		else
			return;
		end
		
		spellPushback = 1;
		
		if(updateRequired) then
			GetQuiverInfo()
		end
		
		castTime = castTime / GetCurrentRangeHaste();
		
		startTime = GetTime() * 1000;
		endTime = startTime + castTime;
		
		dStart = GetTime();
		dPred = dStart + castTime;
		
		StartCastingBar(spellName);
	elseif(subEvent == "SPELL_CAST_SUCCESS") then
		if(sourceID ~= UnitGUID("player")) then return end
		
		local spellName = select(13, ...);
		
		if(spellName == sAimedShot or spellName == sMultiShot) then
			FinishCastingBar();
		end
	end
end

local function SpellInterrupted(source, castGUID, spellID)
	if(source ~= "player") then return end
	
	local spellName = GetSpellInfo(spellID);
	
	if(spellName == sAimedShot or spellName == sMultiShot) then
		InterruptCastingBar();
	end
end

local eventFrame = CreateFrame("Frame");
eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED");
eventFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED");
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
eventFrame:SetScript("OnEvent", function(self, event, ...)
	if(event == "COMBAT_LOG_EVENT_UNFILTERED" ) then
		CombatLogEvent(CombatLogGetCurrentEventInfo());
	elseif(event == "UNIT_SPELLCAST_INTERRUPTED") then
		SpellInterrupted(...);
	elseif(event == "PLAYER_ENTERING_WORLD") then
		GetQuiverInfo();
		updateRequired = true;
	end
end)

hooksecurefunc("PaperDollItemSlotButton_Update", function(self)
	local id = self:GetID();
	if(id < 20 or id > 23) then return end
	
	local texId = self.icon:GetTexture();
	
	if(previousId[id] == texId) then return end
	
	updateRequired = true;
	previousId[id] = texId;
end)