-- GuildMap3 by Bru on Blackhand(DE) - wow@docbru.de
-- Enhanced by Voogue on Agamaggan
-- Updated by Sadiqi on Shadow Council

-- reference(s) to Ace Libraries
local Astrolabe = DongleStub("Astrolabe-0.4")
local L = AceLibrary("AceLocale-2.2"):new("GuildMap3")
local BZ = AceLibrary("Babble-Zone-2.2")
local playerName = UnitName("player")

-- construct addon and add mixins
GuildMap3 = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0")

-- seconds after which the UI shall be repainted
local UPDATE_INTERVAL = 0.25

-- seconds to wait between announces when a player is moving
local EARLIEST_ANNOUNCE = 3.0
-- seconds after which an announce must take place when a player is standing still
local LATEST_ANNOUNCE = 30.0

-- seconds after which a player without updated status info will be removed
local PLAYER_TIMEOUT = 45.0

-- constants (do not modify)
local MAX_MINIMAP_ICONS = 75
local MAX_WORLDMAP_ICONS = 250

local lastAnnounce = 0
local versionWarning = true
local announceVersion = true
local pendingAnnounce = false
local players = {}
local position = {continent = 0, zone = 0, x = 0, y = 0}

local myLevel = 0;
local myClass = 0;

-- Called when the addon is loaded
function GuildMap3:OnInitialize()
	--GuildMap3:print("OnInitialize");
	self:InitRegionData()
	myLevel = UnitLevel("player");
	myClass = UnitClass("player");
	myClass = string.upper(myClass);

end

-- Called when the addon is enabled
function GuildMap3:OnEnable()
	self:RegisterEvent("CHAT_MSG_ADDON")
	self:RegisterEvent("WORLD_MAP_UPDATE")
	self:RegisterEvent("RAID_ROSTER_UPDATE", "UpdatePartyOrRaid")
	self:RegisterEvent("PARTY_MEMBERS_CHANGED", "UpdatePartyOrRaid")
	self:RegisterEvent("PLAYER_LEVEL_UP");

	self:ScheduleRepeatingEvent(self.Update, UPDATE_INTERVAL, self)
end

local function handleSplitMessage(sender, ...)
	for i = 1, select("#", ...) do
		GuildMap3:HandleMessage(sender, (select(i, ...)))
	end
end

function GuildMap3:PLAYER_LEVEL_UP(...)
	myLevel = UnitLevel("player");
end

function GuildMap3:CHAT_MSG_ADDON(prefix, message, distribution, sender)
	if prefix ~= "GuildMap3" or playerName == sender then return end

	if message:find("#") then
		handleSplitMessage(sender, strsplit("#", message))
	else
		self:HandleMessage(sender, message)
	end
end

function GuildMap3:WORLD_MAP_UPDATE()
	if WorldMapFrame:IsVisible() then
		self:UpdateWorldMap()
	end
end

function GuildMap3:UpdateWorldMap()
	local currentContinent = GetCurrentMapContinent()
	-- there was a crash for battlegrounds (which return -1 for continent I think)
	if currentContinent < 0 or currentContinent > 3 then
		for i = 1, MAX_WORLDMAP_ICONS do
			local poi = getglobal("GuildMap3Main" .. i)
			poi:Hide()
		end
		return
	end

	local w, h = WorldMapDetailFrame:GetWidth(), WorldMapDetailFrame:GetHeight()

	local currentZone = GetCurrentMapZone()
	local count = 1
	for player, playerData in pairs(players) do
		if playerData.show then
			local playerX = playerData.x / 10000
			local playerY = playerData.y / 10000

			local mapx, mapy = Astrolabe:TranslateWorldMapPosition(playerData.continent, playerData.zone, playerX, playerY, currentContinent, currentZone)
			if mapx and mapy and mapx >= 0 and mapx <= 1 and mapy >= 0 and mapy <= 1 then
				local mnX = mapx * w
				local mnY = -mapy * h

				local poi = getglobal("GuildMap3Main" .. count)
				poi:SetPoint("CENTER", "WorldMapDetailFrame", "TOPLEFT", mnX, mnY)
				poi:Show()

				poi.unit = player
				poi.level = playerData.level;
				poi.class = playerData.class;
--				GuildMap3:print("UpdateWorldMap: "..player.." "..playerData.level.." "..playerData.class);
				count = count + 1
			end
		end

		-- no more than 250 players on world map
		if count >= MAX_WORLDMAP_ICONS then break end
	end

	for i = count, MAX_WORLDMAP_ICONS do
		local poi = getglobal("GuildMap3Main" .. i)
		poi:Hide()
	end
end


function GuildMap3:UpdateMiniMap()
	local continent = position.continent
	if continent < 1 or continent > 3 then
		for i = 1, MAX_MINIMAP_ICONS do
			local poi = getglobal("GuildMap3Mini" .. i)
			poi:Hide()
		end
		return
	end

	local zone = position.zone
	local currFrame = 1
	for player, playerData in pairs(players) do
		if playerData.show and continent == playerData.continent then
			local x = position.x / 10000
			local y = position.y / 10000
			local playerX = playerData.x / 10000
			local playerY = playerData.y / 10000

			local distFromCenter, deltax, deltay = Astrolabe:ComputeDistance(continent, zone, x, y, playerData.continent, playerData.zone, playerX, playerY)
			if distFromCenter < 400 then
				local poi = getglobal("GuildMap3Mini" .. currFrame)
				local poiTexture = getglobal("GuildMap3Mini" .. currFrame .. "Texture")
				
				poi.unit = player
			  poi.level = playerData.level;
			  poi.class = playerData.class;					

			  Astrolabe:PlaceIconOnMinimap( poi, playerData.continent, playerData.zone, playerX, playerY );

				if Astrolabe:IsIconOnEdge( poi ) then
				  local dir = Astrolabe:GetDirectionToIcon( poi );
					poiTexture:SetTexture(self:Angle(dir));   --roundDeltax, roundDeltay))				
				else
					poiTexture:SetTexture("Interface\\AddOns\\GuildMap3\\Images\\MiniMapIcon")
				end

				currFrame = currFrame + 1
			end
		end

		-- no more than 75 players on mini map
		if currFrame >= MAX_MINIMAP_ICONS then break end
	end
	for i = currFrame, MAX_MINIMAP_ICONS do
		local poi = getglobal("GuildMap3Mini" .. i)
		poi:Hide()
	end
end

function GuildMap3:Update()
	self:UpdatePartyOrRaid()

	local announceDelta = GetTime() - lastAnnounce

	local positionChanged = self:UpdatePlayerPos()
	-- an announce is scheduled if the player position has changed or 
	-- the maximum time without an announce was exceeded
	pendingAnnounce =
		pendingAnnounce or
		positionChanged or
		(announceDelta > LATEST_ANNOUNCE)

	if pendingAnnounce and announceDelta > EARLIEST_ANNOUNCE then
		self:AnnouncePosition()
	end

	-- garbage collect timed out players
	self:RemoveOldPlayers()

	if WorldMapFrame:IsVisible() then
		self:UpdateWorldMap()
	else
		self:EnsureZoneMap()
		self:UpdateMiniMap()
	end
end

function GuildMap3:UpdatePlayerPos()
	local continent, zone, x, y = self:GetCurrentPlayerPosition()

	x = math.floor(x * 10000)
	y = math.floor(y * 10000)

	if position.x ~= x or position.y ~= y then
		position.continent = continent
		position.zone = zone
		position.x = x
		position.y = y
		return true
	end
end

function GuildMap3:AnnouncePosition()
	if self:IsValidPosition(position.continent, position.zone, position.x, position.y) then
		pendingAnnounce = nil
		lastAnnounce = GetTime()
		local outZone = self:ConvertZoneOut(position.continent, position.zone)
		local message = "POS " .. position.continent .. " " .. outZone .. " " .. position.x .. " " .. position.y .. " " .. myLevel .. " " .. myClass
		if announceVersion then
			message = message .. "#V " .. self.version
			announceVersion = nil
		end
			
		self:Broadcast(message)
	end
end

function GuildMap3:Broadcast(message)
	if IsInGuild() then
		local clearAFK = GetCVar("autoClearAFK")
		SetCVar("autoClearAFK", 0)
		SendAddonMessage("GuildMap3", message, "GUILD")
		SetCVar("autoClearAFK", clearAFK)
	end
end

function GuildMap3:HandleMessage(sender, message)
	if message:sub(1, 4) == "POS " then
		self:HandlePosition(sender, message)
	elseif message:sub(1, 2) == "V " then
		self:HandleVersion(sender, message)
	end
end

function GuildMap3:HandlePosition(sender, message)
	local tmpcont, tmpzone, tmpx, tmpy , tmplvl, tmpclass = select(3, message:find("POS (%d+) (%d+) (%d+) (%d+) (%d+) (%w+)"))
	if tmpcont and tmpzone and tmpx and tmpy then
		-- perform some validations to prevent crashes later -)
		tmpcont = tonumber(tmpcont)
		tmpzone = tonumber(tmpzone)
		tmpx = tonumber(tmpx)
		tmpy = tonumber(tmpy)
		tmplvl = tonumber(tmplvl);
		
--		GuildMap3:print(sender.." cont="..tmpcont.." zone="..tmpzone.." "..tmplvl.." "..tmpclass);

		if self:IsValidPosition(tmpcont, tmpzone, tmpx, tmpy) then
			tmpzone = self:ConvertZoneIn(tmpcont, tmpzone)
			if not players[sender] then
				-- new player: announce
				announceVersion = true
				pendingAnnounce = true
				players[sender] = { continent = tmpcont, zone = tmpzone, x = tmpx, y = tmpy, lastUpdate = GetTime(), show = true, level=tmplvl, class=tmpclass }
			else
				-- if we have the player already, do update him only to not reset the show flag
				players[sender].continent = tmpcont
				players[sender].zone = tmpzone
				players[sender].x = tmpx
				players[sender].y = tmpy
				players[sender].level = tmplvl;
				players[sender].class = tmpclass;
				players[sender].lastUpdate = GetTime()
			end
		end
	end
end

function GuildMap3:HandleVersion(sender, message)
	if players[sender] then
		local version = message:sub(3, message:len())
		players[sender].version = version
		local revision = tonumber(version)
		if revision and versionWarning and revision > tonumber(self.version) then
			local updateMessage = string.format(L["%s is using a newer version (rev. %s) of GuildMap3. Please update."], sender, revision)
			DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00" .. updateMessage)
			versionWarning = nil
		end
	end
end

function GuildMap3:MiniMap_OnClick(arg1)
	local x, y = GetCursorPosition()
	Minimap:PingLocation(x, y)
end

function GuildMap3:RemoveOldPlayers()
	for player, playerData in pairs(players) do
		if (GetTime() - playerData.lastUpdate > PLAYER_TIMEOUT) then
			players[player] = nil
		end
	end
end

function GuildMap3:IsValidPosition(continent, zone, x, y)
  if zone == nil then return false end
  if continent == nil then return false end
	if continent < 1 or continent > 4 then return false end
	if continent == 1 and (zone < 1 or zone > 24) then return false end
	if continent == 2 and (zone < 1 or zone > 29) then return false end
	if continent == 3 and (zone < 1 or zone > 8) then return false end
	if continent == 4 and (zone < 1 or zone > 11) then return false end
	if x < 1 or x > 9999 then return false end
	if y < 1 or y > 9999 then return false end
	return true
end

function GuildMap3:ShowMiniMapToolTip( whichicon )
	GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT")
	local tooltipText = ""
	local unitcolor = {};
	local unitlevel = 0;

	GameTooltip:ClearLines();
	unitcolor = RAID_CLASS_COLORS[whichicon.class];		
	unitlevel = whichicon.level;
	tooltipText = "["..unitlevel.."] "..whichicon.unit
	GameTooltip:AddLine( tooltipText, unitcolor.r, unitcolor.g, unitcolor.b );
	GameTooltip:Show()
end

function GuildMap3:ShowWorldMapToolTip( whichicon )
	-- determine tooltip anchor
	local x, y = this:GetCenter()
	local parentX, parentY = this:GetParent():GetCenter()
	if x > parentX then
		WorldMapTooltip:SetOwner(this, "ANCHOR_LEFT")
	else
		WorldMapTooltip:SetOwner(this, "ANCHOR_RIGHT")
	end

	local tooltipText = ""
	
	local unitlevel = 0;
	local unitcolor = {};

	unitcolor = RAID_CLASS_COLORS[whichicon.class];		
	unitlevel = whichicon.level;
	tooltipText = "["..unitlevel.."] "..whichicon.unit
	WorldMapTooltip:AddLine( tooltipText, unitcolor.r, unitcolor.g, unitcolor.b );

	WorldMapTooltip:Show()
end

function GuildMap3:UpdatePartyOrRaid()
	for player, playerData in pairs(players) do
		playerData.show = true
	end

	local numRaidMembers = GetNumRaidMembers()
	for i = 1, numRaidMembers do
		local name = GetRaidRosterInfo(i)
		if name and players[name] then
			players[name].show = false
		end
	end

	local numPartyMembers = GetNumPartyMembers()
	for i = 1, numPartyMembers do
		local name = UnitName("party" .. i)
		if name and players[name] then
			players[name].show = false
		end
	end
end

function GuildMap3:Angle( rad )
	local angle = 360-(rad * 57.295)
	local fileNumber = math.floor((angle / 10) + 0.5) * 10
	if fileNumber == 360 then
		fileNumber = 0
	end
	return "Interface\\Addons\\GuildMap3\\images\\MiniMapArrow" .. fileNumber

end

function GuildMap3:GetCurrentPlayerPosition()
	local continent = GetCurrentMapContinent()
	local zone = GetCurrentMapZone()
	local x, y = GetPlayerMapPosition("player")
	continent = continent or -1
	zone = zone or -1
	x = x or -1
	y = y or -1
	return continent, zone, x, y
end

local function CurrentZoneFix_SetupFix()
	-- CurrentZoneFix v2.0 by Legorol
	-- email: legorol@cosmosui.org
	local versionID = 20
	local id = CurrentZoneFix_FixInPlace
	if id then
		id = tonumber(id)
		if id and id >= versionID then
			return
		elseif not SetMapToCurrentZone(true) then
			DEFAULT_CHAT_FRAME:AddMessage("Warning! Obsolete version "..
				"of CurrentZoneFix function detected. The old "..
				"version is being loaded either as a standalone "..
				"AddOn, or as code embedded inside another AddOn. "..
				"You must update it to avoid bugs with the map!",1,0,0)
			return
		end
	end

	local zoneID = {}
	for continent in ipairs{GetMapContinents()} do
		for zone,name in ipairs{GetMapZones(continent)} do
			zoneID[name] = zone
		end
	end

	local orig_SetMapToCurrentZone = SetMapToCurrentZone
	SetMapToCurrentZone = function(deactivate)
		if deactivate then
			SetMapToCurrentZone = orig_SetMapToCurrentZone
			CurrentZoneFix_FixInPlace = nil
			return true
		end
		orig_SetMapToCurrentZone()
		if GetCurrentMapZone() == 0 and GetCurrentMapContinent() > 0 then
			SetMapZoom(GetCurrentMapContinent(), zoneID[GetRealZoneText()])
		else
			SetMapToCurrentZone = orig_SetMapToCurrentZone
			CurrentZoneFix_FixInPlace = "Deactivated "..versionID
		end
	end
	CurrentZoneFix_FixInPlace = versionID
end

local function iterateZones(continent, ...)
  --GuildMap3:print("iterateZones");
	for i = 1, select("#", ...) do
		local zone = select(i, ...)
		local translatedEN = BZ:GetReverseTranslation(zone)
		local indexEN = GuildMap3_ZoneIndexesEN[translatedEN]
--		if indexEN == nil then
			---GuildMap3:print("GuildMap3_ZoneIndexesEN returned nil on translatedEN="..translatedEN);
			--GuildMap3:print("setting GuildMap3_ZoneIndexesOut["..continent.."]["..i.."]= nil ");
		--else
--			GuildMap3:print("setting GuildMap3_ZoneIndexesOut["..continent.."]["..i.."]="..indexEN);
		--end
		GuildMap3_ZoneIndexesOut[continent][i] = indexEN
		GuildMap3_ZoneIndexesIn[continent][indexEN] = i
	end
end

function GuildMap3:print( str )
	DEFAULT_CHAT_FRAME:AddMessage( str );
end

local function iterateContinents(...)
	for i = 1, select("#", ...) do
		local c = select(i, ...)
		GuildMap3_ZoneIndexesOut[i] = {}
		GuildMap3_ZoneIndexesIn[i] = {}
		iterateZones(i, GetMapZones(i))
	end
end

function GuildMap3:InitRegionData()
	iterateContinents(GetMapContinents())
	CurrentZoneFix_SetupFix()
end

function GuildMap3:ConvertZoneOut(continent, zone)
--GuildMap3:print("ConvertZoneOut");
	if continent >= 1 and continent <= 4 and zone ~= 0 then
		if continent==nil then DEFAULT_CHAT_FRAME:AddMessage("Continent=nil") end
		if zone==nil then DEFAULT_CHAT_FRAME:AddMessage("zone=nil") end
		return GuildMap3_ZoneIndexesOut[continent][zone]
	else
		return zone
	end
end

function GuildMap3:ConvertZoneIn(continent, zone)
	if continent >= 1 and continent <= 4 and zone ~= 0 then
		return GuildMap3_ZoneIndexesIn[continent][zone]
	else
		return zone
	end
end

function GuildMap3:SetupDummies()
	local continents = { GetMapContinents() }
	for c in ipairs({GetMapContinents()}) do
		local zones = { GetMapZones(c) }
		for z, zone in ipairs(zones) do
			players[zone] = { continent = c, zone = z, x = 5000, y = 5000, lastUpdate = 10000000, show = true }
		end
	end
end

function GuildMap3:EnsureZoneMap()
	local mapContinent = GetCurrentMapContinent()
	local mapZone = GetCurrentMapZone()

	local playerContinent, playerZone = Astrolabe:GetCurrentPlayerPosition()

	if playerContinent ~= 0 and playerZone ~= 0 and (mapContinent ~= playerContinent or mapZone ~= playerZone) then
		SetMapToCurrentZone()
	end
end

