-- OptionsList didn't quite work out the way I intended,
-- so I literally just coded my own instead
-- TODO: Multiple-choice menus so I can add more stuff

local player = ...
assert(player,"[ScreenSelectMusic decorations/InfoPane.lua] Can't display player options menu without a PlayerNumber!")

-- Tracking.
local activeSection = "Main"
local mainMenuFocus = "Speed"
local PMFocus = false

-- Input handling.
local function InputHandler( event )
	--SM(event)
	if event.PlayerNumber ~= player or not GAMESTATE:IsHumanPlayer(player) then return end
	if PMFocus then
		if (event.GameButton == "Back" or event.GameButton == "Select") and event.type == "InputEventType_FirstPress" then
			if event.GameButton == "Select" or activeSection == "Main" then
				-- Close the menu.
				PMFocus = false
				MESSAGEMAN:Broadcast("PMClosed", {Player=player})
			else
				-- Back to main menu.
				activeSection = "Main"
				MESSAGEMAN:Broadcast("PMSectUpdated", {Player=player})
			end
		end
		
		if event.GameButton == "Start" and event.type == "InputEventType_FirstPress" then
			SCREENMAN:PlayStartSound()
			-- Gotta do this a bit weird to keep from two menus reacting at the same time.
			-- (activeSection doesn't change until whatever functions are called after this message's been sent)
			MESSAGEMAN:Broadcast("StartButton", {Player=player, Sect=activeSection})
			MESSAGEMAN:Broadcast("CheckFocus")
		end
		
		if string.find(event.GameButton, "Menu") and event.type ~= "InputEventType_Release" then
			local mov = (event.GameButton == "MenuRight" or event.GameButton == "MenuDown") and 1 or -1
			local ver = (event.GameButton == "MenuUp" or event.GameButton == "MenuDown")
			MESSAGEMAN:Broadcast("Navigation", {Player=player, Movement=mov, Vertical=ver})
		end
	else
		if event.GameButton == "Select" and event.type == "InputEventType_FirstPress" then
			-- Open the menu.
			PMFocus = true
			-- Set active section in case it was closed from a submenu last time.
			activeSection = "Main"
			MESSAGEMAN:Broadcast("PMOpened", {Player=player})
			--SM(SCREENMAN:GetTopScreen():GetChildren())
		end
	end
end

local function LifeChoices()
	return GAMESTATE:GetCurrentGame():GetName() == "para" and {"training", "default", "hard", "pressure"} or {"training", "fallback", "pressure"}
end

-- A lot of these controls aren't very useful outside "para" mode; but still include just a bare minimum.
local function VidChoices()
	return GAMESTATE:GetCurrentGame():GetName() == "para" and {"default", "stan_default", "parapara", "stan_parapara", "arcade", "static"} or {"default", "static"}
end

-- Table of main menu choices. Formatted as such:
-- Name: String. Must match the prefix of the ModIcons used.
-- OptMetrics: The metric group under [ScreenOptionsMaster] to rip from. Failing that...
-- Choices: choices obtained by any other means, whether it be something like "NOTESKIN:GetNoteSkinNames()"
	-- or just a hard-coded table.
-- Default: The choice that the scroller should start on by default,
	-- for cases like the Rate menu where it shouldn't start on the first option;
	-- or cases where it should start on the player's choices on screen load.
-- ChoiceDisplay: Must be a function with a "choice" parameter, returning an Actor of some kind.
	-- It will replace the default icon handling.
-- OnSet: Must be a function with a "choice" parameter. For options that don't go through PlayerOptions
	-- or SongOptions, this given function is used instead.
-- AffectsAll: Set to "true" to add an extra footnote to the explanation at the bottom of the menu.
-- ExplanationPerOption: Set to "true" to make the explanation text use different text for each choice.
--[[
-- Use if a submenu doesn't have icons yet:
ChoiceDisplay = function(choice)
	return Def.ActorFrame{
		Name = choice,
		Def.BitmapText {
			Font = "Common Normal",
			Text = choice,
			OnCommand=function(self) self:zoom(0.75):maxwidth(64/0.75) end
		}
	}
end,
--]]
local menuSects = {
	{
		Name = "Speed",
		OptMetrics = "OpLSpeed"
	},
	{
		Name = "Turn",
		OptMetrics = "OpLTurn"
	},
	{
		Name = "Vis",
		OptMetrics = "OpLVisib",
		Multiple = true
	},
	{
		Name = "Rate",
		OptMetrics = "EZRate",
		Default = 5,
		AffectsAll = true
	},
	-- Scary scary. A lot of custom stuff needs to be done for the Noteskin menu.
	{
		Name = "NoteSkin",
		Choices = NOTESKIN:GetNoteSkinNames(),
		ChoiceDisplay = function(choice)
			return Def.ActorFrame{
				Name = choice,
				NOTESKIN:LoadActorForNoteSkin(NoteForNoteSkinPreview(), "Tap Note", choice) .. {
					InitCommand=function(self)
						self:zoom(0.55):y(-2)
					end
				},
				Def.BitmapText{
					Font = "Common Normal",
					Text = string.upper(choice),
					InitCommand=function(self)
						self:zoom(0.3):maxwidth(50/0.3):y(21):vertalign(bottom)
					end
				}
			}
		end,
		OnSet = function(choice)
			GAMESTATE:GetPlayerState(player):GetPlayerOptions(ModsLevel[1]):NoteSkin(choice)
		end
	},
	{
		Name = "Life",
		Choices = LifeChoices(),
		Default = 2,
		OnSet = function(choice)
			setenv("LifeBarType" .. ToEnumShortString(player), choice)
			SetLifeBarTypeSettings(player)
		end,
		ExplanationPerOption = true
	},
	{
		Name = "VidMode",
		Choices = VidChoices(),
		OnSet = function(choice)
			THEME_EKSDBGVidMode = choice
			GAMESTATE:GetSongOptionsObject(ModsLevel[1]):StaticBackground(choice == "static")
		end,
		AffectsAll = true,
		ExplanationPerOption = true
	}
}

-- Arcade-style navigation won't have an Options button;
-- we need a separate Exit option to get out in this case.
if PREFSMAN:GetPreference("ThreeKeyNavigation") then
	menuSects[#menuSects+1] = {
		Name = "Exit",
		OnSelection = function()
			PMFocus = false
			MESSAGEMAN:Broadcast("PMClosed", {Player=player})
			-- Move it back to the first spot on the menu.
			mainMenuFocus = "Speed"
			MESSAGEMAN:Broadcast("FocusUpdated", {Player=player})
		end
	}
end

-- Basic main menu stuff.
local function CreateMainMenuItems()
	local t = {}
	
	for i, choic in ipairs(menuSects) do
		-- todo: make fancier?
		t[#t+1] = Def.ActorFrame{
			Def.BitmapText{
				Font="Common Normal",
				Text=choic.Name,
				OnCommand=cmd(shadowlength,0;zoom,0.5;strokecolor,Color("Outline");maxwidth,64/0.5)
			}
		}
	end
	
	return t
end

-- Arbitrarily read from some metrics to generate our submenus' items.
-- Keep this separate so other functions can use it.
local function GetSubmenuChoices(menuIndex)
	local par = menuSects[menuIndex]
	local ret = {}
	
	if par.OptMetrics then
		local lDef = THEME:GetMetric("ScreenOptionsMaster", par.OptMetrics)
		local haha, no, cCount = string.find(lDef, "(%d+)")
		
		for i=1, cCount do
			lIne = THEME:GetMetric("ScreenOptionsMaster", par.OptMetrics .. "," .. i)
			local haha, no, theMod = string.find(lIne, "mod,(.-);")
			
			ret[#ret+1] = theMod
		end
	end
	
	return ret
end

local function CreateSubmenuItems(menuIndex, Choices)
	local t = {}
	local par = menuSects[menuIndex]
	--if par.OnSelection then return t end
	
	for i, choic in ipairs(Choices) do
		if par.ChoiceDisplay then
			t[#t+1] = par.ChoiceDisplay(choic)
		else
			t[#t+1] = Def.ActorFrame{
				Name = choic,
				-- dots (speed stuff) in filename strings need to be replaced with dashes, game has a hard time picking up the files otherwise
				LoadActor(THEME:GetPathG("", "_ModIcons/" .. par.Name .. " " .. string.gsub(choic, "%.", "-"))) .. {
					InitCommand=function(self)
						self:scaletocover(-24,-24,24,24)
					end
				}
			}
		end
	end
	
	return t
end

local posMod = player == PLAYER_1 and -1 or 1
local paneWidth = (_screen.w - 240) / 2

-- Function to make the submenu itself.
local function CreateSubmenu(menuIndex)
	local par = menuSects[menuIndex]
	
	-- if par.Multiple then
		-- local Tracking = {}
		-- for i, choic in ipairs(Choices) do
			-- Tracking[choic] = false
		-- end
	-- end
	if par.OnSelection then
		return Def.ActorFrame{
			Name = par.Name,
			OnCommand=function(self)
				self:visible(menuIndex == 1)
			end,
			FocusUpdatedMessageCommand = function(self,param)
				if param.Player ~= player then return end
				self:visible(mainMenuFocus == self:GetName())
			end,
			NavigationMessageCommand = function(self,param)
				if param.Player ~= player then return end
				-- failsafe
				if activeSection == self:GetName() then
					activeSection = "Main"
					MESSAGEMAN:Broadcast("PMSectUpdated", {Player=player})
				end
			end
		}
	else	
		local Choices = par.Choices or GetSubmenuChoices(menuIndex)
		return Def.ActorFrame{
			Name = par.Name,
			OnCommand=function(self)
				self:visible(menuIndex == 1)
				
				local Scroll = self:GetChild("Scroll")
				
				-- Set it to a default place on the scroller, if specified.
				if par.Default then
					Scroll:SetDestinationItem(par.Default-1)
				end
				
				-- When a player's mods are different from default on screen load, 
				-- go through this menu's choices to figure out which one we should start on.
				-- todo: make less hard-coded
				for i, choic in ipairs(Choices) do
					if par.Name == "Life" then
						if choic == getenv("LifeBarType" .. ToEnumShortString(player)) then
							Scroll:SetDestinationItem(i-1)
							break
						end
					elseif par.Name == "VidMode" then
						if choic == THEME_EKSDBGVidMode then
							Scroll:SetDestinationItem(i-1)
							break
						end
					else
						-- Song rate uses song options, not player options.
						local modString = par.Name == "Rate" and GAMESTATE:GetSongOptions(ModsLevel[1]) or GAMESTATE:GetPlayerState(player):GetPlayerOptionsString(ModsLevel[1])
						
						-- Make the player options string lowercase so we can catch The Mod with what we have.
						if string.find(modString:lower(), choic:lower()) then
							-- Set the scroller to the appropriate item.
							Scroll:SetDestinationItem(i-1)
							break
						end
					end
				end
			end,
			FocusUpdatedMessageCommand = function(self,param)
				if param.Player ~= player then return end
				self:visible(mainMenuFocus == self:GetName())
			end,
			NavigationMessageCommand = function(self,param)
				if param.Player ~= player or activeSection ~= self:GetName() then return end
				
				local Scroll = self:GetChild("Scroll")
				
				-- number of items, 0-is-start
				local numItemsInd = Scroll:GetNumItems()-1
				-- floor it to prevent oddities caused by button mashing from occurring
				local location = math.floor(Scroll:GetCurrentItem() + param.Movement)
				-- Scroller loop logic
				if location > numItemsInd then
					Scroll:SetDestinationItem(0)
				elseif location < 0 then
					Scroll:SetDestinationItem(numItemsInd)
				else
					Scroll:SetDestinationItem(location)
				end
				
				-- If deemed necessary, update the explanation text.
				if par.ExplanationPerOption then
					self:GetChild("Explain"):settext(THEME:GetString("OptionExplanations", "OpL" .. par.Name .. "_" .. Choices[Scroll:GetDestinationItem()+1]))
				end
			end,
			StartButtonMessageCommand=function(self,param)
				if param.Player ~= player or param.Sect ~= self:GetName() then return end
				local Scroll = self:GetChild("Scroll")
				
				if par.OnSet then
					par.OnSet(Choices[Scroll:GetDestinationItem()+1])
				else
					-- gamecommand? really?...
					-- TODO: MULTIPLE CHOICE HANDLING
					GAMESTATE:ApplyGameCommand("mod," .. Choices[Scroll:GetDestinationItem()+1], player)
				end
				
				Trace(GAMESTATE:GetPlayerState(player):GetPlayerOptionsString(ModsLevel[1]) .. "\n" .. GAMESTATE:GetSongOptions(ModsLevel[1]))
				-- Use an actual OptionsList message command because I'm lazy and don't want to update the message command names right now
				MESSAGEMAN:Broadcast("OptionsListStart")
				activeSection = "Main"
			end,
			
			Def.ActorScroller{
				Name = 'Scroll',
				NumItemsToDraw=15,
				OnCommand=function(self)
					self:SetSecondsPerItem(0.01):SetFastCatchup(true)
				end,
				TransformFunction=function(self, offset, itemIndex, numItems)
					local badCentered = numItems > 5 and offset or itemIndex - ((numItems/2)-0.5)
					self:xy(52*badCentered,-4)
					if math.abs(offset) < 1 then
						self:diffuse(Color.White)
					else
						self:diffuse(ColorDarkTone(PlayerColor(player)))
					end
				end,
				children = CreateSubmenuItems(menuIndex, Choices)
			},
			
			Def.BitmapText {
				Name="Explain",
				Font="Common Normal",
				OnCommand=function(self)
					self:y(28):zoom(0.3):strokecolor(Color.Outline):maxwidth((paneWidth-4)/0.3):wrapwidthpixels((paneWidth-4)/0.3)
					
					local stringAdd = par.AffectsAll and THEME:GetString("ScreenSelectMusic", "OptionsAffectsAll") or ""
					self:settext(THEME:GetString("OptionExplanations", "OpL" .. par.Name) .. stringAdd)
				end
			}
		}
	end
	
	--return s
end

--
-- Enough functions.
--

local t = Def.ActorFrame { 
	-- TODO: either made Select a code thingy, or just get rid of this entirely...
	CodeMessageCommand=function(self, params)
		if params.Name == "OpenOptionsList" and params.PlayerNumber == player then
			-- Open the menu.
			PMFocus = true
			-- Set active section in case it was closed from a submenu last time.
			activeSection = "Main"
			MESSAGEMAN:Broadcast("PMOpened", {Player=player})
		end
	end,
	
	InitCommand=function(self)
		self:xy((240/2)*posMod,_screen.cy - 126)
		--self:player(player)
	end,
	OnCommand = function(self)
		SCREENMAN:GetTopScreen():AddInputCallback( InputHandler )
		-- just in case.
		SCREENMAN:set_input_redirected(player, false)
		self:x((_screen.cx+20)*posMod)
	end,
	
	-- PlayerJoinedMessageCommand=function( self, param )
		-- if param.Player ~= player then return end
		-- self:player(player)
	-- end,
	PMOpenedMessageCommand=function( self, param )
		if param.Player ~= player then return end
		SCREENMAN:set_input_redirected(player, true)
		self:stoptweening():decelerate(0.4):x((240/2)*posMod)
		MESSAGEMAN:Broadcast("CheckFocus")
	end,
	PMClosedMessageCommand=function( self, param )
		if param.Player ~= player then return end
		SCREENMAN:set_input_redirected(player, false)
		self:stoptweening():decelerate(0.4):x((_screen.cx+20)*posMod)
	end,
	NavigationMessageCommand=function(self,param)
		if param.Player == player then self:GetChild("NavSnd"):play() end
		if param.Player ~= player or activeSection ~= "Main" then return end
		local Scroll = self:GetChild("MainScroll")
		
		local numItemsInd = Scroll:GetNumItems()-1
		-- floor it to prevent oddities caused by button mashing from occurring
		local location = math.floor(Scroll:GetCurrentItem() + param.Movement)
		-- Loop logic
		if location > numItemsInd then
			Scroll:SetDestinationItem(0)
		elseif location < 0 then
			Scroll:SetDestinationItem(numItemsInd)
		else
			Scroll:SetDestinationItem(location)
		end
		
		-- Only one sub-scroller on screen at a time.
		mainMenuFocus = menuSects[Scroll:GetDestinationItem()+1].Name
		--SM(mainMenuFocus)
		MESSAGEMAN:Broadcast("FocusUpdated", {Player=player})
	end,
	StartButtonMessageCommand=function(self,param)
		if param.Player ~= player or param.Sect ~= "Main" then return end
		local Scroll = self:GetChild("MainScroll")
		-- Handle menu choices with no submenus.
		if menuSects[Scroll:GetDestinationItem()+1].OnSelection then
			menuSects[Scroll:GetDestinationItem()+1].OnSelection()
		else
			activeSection = menuSects[Scroll:GetDestinationItem()+1].Name
		end
		MESSAGEMAN:Broadcast("OptionsListStart")
	end,
	
	
	-- Manually the menu sound.
	LoadActor( THEME:GetPathS("Common","value") )..{
		Name = "NavSnd"
	},
	
	-- mask :(
	Def.Quad{
		OnCommand=function(self)
			self:zoomto(paneWidth,96):vertalign(top):horizalign(player == PLAYER_1 and right or left):clearzbuffer(true):MaskSource()
		end
	},
	-- Actual backing.
	Def.Quad{
		OnCommand=function(self)
			self:zoomto(paneWidth,96):diffuse(Color.Black):vertalign(top):horizalign(player == PLAYER_1 and right or left)
		end
	},
	-- Header thingy.
	Def.Quad{
		OnCommand=function(self)
			self:zoomto(paneWidth,20):diffuse(PlayerColor(player)):vertalign(bottom):horizalign(player == PLAYER_1 and right or left)
		end
	},
	
	Def.BitmapText{
		Font = "Common Small",
		Text = THEME:GetString("ScreenSelectMusic", "OptionsListHeader"),
		OnCommand=function(self)
			self:horizalign(left):xy(player == PLAYER_1 and (-paneWidth)+6 or 6,-11):zoom(0.5):strokecolor(Color.Black)
		end
	},
	Def.BitmapText{
		Font = "Common Normal",
		Text = THEME:GetString("ScreenSelectMusic", "OptionsListClose"),
		OnCommand=function(self)
			self:horizalign(right):xy(player == PLAYER_1 and -6 or (paneWidth)-6,-11):zoom(0.5):strokecolor(Color.Black)
		end
	},
	
	LoadActor(THEME:GetPathG("_frame","3x3"), "glo", paneWidth-30, 96-10) .. {
		InitCommand=function(self)
			self:xy(((paneWidth/2))*(posMod),38):diffuseshift():effectcolor1(Color.White):effectcolor2(ColorBlend(Color.White,PlayerColor(player)))
		end
	},
	
	Def.ActorScroller{
		Name = 'MainScroll',
		NumItemsToDraw=100,
		OnCommand=function(self)
			self:SetSecondsPerItem(0.1):SetFastCatchup(true)
			self:xy(paneWidth/2*(posMod),10):ztestmode("ZTestMode_WriteOnFail")
		end,
		TransformFunction=function(self, offset, itemIndex, numItems)
			local badCentered = numItems > 5 and offset or itemIndex - ((numItems/2)-0.5)
			local spacing = numItems > 5 and 70 or 50
			self:x(spacing*badCentered)
			if math.abs(offset) < 1 then
				self:diffuse(Color.White)
			else
				self:diffuse(PlayerColor(player))
			end
		end,
		children = CreateMainMenuItems()
	}
}

-- Finally add the submenus.
for i, choic in ipairs(menuSects) do
	t[#t+1] = CreateSubmenu(i) .. {
		InitCommand=function(self)
			self:xy(-paneWidth/2*(posMod*-1),54):MaskDest():ztestmode("ZTestMode_WriteOnFail")
		end
	}
end

-- Somehow we're still not done!
-- Covers for both the main scroller and all the sub-scrollers.
-- Plus, little navigational indicators, can't go wrong with those
t[#t+1] = Def.ActorFrame{
	CheckFocusMessageCommand=function(self)
		self:GetChild("Top"):diffusealpha(activeSection == "Main" and 0 or 0.5)
		self:GetChild("Bot"):diffusealpha(activeSection ~= "Main" and 0 or 0.5)
		self:GetChild("Nav"):y(activeSection ~= "Main" and 52 or 13)
	end,
	
	Def.Quad{
		Name = "Top",
		OnCommand=function(self)
			self:vertalign(top):zoomto(paneWidth,26):horizalign(player == PLAYER_1 and right or left):diffuse(PlayerColor(player))
		end
	},
	Def.Quad{
		Name = "Bot",
		OnCommand=function(self)
			self:vertalign(bottom):y(96):zoomto(paneWidth,68):horizalign(player == PLAYER_1 and right or left):diffuse(PlayerColor(player))
		end
	},
	
	Def.ActorFrame{
		Name="Nav",
		Def.BitmapText {
			Font = "Common Normal",
			Text = "&MENULEFT;",
			OnCommand=function(self)
				self:horizalign(left):x(player == PLAYER_1 and -paneWidth+3 or 3):zoom(0.5):bounce():effectmagnitude(4,0,0):effectperiod(1)
			end
		},
		Def.BitmapText {
			Font = "Common Normal",
			Text = "&MENURIGHT;",
			OnCommand=function(self)
				self:horizalign(right):x(player == PLAYER_1 and -3 or paneWidth-3):zoom(0.5):bounce():effectmagnitude(-4,0,0):effectperiod(1)
			end
		}
	}
}

return t