local _, addon = ...
local API = addon.API;
local RaidCheck = addon.RaidCheck;


local SelectorUI = RaidCheck.SelectorUI;


local IsInInstance = IsInInstance;
local IsIndoors = IsIndoors;
local GetPlayerMap = API.GetPlayerMap;
local GetPlayerMapCoord = API.GetPlayerMapCoord;
local API_GetDungeonEntrancesForMap = C_EncounterJournal.GetDungeonEntrancesForMap;
local GetMapWorldSize = C_Map.GetMapWorldSize;
local GetMapGroupID = C_Map.GetMapGroupID;
local GetMapGroupMembersInfo = C_Map.GetMapGroupMembersInfo;


local DEFAULT_RANGE = 31^2;


local EL = CreateFrame("Frame");
RaidCheck.LocationTracker = EL;
EL.updateDelay = 0.5;   --Increase if the player is far away


EL.mapEvents = {
	PLAYER_ENTERING_WORLD = true,
	PLAYER_MAP_CHANGED = true,
	ZONE_CHANGED = true,
	ZONE_CHANGED_NEW_AREA = true,
	NEW_WMO_CHUNK = true,   --Flying from Caverns of Time exterior to interior doesn't trigger other events, so we need this to update the best uiMapID
	ZONE_CHANGED_INDOORS = true,
};

EL.instancePos = {
	--This overrides the info from map pin
	--Hardcode XY for certain instance whose entrance position doesn't match the pin
	--Some map pins don't show on map until you complete relevant quests or discover the map
	--[journalInstanceID] = {x, y, indoors}
	[285] = {0.57287, 0.46811, true},   --Utgarde Keep
	[286] = {0.57252, 0.46620, false},  --Utgarde Pinnacle
	[786] = {0.44148, 0.59743, true},   --Nighthold
	[726] = {0.41068, 0.61744, true},   --The Arcway
	[187] = {0.61534, 0.26397},         --Dragon Soul
	[184] = {0.57381, 0.29142},         --End Time
	[750] = {0.35528, 0.15325},         --The Battle For Mount Hyjal
	[251] = {0.26814, 0.35114},         --Old Hillsbrad Foothills
	[255] = {0.35972, 0.83893},         --The Black Morass
	[279] = {0.57488, 0.82711},         --The Culling of Stratholme
	[1023]= {0.71979, 0.15423},         --Siege of Boralus (Alliance)
	[1200]= {0.73039, 0.55700},			--Vault of the Incarnate
	[1304]= {0.56979, 0.61049, false},  --Murder Row (Outdoor only)
};

EL.instancePos_Horde = {
	[1023]= {0.88284, 0.51036},         --Siege of Boralus (Horde)
};

EL.mapsWithFloors = {
	--The usual map events may not trigger when BestMap changes. e.g. flying from Searing Gorge to Blackrock Depths
	[32] = true,
	[33] = true,
	[34] = true,
	[35] = true,
};

EL.extraPins = {
	--For instances with multiple entrances but the map only shows one.
	--[uimapID] = { {position = {x = 0, y = 0}, journalInstanceID = 0} }

	[42] = {
		{position = {x = 0.46691, y = 0.70226}, journalInstanceID = 860},    --Karazhan Side Entrance (Return to Karazhan, the dungeon) Main Entrance is the old raid
	},

	[55] = {
		{position = {x = 0.25667, y = 0.50933}, journalInstanceID = 63},    --Deadmines
	},

	[1528] = {
		{position = {x = 0.47607, y = 0.32849}, journalInstanceID = 1179},    --The Eternal Palace (not shown on map)
	},
};

EL.ignoredMap = {
	--[uiMapID] = true,
	[52] = true,    --Westfall Deadmines entrance is actually on 55
};


local function GetDungeonEntrancesForMap(uiMapID)
	local dungeonEntrances;

	if not EL.ignoredMap[uiMapID] then
		dungeonEntrances = API_GetDungeonEntrancesForMap(uiMapID);
	end

	if EL.extraPins[uiMapID] then
		if not dungeonEntrances then
			dungeonEntrances = {};
		end
		for _, v in ipairs(EL.extraPins[uiMapID]) do
			table.insert(dungeonEntrances, v);
		end
	end

	return dungeonEntrances
end


function EL:DoesMapHaveFloors(uiMapID)
	if self.mapsWithFloors[uiMapID] == nil then
		local mapGroupID = GetMapGroupID(uiMapID);
		if mapGroupID and GetMapGroupMembersInfo(mapGroupID) then
			self.mapsWithFloors[uiMapID] = true;
		else
			self.mapsWithFloors[uiMapID] = false;
		end
	end
	return self.mapsWithFloors[uiMapID]
end

function EL:LoadFactionOverride()
	if API.GetPlayerFactionIndex() == 2 then
		for k, v in ipairs(self.instancePos_Horde) do
			self.instancePos[k] = v;
		end
	end
end

function EL:ListenEvents(state)
	if state then
		for event in pairs(self.mapEvents) do
			self:RegisterEvent(event);
		end
		self:SetScript("OnEvent", self.OnEvent);
	else
		for event in pairs(self.mapEvents) do
			self:UnregisterEvent(event);
		end
		self:SetScript("OnEvent", nil);
	end
end

function EL:OnEvent(event, ...)
	if self.mapEvents[event] then
		self:RequestUpdateMap();
		--print(event);
	else

	end
end

function EL:RequestUpdateMap()
	self.t = 0;
	self.mapDirty = true;
	self:SetScript("OnUpdate", self.OnUpdate);
end

function EL:UpdateMap()
	if self.isMultiFloors then
		self.mapDirty = true;
	else
		self.mapDirty = nil;
	end

	local uiMapID = GetPlayerMap();

	if uiMapID ~= self.uiMapID then
		self.uiMapID = uiMapID;

		local isMultiFloors = uiMapID and self:DoesMapHaveFloors(uiMapID);
		--print(uiMapID, isMultiFloors)
		if isMultiFloors then
			self.isMultiFloors = true;
		else
			self.isMultiFloors = nil;
		end

		local trackPosition;

		if IsInInstance() then
			self.isMultiFloors = nil;
		else
			if uiMapID then
				local dungeonEntrances = GetDungeonEntrancesForMap(uiMapID);
				self.x, self.y = GetPlayerMapCoord(uiMapID);
				self.mapWidth, self.mapHeight = GetMapWorldSize(uiMapID);
				if dungeonEntrances and #dungeonEntrances > 0 and self.x and self.y and self.mapWidth > 0 and self.mapHeight > 0 then
					trackPosition = true;
					self.dungeonEntrances = dungeonEntrances;
					local entranceInfo = {};
					local n = 0;
					local x, y, indoorsOnly;
					for k, v in ipairs(dungeonEntrances) do
						if v.position then
							n = n + 1;
							if self.instancePos[v.journalInstanceID] then
								x, y, indoorsOnly = unpack(self.instancePos[v.journalInstanceID]);
							else
								x = v.position.x;
								y = v.position.y;
							end
							entranceInfo[n] = {
								x = x,
								y = y,
								journalInstanceID = v.journalInstanceID,
								indoorsOnly = indoorsOnly,
							};
						end
					end
					self.total = n;
					self.entranceInfo = entranceInfo;
					trackPosition = n > 0;
				end
			else

			end
		end

		if trackPosition then
			self.trackPosition = true;
			self.defaultRange = DEFAULT_RANGE;
			self:SetScript("OnUpdate", self.OnUpdate);
		else
			self.trackPosition = nil;
			self.total = 0;
			self.entranceInfo = nil;
			self:SetScript("OnUpdate", nil);
			SelectorUI:HideUI();
		end
	end
end

function EL:GetMapPointsDistanceSquare(x1, y1, x2, y2)
	local x = self.mapWidth * (x1 - x2);
	local y = self.mapHeight * (y1 - y2);

	return x*x + y*y
end

function EL:OnUpdate(elapsed)
	self.t = self.t + elapsed;
	if self.t >= self.updateDelay then
		self.t = 0;

		if self.mapDirty then
			self:UpdateMap();
		end

		if self.trackPosition then
			self.x, self.y = GetPlayerMapCoord(self.uiMapID);
			self.closestDistance = 10000;
			self.closestIndex = nil;

			if self.x then
				for i = 1, self.total do
					local d = self:GetMapPointsDistanceSquare(self.x, self.y, self.entranceInfo[i].x, self.entranceInfo[i].y);
					if d < self.closestDistance then
						if not (self.entranceInfo[i].indoorsOnly ~= nil and self.entranceInfo[i].indoorsOnly ~= IsIndoors()) then
							self.closestDistance = d;
							self.closestIndex = i;
						end
					end
				end

				if self.closestDistance < 3600 then
					--60 yds
					self.updateDelay = 0.5;
				else
					self.updateDelay = 1;
				end
			end

			--/dump GetMouseFoci()[1].journalInstanceID (use on map pin)
			--print(self.closestDistance, self.closestIndex and self.entranceInfo[self.closestIndex].journalInstanceID, self.x, self.y)

			if self.closestIndex and self.closestDistance < self.defaultRange then
				SelectorUI:ShowInstance(self.entranceInfo[self.closestIndex].journalInstanceID, self.uiMapID);
			else
				SelectorUI:HideUI();
			end
		end
	end
end

function EL:Enable(state)
	if state then
		self:ListenEvents(true);
		self:LoadFactionOverride();
		self:RequestUpdateMap();
	else
		self:SetScript("OnUpdate", nil);
		self:ListenEvents(false);
	end
end
