-----------------------------------------------------------------------
-- AddOn namespace.
-----------------------------------------------------------------------
local LibStub = _G.LibStub
local ADDON_NAME, private = ...

-- Locales
local AL = LibStub("AceLocale-3.0"):GetLocale("RareScanner");

-- Minimap pins
local HBD_Pins = LibStub("HereBeDragons-Pins-2.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")

-- Minimap icon
local ldi = LibStub("LibDBIcon-1.0")

local RSMinimap = private.NewLib("RareScannerMinimap")

-- RareScanner database libraries
local RSGeneralDB = private.ImportLib("RareScannerGeneralDB")
local RSConfigDB = private.ImportLib("RareScannerConfigDB")
local RSNpcDB = private.ImportLib("RareScannerNpcDB")
local RSContainerDB = private.ImportLib("RareScannerContainerDB")
local RSEventDB = private.ImportLib("RareScannerEventDB")
local RSGuideDB = private.ImportLib("RareScannerGuideDB")

-- RareScanner services
local RSMap = private.ImportLib("RareScannerMap")
local RSNpcPOI = private.ImportLib("RareScannerNpcPOI")
local RSContainerPOI = private.ImportLib("RareScannerContainerPOI")
local RSEventPOI = private.ImportLib("RareScannerEventPOI")
local RSGuidePOI = private.ImportLib("RareScannerGuidePOI")

-- RareScanner general libraries
local RSConstants = private.ImportLib("RareScannerConstants")
local RSLogger = private.ImportLib("RareScannerLogger")
local RSUtils = private.ImportLib("RareScannerUtils")

---============================================================================
-- Minimap button
---============================================================================

local MINIMAP_BUTTON_NAME = "RareScannerMinimapIcon"
local ENTITY_FRAME_LEVEL = 1002
local OVERLAY_FRAME_LEVEL = 1003
local GUIDE_FRAME_LEVEL = 1004

function RSMinimap.LoadMinimapButton()
	local RareScannerMinimapLDB = LibStub("LibDataBroker-1.1"):NewDataObject("RareScannerLDB", {
		type = "data source",
		text = "RareScanner",
		label = "RareScanner",
		icon = RSConstants.NORMAL_NPC_TEXTURE,
		OnClick = function(self, button) 
			if (not InCombatLockdown()) then
				if (button == "LeftButton") then
					RSExplorerFrame:Show()
				elseif (button == "RightButton") then
					Settings.OpenToCategory(AceConfigDialog.BlizOptionsIDMap["RareScanner"])
				end
			end
		end,
		OnTooltipShow = function(tooltip)
			tooltip:SetText("RareScanner")
			tooltip:AddLine(AL["MINIMAP_ICON_TOOLTIP1"], 1, 1, 1)
			tooltip:AddLine(AL["MINIMAP_ICON_TOOLTIP2"], 1, 1, 1)
		end
	})
	
	ldi:Register(MINIMAP_BUTTON_NAME, RareScannerMinimapLDB, RSConfigDB.GetMMinimapButtonDB()) 
end

function RSMinimap.ToggleMinimapButton() 
	if (RSConfigDB.IsShowingMinimapButton()) then 
		ldi:Show(MINIMAP_BUTTON_NAME) 
	else 
		ldi:Hide(MINIMAP_BUTTON_NAME) 
	end 
end

---============================================================================
-- All layerers
---============================================================================

local previousMapID
local pinFramesPool
local overlayFramesPool
local guideFramesPool

function RSMinimap.RemoveAllData()
	HBD_Pins:RemoveAllMinimapIcons(RSMinimap)
	
	if (pinFramesPool) then
		pinFramesPool:ReleaseAll()
	end
	
	if (overlayFramesPool) then
		overlayFramesPool:ReleaseAll()
	end
	
	if (guideFramesPool) then
		guideFramesPool:ReleaseAll()
	end
end

---============================================================================
-- Entities layer
---============================================================================

local function AddMinimapPin(POI)
	if (not POI) then
		return
	end
	
	local pin = pinFramesPool:Acquire()
	pin.POI = POI
		
	-- Ignore POIs from worldmap
	if (not POI.worldmap) then
		pin.Texture:SetTexture(POI.Texture)
		pin.Texture:SetScale(RSConfigDB.GetIconsMinimapScale())
		pin:SetFrameLevel(ENTITY_FRAME_LEVEL)
		pin.IconTexture:SetAtlas(POI.iconAtlas)
		if (type(POI.mapID) == "number" and type(RSUtils.FixCoord(POI.x)) == "number" and type(RSUtils.FixCoord(POI.y)) == "number") then
			HBD_Pins:AddMinimapIconMap(RSMinimap, pin, POI.mapID, RSUtils.FixCoord(POI.x), RSUtils.FixCoord(POI.y), false, false)
		end
	end
end

function RSMinimap.RefreshAllData(forzed)
	-- Ignore if minimap not available
	if (not Minimap:IsVisible()) then
		RSMinimap.RemoveAllData()
		return
	end
	
	-- Ignore if not showing icons in the minimap
	if (not RSConfigDB:IsShowingMinimapIcons()) then
		RSMinimap.RemoveAllData()
		return
	end

	-- Gets MAPID from players position
	local mapID = C_Map.GetBestMapForUnit("player")
	if (not mapID or mapID == 0) then
		return
	end

	-- If same zone ignore it
	if (not forzed and previousMapID and previousMapID == mapID) then
		return
	end

	-- Initialize pool
	if (not pinFramesPool) then
		pinFramesPool = CreateFramePool("FRAME", Minimap, "RSMinimapPinTemplate");
	end

	-- Release current pins
	RSMinimap.RemoveAllData()

	-- Otherwise refresh data
	previousMapID = mapID

	-- Loads new pins
	local POIs = RSMap.GetMapPOIs(mapID, false, true)
	if (not POIs) then
		return
	end

	for _, POI in ipairs (POIs) do
		AddMinimapPin(POI)
	end
	
	-- Adds overlay if active
	for entityID, _ in pairs (RSGeneralDB.GetAllOverlayActive()) do
		RSMinimap.AddOverlay(entityID)
	end
	
	-- Adds guide if active
	if (RSGeneralDB.GetGuideActive()) then
		RSMinimap.AddGuide(RSGeneralDB.GetGuideActive())
	end
end

function RSMinimap.RefreshEntityState(entityID)
	if (pinFramesPool) then
		local pinExists = false
		for pin in pinFramesPool:EnumerateActive() do
			local POI = pin.POI
			if (POI.entityID == entityID) then
				pinExists = true
				HBD_Pins:RemoveMinimapIcon(RSMinimap, pin)
				
				local isFiltered = false
				if (POI.isNpc) then
					-- If the entity spawns in multiple places use the POIs coordinates
					local alreadyFoundInfo = RSGeneralDB.GetAlreadyFoundEntity(entityID, RSConstants.NPC_VIGNETTE)
					if (alreadyFoundInfo and RSNpcDB.IsMultiZoneSpawn(entityID)) then
						alreadyFoundInfo.coordX = POI.x
						alreadyFoundInfo.coordY = POI.y
					end
					
					POI = RSNpcPOI.GetNpcPOI(entityID, POI.mapID, RSNpcDB.GetInternalNpcInfo(entityID), alreadyFoundInfo)
					if (POI.isDead and not RSConfigDB.IsShowingAlreadyKilledNpcs()) then
						isFiltered = true
					end
				elseif (POI.isContainer) then
					-- If the entity spawns in multiple places use the POIs coordinates
					local alreadyFoundInfo = RSGeneralDB.GetAlreadyFoundEntity(entityID, RSConstants.CONTAINER_VIGNETTE)
					if (alreadyFoundInfo and RSContainerDB.IsMultiZoneSpawn(entityID)) then
						alreadyFoundInfo.coordX = POI.x
						alreadyFoundInfo.coordY = POI.y
					end
					
					POI = RSContainerPOI.GetContainerPOI(entityID, POI.mapID, RSContainerDB.GetInternalContainerInfo(entityID), alreadyFoundInfo)
					if (POI.isOpened and not RSConfigDB.IsShowingAlreadyOpenedContainers()) then
						isFiltered = true
					end
				elseif (POI.isEvent) then
					local alreadyFoundInfo = RSGeneralDB.GetAlreadyFoundEntity(entityID, RSConstants.EVENT_VIGNETTE)
					POI = RSEventPOI.GetEventPOI(entityID, POI.mapID, RSEventDB.GetInternalEventInfo(entityID), alreadyFoundInfo)
					if (POI.isCompleted and not RSConfigDB.IsShowingCompletedEvents()) then
						isFiltered = true
					end
				end
				
				if (isFiltered) then
					RSLogger:PrintDebugMessage(string.format("RSMinimap.RefreshEntityState[RELEASED][%s,x=%s,y=%s]", entityID, POI.x, POI.y))
					pinFramesPool:Release(pin)
				else
					RSLogger:PrintDebugMessage(string.format("RSMinimap.RefreshEntityState[ADDED][%s,x=%s,y=%s]", entityID, POI.x, POI.y))
					pin.Texture:SetTexture(POI.Texture)
					pin.Texture:SetScale(RSConfigDB.GetIconsMinimapScale())
					pin.IconTexture:SetAtlas(POI.iconAtlas)
					pin:SetFrameLevel(ENTITY_FRAME_LEVEL)
					if (type(POI.mapID) == "number" and type(RSUtils.FixCoord(POI.x)) == "number" and type(RSUtils.FixCoord(POI.y)) == "number") then
						HBD_Pins:AddMinimapIconMap(RSMinimap, pin, POI.mapID, RSUtils.FixCoord(POI.x), RSUtils.FixCoord(POI.y), false, false)
					end
				end
				
				-- If the entity spawns in multiple places keep checking the rest of entities in the list
				if (not RSNpcDB.IsMultiZoneSpawn(entityID) and not RSContainerDB.IsMultiZoneSpawn(entityID)) then
					break
				end
			end
		end
		
		if (not pinExists) then
			-- Avoid adding the pin if it doesn't belong to the current player's map
			if (not previousMapID) then
				return
			else
				local mapID = C_Map.GetBestMapForUnit("player")
				if (not mapID or mapID ~= previousMapID) then
					return
				end
			end
	
			local POI
			if (RSNpcDB.GetInternalNpcInfoByMapID(entityID, previousMapID)) then
				POI = RSNpcPOI.GetNpcPOI(entityID, previousMapID, RSNpcDB.GetInternalNpcInfo(entityID), RSGeneralDB.GetAlreadyFoundEntity(entityID))
				if (POI.isDead and not RSConfigDB.IsShowingAlreadyKilledNpcs()) then
					return
				end
			elseif (RSContainerDB.GetInternalContainerInfoByMapID(entityID, previousMapID)) then
				POI = RSContainerPOI.GetContainerPOI(entityID, previousMapID, RSContainerDB.GetInternalContainerInfo(entityID), RSGeneralDB.GetAlreadyFoundEntity(entityID))
				if (POI.isOpened and not RSConfigDB.IsShowingAlreadyOpenedContainers()) then
					return
				end
			elseif (RSEventDB.GetInternalEventInfoByMapID(entityID, previousMapID)) then
				POI = RSEventPOI.GetEventPOI(entityID, previousMapID, RSEventDB.GetInternalEventInfo(entityID), RSGeneralDB.GetAlreadyFoundEntity(entityID))
				if (POI.isCompleted and not RSConfigDB.IsShowingCompletedEvents()) then
					return
				end
			end
			
			AddMinimapPin(POI)
		end
	end
end

function RSMinimap.HideIcon(entityID, x, y)
	if (pinFramesPool) then
		for pin in pinFramesPool:EnumerateActive() do
			local POI = pin.POI
			if (x ~= nil and y ~= nil) then
				if (POI.entityID == entityID and tostring(POI.x) == x and tostring(POI.y) == y) then
					HBD_Pins:RemoveMinimapIcon(RSMinimap, pin)
					pinFramesPool:Release(pin)
					break
				end
			elseif (POI.entityID == entityID) then
				HBD_Pins:RemoveMinimapIcon(RSMinimap, pin)
				pinFramesPool:Release(pin)
				break
			end
		end
	end
end

---============================================================================
-- Overlay layer
---============================================================================

function RSMinimap.AddOverlay(entityID)
	-- Gets MAPID from players position
	local mapID = C_Map.GetBestMapForUnit("player")
	if (not mapID or mapID == 0) then
		return
	end
	
	-- Check if the entity is actually active
	local overlayActive = RSGeneralDB.GetOverlayActive(entityID)
	if (not overlayActive) then
		return
	end
	
	-- Gets the information of the entity
	local isNpc = false
	local isContainer = false
	if (RSNpcDB.GetInternalNpcInfo(entityID)) then
		isNpc = true
	elseif (RSContainerDB.GetInternalContainerInfo(entityID)) then
		isContainer = true
	end
	
	-- Check if the entity belongs to that map
	if ((isNpc and not RSNpcDB.IsInternalNpcInMap(entityID, mapID)) or (isContainer and not RSContainerDB.IsInternalContainerInMap(entityID, mapID))) then
		return
	end
	
	-- Initializes the pool
	if (not overlayFramesPool) then
		overlayFramesPool = CreateFramePool("FRAME", Minimap, "RSMinimapPinTemplate");
	end

	local overlay = nil
	if (isNpc) then
		overlay = RSNpcDB.GetInternalNpcOverlay(entityID, mapID)
	elseif (isContainer) then
		overlay = RSContainerDB.GetInternalContainerOverlay(entityID, mapID)
	end

	if (overlay) then
		local r, g, b = RSConfigDB.GetWorldMapOverlayColour(overlayActive.colourID)		
		for _, coordinates in ipairs (overlay) do
			local x, y = strsplit("-", coordinates)
			local pin = overlayFramesPool:Acquire()
			pin.POI = {}
			pin.POI.entityID = entityID
			if (isNpc) then
				pin.POI.name = RSNpcDB.GetNpcName(entityID)
			elseif (isContainer) then
				pin.POI.name = RSContainerDB.GetContainerName(entityID)
			end
			
			pin.Texture:SetTexture(RSConstants.OVERLAY_SPOT_TEXTURE)
			pin.Texture:SetVertexColor(r, g, b, 0.7)
			pin.IconTexture:SetAtlas(pin.POI.iconAtlas)
			pin:SetFrameLevel(OVERLAY_FRAME_LEVEL)
			HBD_Pins:AddMinimapIconMap(RSMinimap, pin, mapID, RSUtils.FixCoord(x), RSUtils.FixCoord(y), false, false)
		end
	end
end

function RSMinimap.RemoveOverlay(entityID)
	if (overlayFramesPool) then
		for pin in overlayFramesPool:EnumerateActive() do
			if (pin.POI.entityID == entityID) then
				overlayFramesPool:Release(pin)
				HBD_Pins:RemoveMinimapIcon(RSMinimap, pin)
			end
		end
	end
end

---============================================================================
-- Guide layer
---============================================================================

function RSMinimap.AddGuide(entityID)
	-- Gets MAPID from players position
	local mapID = C_Map.GetBestMapForUnit("player")
	if (not mapID or mapID == 0) then
		return
	end
	
	-- Check if the entity is actually active
	if (not RSGeneralDB.GetGuideActive() or RSGeneralDB.GetGuideActive() ~= entityID) then
		return
	end
	
	-- Initializes the pool
	if (not guideFramesPool) then
		guideFramesPool = CreateFramePool("FRAME", Minimap, "RSMinimapPinTemplate");
	end

	local guide = nil
	if (RSNpcDB.GetInternalNpcInfo(entityID)) then
		guide = RSGuideDB.GetNpcGuide(entityID, mapID)
	elseif (RSContainerDB.GetInternalContainerInfo(entityID)) then
		guide = RSGuideDB.GetContainerGuide(entityID, mapID)
	else
		guide = RSGuideDB.GetEventGuide(entityID, mapID)
	end

	if (guide) then
		for pinType, info in pairs (guide) do
			if (not info.questID or not C_QuestLog.IsQuestFlaggedCompleted(info.questID)) then
				local POI = RSGuidePOI.GetGuidePOI(entityID, pinType, info)
				local pin = guideFramesPool:Acquire()
				pin.POI = POI
				if (pin.POI.tooltip and pin.POI.tooltip.comment) then
					pin.POI.name = pin.POI.tooltip.comment
				end
				pin.Texture:SetTexture(POI.texture)
				pin.Texture:SetScale(RSConfigDB.GetIconsMinimapScale())
				pin.IconTexture:SetAtlas(POI.iconAtlas)
				pin:SetFrameLevel(GUIDE_FRAME_LEVEL)
				HBD_Pins:AddMinimapIconMap(RSMinimap, pin, mapID, POI.x, POI.y, false, false)
			end
		end
	end
end

function RSMinimap.RemoveGuide(entityID)
	if (guideFramesPool) then
		for pin in guideFramesPool:EnumerateActive() do
			if (pin.POI.entityID == entityID) then
				guideFramesPool:Release(pin)
				HBD_Pins:RemoveMinimapIcon(RSMinimap, pin)
			end
		end
	end
end
