DeranjataXPBarDB = {
	tracked = true,
	remoteZones = true,
	incompleted = false,
	popup = true,
	sound = true,
	color = {0, 1, 0, .5},
}

local addonName, ns = ...
ns = CreateFrame("Frame")
ns:SetScript("OnEvent", function(ns, event, ...) ns[event](ns, event, ...) end)
ns:RegisterEvent("ADDON_LOADED")
ns.bars = {}
ns.zone = {}

function ns.ADDON_LOADED(ns, event, name)
	if name == addonName then
		ns:UnregisterEvent(event)
		ns[event] = nil

		ns:HookBars({
			BEBBackground, -- BEB
			Dominos and Dominos.Frame and Dominos.Frame.Get and Dominos.Frame:Get("xp"), -- Dominos
			MainMenuExpBar, -- default UI expbar
			saftExperienceBar, -- saftXP_Backdrop
			SUI_ExperienceBar, -- SpartanUI
			XparkyXPAnchor, -- Xparky
			XPBarNoneBackground, -- XPBarNone
		})

		ns:RegisterEvent("PLAYER_LOGIN")
		ns:PLAYER_LOGIN()

		ns.PLAYER_XP_UPDATE = ns.PLAYER_LEVEL_UP
		ns.QUEST_LOG_UPDATE = ns.PLAYER_LEVEL_UP
		ns.UNIT_ENTERED_VEHICLE = ns.PLAYER_LEVEL_UP
		ns.UNIT_EXITED_VEHICLE = ns.PLAYER_LEVEL_UP
		ns.UNIT_PORTRAIT_UPDATE = ns.PLAYER_LEVEL_UP
		ns.ZONE_CHANGED_NEW_AREA = ns.PLAYER_LEVEL_UP

		ns:RegisterEvent("PLAYER_LEVEL_UP")
		ns:RegisterEvent("PLAYER_XP_UPDATE")
		ns:RegisterEvent("QUEST_LOG_UPDATE")
		ns:RegisterEvent("UNIT_ENTERED_VEHICLE")
		ns:RegisterEvent("UNIT_EXITED_VEHICLE")
		ns:RegisterEvent("UNIT_PORTRAIT_UPDATE")
		ns:RegisterEvent("ZONE_CHANGED_NEW_AREA")

		hooksecurefunc("AddQuestWatch", ns.PLAYER_LEVEL_UP)
		hooksecurefunc("RemoveQuestWatch", ns.PLAYER_LEVEL_UP)

		hooksecurefunc("ExhaustionToolTipText", ns.UpdateTooltip)
		ExhaustionTick:HookScript("OnEnter", ns.UpdateTooltip)
	end
end

function ns.PLAYER_LOGIN(ns)
	ns.currentLevel = UnitLevel("player")
end

function ns.PLAYER_LEVEL_UP(_, event)
	for parent, bar in pairs(ns.bars) do
		bar:UpdateBar()
	end

	if event == "PLAYER_LEVEL_UP" and ns.currentLevel ~= UnitLevel("player") then
		ns.hasPopped, ns.hasAlerted = nil
		ns:PLAYER_LOGIN()
	end
end

function ns.HookBars(ns, bars)
	for _, parent in pairs(bars) do
		if parent and type(parent) == "table" and type(parent.GetObjectType) == "function" and parent:GetObjectType() == "StatusBar" and not ns.bars[parent] then

			local parentTexture = parent:GetStatusBarTexture()
			parentTexture = type(parentTexture) == "table" and type(parentTexture.GetTexture) == "function" and parentTexture:GetTexture() or "Interface\\TargetingFrame\\UI-StatusBar"

			local bar = CreateFrame("StatusBar", nil, parent)
			bar:SetStatusBarTexture(parentTexture)
			bar.UpdateBar = ns.UpdateBar

			ns.bars[parent] = bar
		end
	end
end

function ns.UpdateBar(bar)
	local parent = bar:GetParent()
	local parentMinValue, parentMaxValue = parent:GetMinMaxValues()
	local parentValue = parent:GetValue()
	local parentR, parentG, parentB = parent:GetStatusBarColor()
	local experience, canLevel = ns:CalculateExperience()

	bar:SetMinMaxValues(parentMinValue, parentMaxValue)
	bar:SetValue(parentValue + experience)

	bar:SetFrameLevel(parent:GetFrameLevel() - 1)
	bar:SetFrameStrata(parent:GetFrameStrata())
	bar:SetHeight(parent:GetHeight())
	bar:SetPoint("CENTER", parent, "CENTER", 0, 0)
	bar:SetWidth(parent:GetWidth())

	if type(DeranjataXPBarDB.color) == "table" then
		local configR, configG, configB, configA = unpack(DeranjataXPBarDB.color)

		if configR and configG and configB and configA then
			parentR, parentG, parentB, parentA = configR, configG, configB, configA
		end
	end

	if parentR and parentG and parentB then
		bar:SetStatusBarColor(parentR, parentG, parentB, parentA or .5)
	else
		bar:SetStatusBarColor(0, 1, 0, .5)
	end

	if canLevel then
		if DeranjataXPBarDB.popup and not ns.hasPopped then
			ns.hasPopped = 1

			ns:ShowPopup()
		end

		if DeranjataXPBarDB.sound and not ns.hasAlerted then
			ns.hasAlerted = 1

			ns:PlayAlert()
		end
	end
end

function ns.CalculateExperience(ns)
	local experience = 0

	local playerMoney = GetMoney()
	local backupIndex = GetQuestLogSelection()
	local index, header = 1
	ns:UpdateZoneQuests()

	while GetQuestLogTitle(index) do
		SelectQuestLogEntry(index)

		local title, level, questTag, suggestedGroup, isHeader, isCollapsed, isCompleted, isDaily, questID, startEvent, displayQuestID = GetQuestLogTitle(index)
		local isTracked, isRemote = IsQuestWatched(index), not ns.zone[questID]
		isCompleted = (isCompleted and isCompleted > 0) or (GetNumQuestLeaderBoards(index) == 0 and playerMoney >= GetQuestLogRequiredMoney(index) and not startEvent)

		if isHeader then
			header = title
		elseif (not DeranjataXPBarDB.tracked or isTracked) and (DeranjataXPBarDB.remoteZones or not isRemote) and (DeranjataXPBarDB.incompleted or isCompleted) then
			experience = experience + (GetQuestLogRewardXP() or 0)
		end

		index = index + 1
	end
	SelectQuestLogEntry(backupIndex)

	return experience, experience >= UnitXPMax("player") - UnitXP("player"), experience / (UnitXPMax("player") - UnitXP("player"))
end

function ns.UpdateZoneQuests(ns)
	table.wipe(ns.zone)

	for i = 1, QuestMapUpdateAllQuests(), 1 do
		ns.zone[QuestPOIGetQuestIDByVisibleIndex(i)] = 1
	end
end

function ns.ShowPopup(ns)
	local f, s = LevelUpDisplay
	s = _G[f:GetName() .. "Side"]
	f.level = 0
	f.type = LEVEL_UP_TYPE_CHARACTER
	LevelUpDisplay_Show(f)
	s:Hide()
	LevelUpDisplay_BuildCharacterList(f)
	table.wipe(f.unlockList)
	f.currSpell = 1024
	f.levelFrame.reachedText:SetText("Very Important Update")
	f.levelFrame.levelText:SetText("You Will Ding Level " .. (UnitLevel("player") + 1) .. " If You Turn In Your Quests!")
end

function ns.PlayAlert(ns)
	local _, race = UnitRace("player")
	local gender = UnitSex("player") == 3 and "Female" or "Male"
	local index = math.random(1, 2)

	PlaySoundFile("Sound\\Interface\\LevelUp.ogg")
	PlaySoundFile("Sound\\Character\\" .. race .. "\\" .. race .. "Vocal" .. gender .. "\\" .. race .. gender .. "Cheer0" .. index .. ".ogg")
end

function ns.UpdateTooltip()
	local experience, canLevel, percent = ns:CalculateExperience()

	local DEFAULT_NUM_BARS = 20
	local currentPercent = math.ceil((UnitXP("player") / UnitXPMax("player")) * 100)
	local requiredBarsPercent = math.ceil(((UnitXPMax("player") - UnitXP("player")) * (DEFAULT_NUM_BARS / UnitXPMax("player"))) * 10) / 10

	local text = "|cffFFD200" .. addonName .. "|r"
	text = text .. "\n|cffFFFFFFYou are at " .. currentPercent .. "% XP. You require " .. (100 - currentPercent) .. "% XP to level " .. (UnitLevel("player") + 1) .. "."
	text = text .. "\n(In other terms you need to fill up " .. requiredBarsPercent .. " bars of XP.)"

	if experience > 0 then
		text = text .. "\nYour quest log is worth " .. experience .. " XP."
		if percent <= 1 then
			text = text .. " (" .. math.ceil(percent * 100) .. "% of remaining XP.)"
		end
	end

	if canLevel then
		text = text .. "|r\n\n|cff00FF00You will level up if you deliver your " .. (DeranjataXPBarDB.incompleted and "current " or "completed ") .. "quests!"
	end

	GameTooltip:AddLine("\n" .. text)
	GameTooltip:Show()
end
