-- Written by Kip Turner, Copyright ROBLOX 2015

local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ShellModules = Modules:FindFirstChild("Shell")
local Utility = require(ShellModules:FindFirstChild('Utility'))

local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserInputService = game:GetService('UserInputService')

local SoundManager = require(ShellModules:FindFirstChild('SoundManager'))
local GlobalSettings = require(ShellModules:FindFirstChild('GlobalSettings'))

local ScreenManager = {}

local ScreenStack = {}
local ScreenToHideMap = {}

local ScreenGuis = {[1] = GuiRoot}

local function ContainsScreenInternal(screen)
	local foundScreenIndex = nil
	for i, otherScreen in pairs(ScreenStack) do
		if otherScreen == screen then
			foundScreenIndex = i
		end
	end

	return foundScreenIndex
end

local function GetScreenPriorityInternal(screen)
	local priority = GlobalSettings.DefaultPriority
	if screen.GetPriority ~= nil then
		priority = screen:GetPriority()
	end
	return priority
end

function ScreenManager:GetInsertIndexForScreen(screen)
	local screenPriority = GetScreenPriorityInternal(screen)
	local currentScreen = self:GetTopScreen()
	while currentScreen and GetScreenPriorityInternal(currentScreen) > screenPriority do
		currentScreen = self:GetScreenBelow(currentScreen)
	end
	if currentScreen then
		local currentScreenIndex = ContainsScreenInternal(currentScreen)
		if currentScreenIndex then
			return currentScreenIndex + 1
		end
	end
	return 1
end

function ScreenManager:GetGuiRoot()
	if Utility.ShouldUseVRAppLobby() then
		local Panel3D = require(Modules.VR.Panel3D)
		local appShellVrPanel = Panel3D.Get("AppShellVR")
		return appShellVrPanel:GetGUI()
	else
		return GuiRoot
	end
end

function ScreenManager:GetScreenGuiByPriority(priority)
	priority = math.max(1, priority)
	if not ScreenGuis[priority] then
		for i = 1, priority do
			if not ScreenGuis[i] then
				if Utility.ShouldUseVRAppLobby() then
					local Panel3D = require(Modules.VR.Panel3D)

					local appShellVrPanel = Panel3D.Get("AppShellVR")
					local screenContainerFrame = Utility.Create'Frame'
					{
						Name = "ScreenContainer";
						Size = UDim2.new(1,0,1,0);
						BackgroundTransparency = 1;
						Parent = appShellVrPanel:GetGUI();
					}
					local subPanel = appShellVrPanel:AddSubpanel(screenContainerFrame)
					subPanel:SetDepthOffset(i/10)
					subPanel:SetEnabled(false)
					ScreenGuis[i] = screenContainerFrame

					screenContainerFrame.ChildAdded:connect(function(child)
						subPanel:SetEnabled(true)
					end)

					screenContainerFrame.ChildRemoved:connect(function(child)
						if #screenContainerFrame:GetChildren() == 0 then
							subPanel:SetEnabled(false)
						end
					end)
				else
					ScreenGuis[i] = Utility.Create'ScreenGui'
					{
						Name = 'AppShell' .. tostring(i);
						Parent = CoreGui;
					}
				end
			end
		end
	end

	return ScreenGuis[priority]
end

-- TODO: handle race conditions for opening multiple screens
local openScreenEntryCount = 0
local openScreenExitCount = 0
function ScreenManager:OpenScreen(screen, hideCurrent)
	if openScreenEntryCount ~= openScreenExitCount then
		print("ScreenManager: OpenScreen Re-entry detected" , openScreenEntryCount, openScreenExitCount)
	end
	openScreenEntryCount = openScreenEntryCount + 1

	if hideCurrent == nil then
		hideCurrent = true
	end

	local currentScreen = self:GetTopScreen()
	local insertIndex = self:GetInsertIndexForScreen(screen)
	local isNewTop = insertIndex > #ScreenStack

	if not isNewTop then
		hideCurrent = false
	end

	if currentScreen ~= screen then
		local foundScreenIndex = ContainsScreenInternal(screen)

		if foundScreenIndex then
			table.remove(ScreenStack, foundScreenIndex)
		end

		if currentScreen then
			currentScreen:RemoveFocus()
			if hideCurrent then
				currentScreen:Hide()
			end
			ScreenToHideMap[currentScreen] = hideCurrent
		end

		table.insert(ScreenStack, insertIndex, screen)
		-- spawn(function()
		if isNewTop then
			if ScreenToHideMap[screen] ~= false then
				screen:Show()
			end

			if screen == self:GetTopScreen() then
				screen:Focus()
			end
		else
			ScreenToHideMap[screen] = true
		end
		-- end)
	end

	openScreenExitCount = openScreenExitCount + 1
end

local closeCurrentEntryCount = 0
local closeCurrentExitCount = 0
function ScreenManager:CloseCurrent()
	if closeCurrentEntryCount ~= closeCurrentExitCount then
		print("ScreenManager: CloseScreen Re-entry detected" , closeCurrentEntryCount, closeCurrentExitCount)
	end
	closeCurrentEntryCount = closeCurrentEntryCount + 1

	local currentScreen = ScreenStack[#ScreenStack]
	local belowScreen = currentScreen and self:GetScreenBelow(currentScreen)
	if currentScreen then
		-- spawn(function()
			currentScreen:Hide()
			currentScreen:RemoveFocus()
			if currentScreen.ScreenRemoved then
				currentScreen:ScreenRemoved()
			end
		-- end)
		table.remove(ScreenStack, #ScreenStack)
		ScreenToHideMap[currentScreen] = nil
	end


	-- if belowScreen and belowScreen ~= self:GetTopScreen() then return end
	local newTop = belowScreen
	if newTop and newTop == self:GetTopScreen() then
		local showNewTop = (ScreenToHideMap[newTop] == true)

		-- spawn(function()
			if showNewTop then
				newTop:Show()
			end
			if newTop == self:GetTopScreen() then
				newTop:Focus()
			end
		-- end)
	end
	closeCurrentExitCount = closeCurrentExitCount + 1
end

function ScreenManager:ContainsScreen(screen)
	local index = ContainsScreenInternal(screen)
	if index then
		return ScreenStack[index]
	end
	return nil
end

function ScreenManager:GetScreenBelow(screen)
	local thisScreenIndex = ContainsScreenInternal(screen)
	if thisScreenIndex then
		return ScreenStack[thisScreenIndex - 1]
	end
	return nil
end

function ScreenManager:GetTopScreen()
	return ScreenStack[#ScreenStack]
end


----- TWEENS -----

local function FadeInElement(element, tweeners)
	if element == nil then return end
	if element:IsA('ImageLabel') or element:IsA('ImageButton') then
		table.insert(tweeners, Utility.PropertyTweener(element, 'ImageTransparency', 1, element.ImageTransparency, 0.5, Utility.EaseOutQuad))
	end
	if element:IsA('GuiObject') then
		table.insert(tweeners, Utility.PropertyTweener(element, 'BackgroundTransparency', 1, element.BackgroundTransparency, 0.5, Utility.EaseOutQuad))
	end
	if element:IsA('TextLabel') or element:IsA('TextBox') or element:IsA('TextButton') then
		table.insert(tweeners, Utility.PropertyTweener(element, 'TextTransparency', 1, element.TextTransparency, 0.5, Utility.EaseOutQuad))
	end
	for _, child in pairs(element:GetChildren()) do
		FadeInElement(child, tweeners)
	end
end

function ScreenManager:FadeInSitu(guiObject)
	local tweeners = {}
	if guiObject then
		FadeInElement(guiObject, tweeners)
	end
	return tweeners
end

function ScreenManager:DefaultFadeIn(guiObject)
	local tweeners = {}

	if guiObject then
		table.insert(tweeners, Utility.PropertyTweener(guiObject, 'Position', guiObject.Position + UDim2.new(0.15, 0, 0, 0), guiObject.Position, 0.5,
			function(t,b,c,d)
				if t >= d then return b + c end
				t = t / d;
				local tComputed = t*(t-2)
				return -UDim2.new(c.X.Scale * tComputed, c.X.Offset * tComputed, c.Y.Scale * tComputed, c.Y.Offset * tComputed) + b
			end))

		FadeInElement(guiObject, tweeners)
	end

	return tweeners
end

function ScreenManager:DefaultCancelFade(tweens)
	if tweens then
		for _, tween in pairs(tweens) do
			tween:Finish()
		end
	end

	return nil
end
---------------

--------- SOUNDS ---------

function ScreenManager:PlayDefaultOpenSound()
	SoundManager:Play('ScreenChange')
end

----------------------------



return ScreenManager

