
enum EMenuScreen
{
	// special events
	MS_Exit = -2,
	MS_Current,
	// actual screens
	MS_Main,
	MS_Weapons1,
	MS_Weapons2,
	MS_Weapons3
}

array<kStr> LoadoutTitles = { "T1", "T2", "RW", "Custom" };

class TurokPlusMenu : ScriptObject
{
	kActor@ self;

	// Unreal - removed Loadout item
	array<MenuItem@> MenuScreen_Main =
	{
		// Unreal - removed weapon options
		MenuItem_EnemyRespawn(),
		MenuItem_ItemDrops(),
		MenuItem_EnemySpeedup(),
		MenuItem(),
		MenuItem_PlayerFootsteps(),
		MenuItem_TauntSystem(),
		MenuItem(),
		MenuItem_Navigation( "Close", "Close the menu\n ", MS_Exit )
	};
	// Unreal - emptied weapon menus
	array<MenuItem@> MenuScreen_Weapons1;
	array<MenuItem@> MenuScreen_Weapons2;
	array<MenuItem@> MenuScreen_Weapons3;
	array< array<MenuItem@>@ > MenuScreens =
	{
		@MenuScreen_Main,
		@MenuScreen_Weapons1,
		@MenuScreen_Weapons2,
		@MenuScreen_Weapons3
	};
	array<kStr> ScreenTitles =
	{
		"Turok Plus v",
		"Starters, Utils",
		"Firearms",
		"Special Weapons"
	};

	uint CurrentScreen;
	uint SelectedItem;
	uint OldSelectedItem;

	kStr MenuString;
	uint16 OldButtons;
	float CloseCountdown = -0.2;

	TurokPlusMenu( kActor@ a )
	{
		@self = a;
		SelectedItem = MenuScreen_Main.length()-1;
		CurrentScreen = MS_Main;
		ScreenTitles[ MS_Main ] += TurokPlusVersion;
	}

	void OnSpawn()
	{
		// EXPERIMENTAL - TODO - fold into Turok+
		if ( PlayerScoped() ) CancelScope();
		Player.Lock();
		self.Flags() |= AF_ALWAYSACTIVE;
		OldButtons = Player.Buttons();
		CalcSize();
		RefreshMenu();
	}

	void CalcSize()
	{
		int left=0, right=0, total=0, space=StrLen(OptSpacer);

		for ( uint s=0; s<MenuScreens.length(); ++s )
		for ( uint i=0; i<MenuScreens[s].length(); ++i )
		{
			int L = MenuScreens[s][i].LeftWidth();
			int R = MenuScreens[s][i].RightWidth();
			int T = L + R;
			// 2-column
			if ( R > 0 )
			{
				left = Math::Max( left, L );
				right = Math::Max( right, R );
				T += space;
			}
			total = Math::Max( left+right+space, T );
		}

		for ( uint s=0; s<MenuScreens.length(); ++s )
		for ( uint i=0; i<MenuScreens[s].length(); ++i )
			MenuScreens[s][i].Pad( left, right, total );
	}

	array<MenuItem@>@ MenuItems()
	{
		return MenuScreens[ CurrentScreen ];
	}
	MenuItem@ GetItem( uint i )
	{
		return MenuScreens[ CurrentScreen ][ i ];
	}
	MenuItem@ CurrentItem()
	{
		return MenuScreens[ CurrentScreen ][ SelectedItem ];
	}

	void RefreshMenu()
	{
		MenuString = ScreenTitles[CurrentScreen];
		if ( CurrentScreen != MS_Main )
			MenuString = MenuString +" - "+ LoadoutTitles[ Opt::GetSetting(OPT_Loadout) ];
		MenuString = MenuString +"\n"+ Separator;
		for ( uint i=0; i<MenuItems().length(); ++i )
			MenuString += Selection( GetItem(i).Compose(), i == SelectedItem ) + "\n";
		MenuString += Separator;
		MenuString += CurrentItem().Description();
	}

	bool Pressed( uint buttons )
	{
		return Player.Buttons() & buttons != 0 && OldButtons & buttons == 0;
	}

	void OnTick()
	{
		// delay closing a bit so player will have time to release jump/attack before menu closes
		// hiding menu immediately makes the player feel frozen before being unlocked
		// hiding menu after the full delay makes the menu feel unresponsive
		// but hiding menu in the middle of the delay makes it much harder to notice
		if ( CloseCountdown > 0 )
		{
			if ( (CloseCountdown -= GAME_DELTA_TIME) <= 0 )
				Close();
			else if ( CloseCountdown <= 0.1 )
				Game.PrintHelp( "" );
			else
				Game.PrintHelp( MenuString );
			return;
		}

		// pause game
		ForcePickup( "PauseInvuln" );

		// delay reacting to input, so player will have time to register that the menu has opened
		// to avoid interpreting input intended for player movement as menu actions
		if ( CloseCountdown < 0 )
		{
			CloseCountdown = Math::OLDMin( CloseCountdown + GAME_DELTA_TIME, 0.0f );
			OldButtons = Player.Buttons();
			Game.PrintHelp( MenuString );
			return;
		}

		bool bUpdate = false;

		// if fire is pressed simultaneously with another button, only respond to fire
		// (prevents Unreal fire button from scrolling selection)
		if ( Pressed(BC_ATTACK|BC_JUMP) )
		{
			int menuAction = CurrentItem().Activate();
			if ( menuAction == MS_Exit )
			{
				CloseCountdown = 0.2f;
				return;
			}
			// changing screen
			else if ( menuAction != MS_Current )
			{
				if ( menuAction == MS_Main )
				{
					SelectedItem = OldSelectedItem;
					// save custom loadout when returning to main menu
					if ( Opt::GetSetting(OPT_Loadout) == LOADOUT_Custom )
						Opt::SaveWeaponSettings();
				}
				else
				{
					OldSelectedItem = SelectedItem;
					SelectedItem = 0;
				}
				CurrentScreen = menuAction;
			}
			bUpdate = true;
		}
		else
		{
			if ( Pressed(BC_BACKWARD|BC_MAPZOOMOUT) )
			{
				// skip decorative
				do
				{
					SelectedItem = (SelectedItem+1) % MenuItems().length();
				}
				while ( CurrentItem().bDecorative );
				PlayMenuSelection();
				bUpdate = true;
			}
			if ( Pressed(BC_FORWARD|BC_MAPZOOMIN) )
			{
				// skip decorative
				do
				{
					SelectedItem = (SelectedItem+MenuItems().length()-1) % MenuItems().length();
				}
				while ( CurrentItem().bDecorative );
				PlayMenuSelection();
				bUpdate = true;
			}
			if ( Pressed(BC_STRAFELEFT) )
			{
				CurrentItem().Left();
				bUpdate = true;
			}
			if ( Pressed(BC_STRAFERIGHT) )
			{
				CurrentItem().Right();
				bUpdate = true;
			}
		}
		OldButtons = Player.Buttons();

		if ( bUpdate )
			RefreshMenu();
		Game.PrintHelp( MenuString );
	}

	void Close()
	{
		Opt::SaveSettings();

		// commit to flare gun and add it to inventory
		if ( Bool( Opt::GetSetting(OPT_bFlareGun) ) && !Player.HasWeapon(TW_WEAPON_ALIENGUN) )
		{
			SetSmoke39Flag( SMOKE39_FLAREGUN, true );
			Player.GiveWeapon( TW_WEAPON_ALIENGUN, 15 );
			// simulate a whole minute of charging to make sure we start with a full charge
			RechargeFlareGun( 60*60 );
		}

		SetGameVarB( "Turok+SetupComplete", true );
		Game.PrintHelp( "" );
		Player.Unlock();
		self.Remove();
	}
}

class TurokPlusMenu_Auto : TurokPlusMenu
{
	int wait = 2;

	TurokPlusMenu_Auto( kActor@ a )
	{
		super(a);
	}

	void OnSpawn()
	{
	}

	// open menu one tick after player is unlocked
	// this 1-tick grace period fixes "Play Turok Backwards" mod startup
	void OnTick()
	{
		if ( wait > 0 )
		{
			if ( Player.Locked() )
			{
				wait = 2;
				return;
			}
			if ( --wait > 0 )
				return;
			// change "Back" to "Start Game"
			@MenuScreen_Main[ MenuScreen_Main.length()-1 ] = MenuItem_Navigation(
				"Start Game", "Start the game\nSettings can be changed at save points", MS_Exit );
			TurokPlusMenu::OnSpawn();
		}
		TurokPlusMenu::OnTick();
	}
}

//==============================================================================

kStr Separator = "----------------------------------------\n";
kStr OptSpacer = "  ";

kStr OnOff( bool bOn )
{
	return bOn? " ON" : "OFF";
}

kStr Selection( const kStr&in lineText, bool bSelected )
{
	if ( !bSelected ) return lineText;
	kStr str = "- ";
	return str + lineText +" -";
}

void PlayMenuSelection()
{
	Player.Actor().PlaySound( "sounds/shaders/ready_pistol.ksnd" );
}
void PlayMenuAction()
{
	Player.Actor().PlaySound( "sounds/shaders/auto_shotgun_shot.ksnd" );
}

int StrLen( kStr&in str )
{
	return (str+"</STR>").IndexOf("</STR>");
}

//==============================================================================
//
//    Menu Components
//

// generic menu item
class MenuItem
{
	kStr Name;
	bool bDecorative;

	MenuItem()
	{
		bDecorative = true;
	}
	MenuItem( kStr&in name )
	{
		Name = name;
		bDecorative = true;
	}

	int LeftWidth()
	{
		return StrLen(Name);
	}
	int RightWidth()
	{
		return 0;
	}
	void Pad( int left, int right, int total )
	{
		int len = LeftWidth() + RightWidth();
		while ( len++ < total )
			Name += " ";
	}

	// menu item text
	kStr Compose()
	{
		return Name;
	}
	// footer help text
	kStr Description()
	{
		return "";
	}

	int Activate() { return MS_Current; }
	void Left() {}
	void Right() {}
}

class MenuItem_Action : MenuItem
{
	kStr DescString;

	MenuItem_Action()
	{
		bDecorative = false;
	}
	MenuItem_Action( kStr&in name, kStr&in desc )
	{
		Name = name;
		DescString = desc;
		bDecorative = false;
	}

	kStr Description()
	{
		return DescString;
	}
}

class MenuItemOption : MenuItem_Action
{
	int Setting;

	MenuItemOption() {}
	MenuItemOption( int opt, kStr&in n, kStr&in desc )
	{
		super( n, desc );
		Setting = opt;
	}

	void Left() { Activate(); }
	void Right() { Activate(); }
}

// on/off menu item
class MenuItemToggle : MenuItemOption
{
	MenuItemToggle() {}
	MenuItemToggle( int opt, kStr&in n, kStr&in desc )
	{
		super( opt, n, desc );
	}

	int RightWidth()
	{
		return Math::Max( StrLen( OnOff(true) ), StrLen( OnOff(false) ) );
	}

	kStr Compose()
	{
		return Name + OnOff( Bool( Opt::GetSetting(Setting) ) );
	}

	int Activate()
	{
		Opt::ChangeSetting( Setting, Int( Opt::GetSetting(Setting) == 0 ) );
		PlayMenuAction();
		return MS_Current;
	}
}

// multi-value menu item
class MenuItemMulti : MenuItemOption
{
	array<kStr> DetailStrings, Values;
	bool bDetailsOnly; // omit DescString

	MenuItemMulti() {}
	MenuItemMulti( int opt, kStr&in n, kStr&in desc, array<kStr>&in details, array<kStr>&in vals )
	{
		super( opt, n, desc );
		DetailStrings = details;
		Values = vals;
	}
	MenuItemMulti( int opt, kStr&in n, array<kStr>&in details, array<kStr>&in vals )
	{
		super( opt, n, "" );
		Setting = opt;
		DetailStrings = details;
		Values = vals;
		bDetailsOnly = true;
	}

	int RightWidth()
	{
		int len = 0;
		for ( uint i=0; i<Values.length(); ++i )
			len = Math::Max( len, StrLen( Values[i] ) );
		return len;
	}
	void Pad( int left, int right, int total )
	{
		// add value strings to details before we mess them up with padding
		for ( uint i=0; i<Values.length(); ++i )
			DetailStrings[i] = Values[i] +" - "+ DetailStrings[i];

		int L = LeftWidth() + right;
		while ( L++ < total )
			Name += " ";
		for ( uint i=0; i<Values.length(); ++i )
		{
			int R = StrLen( Values[i] );
			kStr sp = ' ';
			while ( R++ < right )
				Values[i] = sp + Values[i];
		}
	}

	kStr Compose()
	{
		return Name + Values[ Opt::GetSetting(Setting) ];
	}
	kStr Description()
	{
		if ( bDetailsOnly )
			return DetailStrings[ Opt::GetSetting(Setting) ];
		return DescString +"\n"+ DetailStrings[ Opt::GetSetting(Setting) ];
	}

	void ChangeSetting( int newSetting )
	{
		Opt::ChangeSetting( Setting, newSetting );
	}

	void Left()
	{
		Opt::ChangeSetting( Setting, (Opt::GetSetting(Setting)+Values.length()-1) % Values.length() );
		PlayMenuAction();
	}
	void Right()
	{
		Opt::ChangeSetting( Setting, (Opt::GetSetting(Setting)+1) % Values.length() );
		PlayMenuAction();
	}

	int Activate()
	{
		Right();
		return MS_Current;
	}
}

class MenuItem_Navigation : MenuItem_Action
{
	int Target;

	MenuItem_Navigation( kStr&in name, kStr&in desc, int target )
	{
		Name = name;
		DescString = desc;
		Target = target;
	}

	bool IsReturnAction()
	{
		return Target == MS_Main || Target == MS_Exit;
	}

	int RightWidth()
	{
		return IsReturnAction() ? 0 : 3; // nothing or "..."
	}
	void Pad( int left, int right, int total )
	{
		// lef-align submenus, center back/exit
		if ( !IsReturnAction() )
			MenuItem_Action::Pad( left, right, total );
	}

	kStr Compose()
	{
		if ( IsReturnAction() )
			return Name;
		return Name + "...";
	}

	int Activate()
	{
		PlayMenuAction();
		return Target;
	}
}

// Unreal - removed weapon settings

//==============================================================================
//
//    General Settings
//

class MenuItem_EnemyRespawn : MenuItemMulti
{
	MenuItem_EnemyRespawn()
	{
		super( OPT_EnemyRespawn, "Enemy Respawn",  "Which enemies can respawn",
			array<kStr> = { "Increase with difficulty", "No enemies respawn", "Only weak enemies respawn", "Up to mid-tier enemies respawn", "No restrictions over base game" },
			array<kStr> = { "Auto", "None", "Weak", "Mid", "All" } );
	}
}

class MenuItem_ItemDrops: MenuItemMulti
{
	MenuItem_ItemDrops()
	{
		super( OPT_ItemDrops, "Item Drops",
			array<kStr> = { "original behavior\ndisables all drops on hard and hardcore", "enemies drop both health and ammo\n ", "enemies drop only ammo\n ", "enemies drop only health\n ", "disable all item drops\n " },
			array<kStr> = { "Auto", "All", "Ammo", "Health", "None" } );
	}
}

class MenuItem_EnemySpeedup : MenuItemMulti
{
	MenuItem_EnemySpeedup()
	{
		super( OPT_bEnemySpeedup, "Enemy Speed", "How fast enemies move and attack",
			array<kStr> = { "Same as base game", "Increased over base game" },
			array<kStr> = { "Normal", "Faster" } );
	}
}

class MenuItem_PlayerFootsteps : MenuItemToggle
{
	MenuItem_PlayerFootsteps()
	{
		super( OPT_bPlayerFootsteps, "Player Footsteps", "Turok's footsteps make noise\n " );
	}
}

class MenuItem_TauntSystem : MenuItemMulti
{
	MenuItem_TauntSystem()
	{
		super( OPT_TauntFreq, "Taunt Frequency", "How often Turok speaks",
			array<kStr> = { "Do not comment on anything", "Special events only", "All events, on a cooldown", "Always comment on everything" },
			array<kStr> = { "Never", "Low", "Med", "High" } );
	}
}
