--[[
			// FriendsData.lua

			// Caches the current friends pagination to used by anyone in the app
			// polls every POLL_DELAY and gets the latest pagination

			// TODO:
				Need polling to update friends. How are we going to handle all the cases
					like the person you're selecting going offline, etc..
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Players = game:GetService('Players')
local HttpService = game:GetService('HttpService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)

local Http = require(Modules:FindFirstChild('Http'))
local Utility = require(Modules:FindFirstChild('Utility'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local UserData = require(Modules:FindFirstChild('UserData'))

local FriendsData = {}

local pollDelay = Utility.GetFastVariable("XboxFriendsPolling")
if pollDelay then
	pollDelay = tonumber(pollDelay)
end

local POLL_DELAY = pollDelay or 30
local STATUS = {
	UNKNOWN = "Unknown";
	ONLINE = "Online";
	OFFLINE = "Offline";
	AWAY = "Away";
}

local isOnlineFriendsPolling = false
local myCurrentFriendsData = nil
local updateEventCns = {}

local function filterXboxFriends(friendsData)
	local titleId = tostring(PlatformService:GetTitleId())
	local onlineRobloxFriendsData = {}
	local onlineRobloxFriendsUserIds = {}
	local onlineFriendsData = {}
	-- filter into two list, those online in roblox and those online
	-- for those online, also get their roblox userId
	for i = 1, #friendsData do
		local data = friendsData[i]
		if data["status"] and data["status"] == STATUS.ONLINE then
			if data["rich"] then
				-- get rich presence table, last entry is most recent activity from user
				local richTbl = data["rich"]
				richTbl = richTbl[#richTbl]
				if richTbl["titleId"] == titleId then
					table.insert(onlineRobloxFriendsData, data)
					table.insert(onlineRobloxFriendsUserIds, data["robloxuid"])
				else
					table.insert(onlineFriendsData, data)
				end
			end
		end
	end

	-- now get roblox friends presence in roblox
	local robloxPresence = {}
	if #onlineRobloxFriendsUserIds > 0 then
		local jsonTable = {}
		jsonTable["userIds"] = onlineRobloxFriendsUserIds
		local jsonPostBody = HttpService:JSONEncode(jsonTable)
		robloxPresence = Http.GetUsersOnlinePresenceAsync(jsonPostBody)
		if robloxPresence and robloxPresence["UserPresences"] then
			robloxPresence = robloxPresence["UserPresences"]
		end
	end	

	-- now append roblox presence data to each users data
	for i = 1, #onlineRobloxFriendsData do
		if robloxPresence[i] then
			local data = onlineRobloxFriendsData[i]
			-- make sure we have the right person
			for j = 1, #robloxPresence do
				local rbxPresenceData = robloxPresence[j]
				local rbxUserId = rbxPresenceData["VisitorId"]
				if rbxUserId == data["robloxuid"] then
					if rbxPresenceData["IsOnline"] == true then
						local placeId = rbxPresenceData["PlaceId"]
						local lastLocation = rbxPresenceData["LastLocation"]
						data["PlaceId"] = placeId
						data["LastLocation"] = lastLocation
					end
					-- remove from list and gtfo
					table.remove(robloxPresence, j)
					break
				end
			end
		end
	end

	-- now sort those in roblox
	table.sort(onlineRobloxFriendsData, function(a, b)
		if a["PlaceId"] and b["PlaceId"] then
			return a["display"] < b["display"]
		end
		if a["PlaceId"] then
			return true
		end
		if b["PlaceId"] then
			return false
		end

		return a["display"] < b["display"]
	end)

	-- now sort all other friends
	table.sort(onlineFriendsData, function(a, b)
		return a["display"] < b["display"]
	end)

	-- now concat tables
	for i = 1, #onlineFriendsData do
		onlineRobloxFriendsData[#onlineRobloxFriendsData + 1] = onlineFriendsData[i]
	end

	return onlineRobloxFriendsData
end

--[[
		// Returns table with an array with the key friends
		// Keys
			// xuid - number, xbox user id
			// gamertage - string, users gamertag
			// display - string, users display name (depends on user settings, could be same as gamertag)
			// robloxuid - number, users roblox userId
			// status - string (Online, Away, Offline, Unknown)
			// rich - array of rich presence, might currently be empty?
				// timestamp - number, UTC timestamp of record
				// device - string, XboxOne, Windows8, etc.
				// title - string, of game
				// playing - boolean, are they playing the game
				// presence - string, rich presence string from that title
]]
local function fetchXboxFriendsAsync()
	local success, result = pcall(function()
		if PlatformService then
			return PlatformService:BeginFetchFriends(Enum.UserInputType.Gamepad1)
		end
	end)
	if success then
		return HttpService:JSONDecode(result)
	else
		print("fetchXboxFriends failed because", result)
	end
end

local function getOnlineFriends()
	if UserSettings().GameSettings:InStudioMode() then
-- Roblox Friends - leaving this in for testing purposes in studio
		local result = Http.GetOnlineFriendsAsync()
		if not result then
			-- TODO: Error code
			return nil
		end
		--
		local myOnlineFriends = {}

		for i = 1, #result do
			local data = result[i]
			local friend = {
				Name = Players:GetNameFromUserIdAsync(data["VisitorId"]);
				UserId = data["VisitorId"];
				LastLocation = data["LastLocation"];
				PlaceId = data["PlaceId"];
				LocationType = data["LocationType"]
			}
			table.insert(myOnlineFriends, friend)
		end

		local function sortFunc(a, b)
			if a.LocationType == b.LocationType then
				return a.Name:lower() < b.Name:lower()
			end
			return a.LocationType > b.LocationType
		end

		table.sort(myOnlineFriends, sortFunc)

		return myOnlineFriends
	elseif game:GetService('UserInputService'):GetPlatform() == Enum.Platform.XBoxOne then
-- Xbox Friends
		local myXboxFriends = fetchXboxFriendsAsync()
		local myOnlineFriends = {}
		if myXboxFriends then
			myXboxFriends = myXboxFriends["friends"]
			myOnlineFriends = filterXboxFriends(myXboxFriends)
		end

		return myOnlineFriends
	end
end

--[[ Public API ]]--
FriendsData.OnFriendsDataUpdated = Utility.Signal()

local isFetchingFriends = false
function FriendsData.GetOnlineFriendsAsync()
	-- can only make one call into PlatformService:BeginFetchFriends() at a time
	while isFetchingFriends do
		wait()
	end
	-- we have current data, this will be updated when polling
	if myCurrentFriendsData then
		return myCurrentFriendsData
	end
	isFetchingFriends = true

	myCurrentFriendsData = getOnlineFriends()
	-- spawn polling on first request
	if not isOnlineFriendsPolling then
		FriendsData.BeginPolling()
	end

	isFetchingFriends = false

	return myCurrentFriendsData
end

function FriendsData.BeginPolling()
	if not isOnlineFriendsPolling then
		isOnlineFriendsPolling = true
		local requesterId = UserData:GetRbxUserId()
		spawn(function()
			wait(POLL_DELAY)
			while requesterId == UserData:GetRbxUserId() do
				myCurrentFriendsData = getOnlineFriends()
				FriendsData.OnFriendsDataUpdated:fire(myCurrentFriendsData)
				wait(POLL_DELAY)
			end
		end)
	end
end

-- we make connections through this function so we can clean them all up upon
-- clearing the friends data
function FriendsData.ConnectUpdateEvent(cbFunc)
	local cn = FriendsData.OnFriendsDataUpdated:connect(cbFunc)
	table.insert(updateEventCns, cn)
end

function FriendsData.Reset()
	isOnlineFriendsPolling = false
	myCurrentFriendsData = nil
	for index,cn in pairs(updateEventCns) do
		cn = Utility.DisconnectEvent(cn)
		updateEventCns[index] = nil
	end
	print('FriendsData: Cleared last users FriendsData')
end

return FriendsData
