local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB
local NP = E:GetModule('NamePlates')
local oUF = E.oUF

local _G = _G
local pairs, ipairs, ceil, floor, tonumber = pairs, ipairs, ceil, floor, tonumber
local strmatch, strlower, strfind = strmatch, strlower, strfind

local GetLocale = GetLocale
local IsInInstance = IsInInstance
local UnitIsPlayer = UnitIsPlayer
local GetQuestLogSpecialItemInfo = GetQuestLogSpecialItemInfo
local C_QuestLog_GetTitleForQuestID = C_QuestLog.GetTitleForQuestID
local C_QuestLog_GetLogIndexForQuestID = C_QuestLog.GetLogIndexForQuestID
local C_QuestLog_GetTitleForLogIndex = C_QuestLog.GetTitleForLogIndex
local C_QuestLog_GetNumQuestLogEntries = C_QuestLog.GetNumQuestLogEntries
local C_QuestLog_GetQuestIDForLogIndex = C_QuestLog.GetQuestIDForLogIndex
local ThreatTooltip = THREAT_TOOLTIP:gsub('%%d', '%%d-')

local questIcons = {
	iconTypes = { 'Default', 'Item', 'Skull', 'Chat' },
	indexByID = {}, --[questID] = questIndex
	activeQuests = {} --[questTitle] = questID
}

NP.QuestIcons = questIcons

local typesLocalized = {
	enUS = {
		--- short matching applies here so,
		-- kill: killed, destory: destoryed, etc ...
		KILL = {'slain', 'destroy', 'eliminate', 'repel', 'kill', 'defeat'},
		CHAT = {'speak', 'talk'}
	},
	zhCN = {
		KILL = {'消灭', '摧毁', '击败', '毁灭', '击退'},
		CHAT = {'交谈', '谈一谈'}
	},
	zhTW = {
		KILL = {'毀滅', '擊退', '殺死'},
		CHAT = {'交談', '說話'}
	},
}

local questTypes = typesLocalized[GetLocale()] or typesLocalized.enUS

local function CheckTextForQuest(text)
	local x, y = strmatch(text, '(%d+)/(%d+)')
	if x and y then
		return floor(y - x)
	elseif not strmatch(text, ThreatTooltip) then
		local progress = tonumber(strmatch(text, '([%d%.]+)%%'))
		if progress and progress <= 100 then
			return ceil(100 - progress), true
		end
	end
end
NP.QuestIcons.CheckTextForQuest = CheckTextForQuest

local function GetQuests(unitID)
	if IsInInstance() then return end

	E.ScanTooltip:SetOwner(_G.UIParent, 'ANCHOR_NONE')
	E.ScanTooltip:SetUnit(unitID)
	E.ScanTooltip:Show()

	local QuestList, notMyQuest, activeID
	for i = 3, E.ScanTooltip:NumLines() do
		local str = _G['ElvUI_ScanTooltipTextLeft' .. i]
		local text = str and str:GetText()
		if not text or text == '' then return end

		if UnitIsPlayer(text) then
			notMyQuest = text ~= E.myname
		elseif text and not notMyQuest then
			local count, percent = CheckTextForQuest(text)

			-- this line comes from one line up in the tooltip
			local activeQuest = NP.QuestIcons.activeQuests[text]
			if activeQuest then activeID = activeQuest end

			if count then
				local type, index, texture, _
				if activeID then
					index = questIcons.indexByID[activeID]
					_, texture = GetQuestLogSpecialItemInfo(index)
				end

				if texture then
					type = 'QUEST_ITEM'
				else
					local lowerText = strlower(text)

					-- check chat type first
					for _, listText in ipairs(questTypes.CHAT) do
						if strfind(lowerText, listText, nil, true) then
							type = 'CHAT'
							break
						end
					end

					-- check kill type if chat type doesn't exist
					if not type then
						for _, listText in ipairs(questTypes.KILL) do
							if strfind(lowerText, listText, nil, true) then
								type = 'KILL'
								break
							end
						end
					end
				end

				if not QuestList then QuestList = {} end
				QuestList[#QuestList + 1] = {
					isPercent = percent,
					itemTexture = texture,
					objectiveCount = count,
					questType = type or 'DEFAULT',
					-- below keys are currently unused
					questLogIndex = index,
					questID = activeID
				}
			end
		end
	end

	E.ScanTooltip:Hide()
	return QuestList
end

local function hideIcons(element)
	for _, object in pairs(questIcons.iconTypes) do
		local icon = element[object]
		icon:Hide()

		if icon.Text then
			icon.Text:SetText('')
		end
	end
end

local function Update(self, event, arg1)
	local element = self.QuestIcons
	if not element then return end

	local unit = (event == 'UNIT_NAME_UPDATE' and arg1) or self.unit
	if unit ~= self.unit then return end

	if element.PreUpdate then
		element:PreUpdate()
	end

	hideIcons(element)

	local QuestList = GetQuests(unit)
	if QuestList then
		element:Show()
	else
		element:Hide()
		return
	end

	local shownCount
	for i = 1, #QuestList do
		local quest = QuestList[i]
		local objectiveCount = quest.objectiveCount
		local questType = quest.questType
		local isPercent = quest.isPercent

		if isPercent or objectiveCount > 0 then
			local icon
			if questType == 'DEFAULT' then
				icon = element.Default
			elseif questType == 'KILL' then
				icon = element.Skull
			elseif questType == 'CHAT' then
				icon = element.Chat
			elseif questType == 'QUEST_ITEM' then
				icon = element.Item
			end

			if icon and not icon:IsShown() then
				shownCount = (shownCount and shownCount + 1) or 0

				local size = icon.size or 25
				local setPosition = icon.position or 'TOPLEFT'
				local newPosition = E.InversePoints[setPosition]
				local offset = shownCount * (5 + size)

				icon:Show()
				icon:ClearAllPoints()
				icon:Point(newPosition, element, newPosition, (strmatch(setPosition, 'LEFT') and -offset) or offset, 0)

				if questType ~= 'CHAT' and icon.Text and (isPercent or objectiveCount > 1) then
					icon.Text:SetText((isPercent and objectiveCount..'%') or objectiveCount)
				end

				if questType == 'QUEST_ITEM' then
					element.Item:SetTexture(quest.itemTexture)
				end
			end
		end
	end

	if element.PostUpdate then
		return element:PostUpdate()
	end
end

local function Path(self, ...)
	return (self.QuestIcons.Override or Update) (self, ...)
end

local function ForceUpdate(element)
	return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end

local function Enable(self)
	local element = self.QuestIcons
	if element then
		element.__owner = self
		element.ForceUpdate = ForceUpdate

		if element.Default:IsObjectType('Texture') and not element.Default:GetAtlas() then
			element.Default:SetAtlas('SmallQuestBang')
		end
		if element.Skull:IsObjectType('Texture') and not element.Skull:GetTexture() then
			element.Skull:SetTexture(E.Media.Textures.SkullIcon)
		end
		if element.Chat:IsObjectType('StatusBar') and not element.Chat:GetTexture() then
			element.Chat:SetTexture([[Interface\WorldMap\ChatBubble_64.PNG]])
		end

		self:RegisterEvent('QUEST_LOG_UPDATE', Path, true)
		self:RegisterEvent('UNIT_NAME_UPDATE', Path, true)
		self:RegisterEvent('PLAYER_ENTERING_WORLD', Path, true)

		return true
	end
end

local function Disable(self)
	local element = self.QuestIcons
	if element then
		element:Hide()
		hideIcons(element)

		self:UnregisterEvent('QUEST_LOG_UPDATE', Path)
		self:UnregisterEvent('UNIT_NAME_UPDATE', Path)
		self:UnregisterEvent('PLAYER_ENTERING_WORLD', Path)
	end
end

local frame = CreateFrame('Frame')
frame:RegisterEvent('QUEST_ACCEPTED')
frame:RegisterEvent('QUEST_REMOVED')
frame:RegisterEvent('PLAYER_ENTERING_WORLD')
frame:SetScript('OnEvent', function(self, event, questID)
	if event == 'PLAYER_ENTERING_WORLD' then
		for i = 1, C_QuestLog_GetNumQuestLogEntries() do
			local id = C_QuestLog_GetQuestIDForLogIndex(i)
			if id and id > 0 then
				questIcons.indexByID[id] = i

				local title = C_QuestLog_GetTitleForLogIndex(i)
				if title then questIcons.activeQuests[title] = id end
			end
		end

		self:UnregisterEvent(event)
	elseif event == 'QUEST_ACCEPTED' then
		if questID and questID > 0 then
			local index = C_QuestLog_GetLogIndexForQuestID(questID)
			if index and index > 0 then
				questIcons.indexByID[questID] = index

				local title = C_QuestLog_GetTitleForQuestID(questID)
				if title then questIcons.activeQuests[title] = questID end
			end
		end
	elseif event == 'QUEST_REMOVED' then
		if not questID then return end
		questIcons.indexByID[questID] = nil

		for title, id in pairs(questIcons.activeQuests) do
			if id == questID then
				questIcons.activeQuests[title] = nil
				break
			end
		end
	end
end)

oUF:AddElement('QuestIcons', Path, Enable, Disable)
