context: #region Copyright & License Information
  /*
   * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.Graphics;
  using OpenRA.Mods.Common.Scripting;
  using OpenRA.Mods.Common.Traits;
  using OpenRA.Widgets;
  
  namespace OpenRA.Mods.Common.Widgets.Logic
  {
  	public class IngameMenuLogic : ChromeLogic
  	{
  		readonly Widget menu;
  		readonly Widget buttonContainer;
  		readonly ButtonWidget buttonTemplate;
  		readonly int2 buttonStride;
  		readonly List<ButtonWidget> buttons = new List<ButtonWidget>();
  
  		readonly ModData modData;
  		readonly Action onExit;
  		readonly World world;
  		readonly WorldRenderer worldRenderer;
  		readonly MenuPaletteEffect mpe;
  		readonly bool isSinglePlayer;
  		bool hasError;
  		bool leaving;
  		bool hideMenu;
  
  		[ObjectCreator.UseCtor]
  		public IngameMenuLogic(Widget widget, ModData modData, World world, Action onExit, WorldRenderer worldRenderer,
  			IngameInfoPanel activePanel, Dictionary<string, MiniYaml> logicArgs)
  		{
  			this.modData = modData;
  			this.world = world;
  			this.worldRenderer = worldRenderer;
  			this.onExit = onExit;
  
  			var buttonHandlers = new Dictionary<string, Action>
  			{
  				{ "ABORT_MISSION", CreateAbortMissionButton },
  				{ "RESTART", CreateRestartButton },
  				{ "SURRENDER", CreateSurrenderButton },
  				{ "LOAD_GAME", CreateLoadGameButton },
  				{ "SAVE_GAME", CreateSaveGameButton },
  				{ "MUSIC", CreateMusicButton },
  				{ "SETTINGS", CreateSettingsButton },
  				{ "RESUME", CreateResumeButton },
  				{ "SAVE_MAP", CreateSaveMapButton },
  				{ "EXIT_EDITOR", CreateExitEditorButton }
  			};
  
  			isSinglePlayer = !world.LobbyInfo.GlobalSettings.Dedicated && world.LobbyInfo.NonBotClients.Count() == 1;
  
  			menu = widget.Get("INGAME_MENU");
  			mpe = world.WorldActor.TraitOrDefault<MenuPaletteEffect>();
  			mpe?.Fade(mpe.Info.MenuEffect);
  
  			menu.Get<LabelWidget>("VERSION_LABEL").Text = modData.Manifest.Metadata.Version;
  
  			buttonContainer = menu.Get("MENU_BUTTONS");
  			buttonTemplate = buttonContainer.Get<ButtonWidget>("BUTTON_TEMPLATE");
  			buttonContainer.RemoveChild(buttonTemplate);
  			buttonContainer.IsVisible = () => !hideMenu;
  
  			if (logicArgs.TryGetValue("ButtonStride", out var buttonStrideNode))
  				buttonStride = FieldLoader.GetValue<int2>("ButtonStride", buttonStrideNode.Value);
  
  			var scriptContext = world.WorldActor.TraitOrDefault<LuaScript>();
  			hasError = scriptContext != null && scriptContext.FatalErrorOccurred;
  
  			if (logicArgs.TryGetValue("Buttons", out var buttonsNode))
  			{
  				var buttonIds = FieldLoader.GetValue<string[]>("Buttons", buttonsNode.Value);
  				foreach (var button in buttonIds)
  					if (buttonHandlers.TryGetValue(button, out var createHandler))
  						createHandler();
  			}
  
  			// Recenter the button container
  			if (buttons.Count > 0)
  			{
  				var expand = (buttons.Count - 1) * buttonStride;
  				buttonContainer.Bounds.X -= expand.X / 2;
  				buttonContainer.Bounds.Y -= expand.Y / 2;
  				buttonContainer.Bounds.Width += expand.X;
  				buttonContainer.Bounds.Height += expand.Y;
  			}
  
  			var panelRoot = widget.GetOrNull("PANEL_ROOT");
  			if (panelRoot != null && world.Type != WorldType.Editor)
  			{
  				Action<bool> requestHideMenu = h => hideMenu = h;
  				var gameInfoPanel = Game.LoadWidget(world, "GAME_INFO_PANEL", panelRoot, new WidgetArgs()
  				{
  					{ "activePanel", activePanel },
  					{ "hideMenu", requestHideMenu }
  				});
  
  				gameInfoPanel.IsVisible = () => !hideMenu;
  			}
  		}
  
  		void OnQuit()
  		{
  			// TODO: Create a mechanism to do things like this cleaner. Also needed for scripted missions
  			if (world.Type == WorldType.Regular)
  			{
  				var moi = world.Map.Rules.Actors["player"].TraitInfoOrDefault<MissionObjectivesInfo>();
  				if (moi != null)
  				{
  					var faction = world.LocalPlayer == null ? null : world.LocalPlayer.Faction.InternalName;
  					Game.Sound.PlayNotification(world.Map.Rules, null, "Speech", moi.LeaveNotification, faction);
  				}
  			}
  
  			leaving = true;
  
  			var iop = world.WorldActor.TraitsImplementing<IObjectivesPanel>().FirstOrDefault();
  			var exitDelay = iop != null ? iop.ExitDelay : 0;
  			if (mpe != null)
  			{
  				Game.RunAfterDelay(exitDelay, () =>
  				{
  					if (Game.IsCurrentWorld(world))
  						mpe.Fade(MenuPaletteEffect.EffectType.Black);
  				});
  				exitDelay += 40 * mpe.Info.FadeLength;
  			}
  
  			Game.RunAfterDelay(exitDelay, () =>
  			{
  				if (!Game.IsCurrentWorld(world))
  					return;
  
  				Game.Disconnect();
  				Ui.ResetAll();
  				Game.LoadShellMap();
  			});
  		}
  
  		void ShowMenu()
  		{
  			hideMenu = false;
  		}
  
  		void CloseMenu()
  		{
  			Ui.CloseWindow();
  			mpe?.Fade(MenuPaletteEffect.EffectType.None);
  			onExit();
  			Ui.ResetTooltips();
  		}
  
  		ButtonWidget AddButton(string id, string text)
  		{
  			var button = buttonTemplate.Clone() as ButtonWidget;
  			var lastButton = buttons.LastOrDefault();
  			if (lastButton != null)
  			{
  				button.Bounds.X = lastButton.Bounds.X + buttonStride.X;
  				button.Bounds.Y = lastButton.Bounds.Y + buttonStride.Y;
  			}
  
  			button.Id = id;
  			button.IsDisabled = () => leaving;
  			button.GetText = () => text;
  			buttonContainer.AddChild(button);
  			buttons.Add(button);
  
  			return button;
  		}
  
  		void CreateAbortMissionButton()
  		{
  			if (world.Type != WorldType.Regular)
  				return;
  
  			var button = AddButton("ABORT_MISSION", world.IsGameOver ? "Leave" : "Abort Mission");
  
  			button.OnClick = () =>
  			{
  				hideMenu = true;
  
  				ConfirmationDialogs.ButtonPrompt(
  					title: "Leave Mission",
  					text: "Leave this game and return to the menu?",
  					onConfirm: OnQuit,
  					onCancel: ShowMenu,
  					confirmText: "Leave",
  					cancelText: "Stay");
  			};
  		}
  
  		void CreateRestartButton()
  		{
  			if (world.Type != WorldType.Regular || !isSinglePlayer)
  				return;
  
  			var iop = world.WorldActor.TraitsImplementing<IObjectivesPanel>().FirstOrDefault();
  			var exitDelay = iop != null ? iop.ExitDelay : 0;
  
  			Action onRestart = () =>
  			{
  				Ui.CloseWindow();
  				if (mpe != null)
  				{
  					if (Game.IsCurrentWorld(world))
  						mpe.Fade(MenuPaletteEffect.EffectType.Black);
  					exitDelay += 40 * mpe.Info.FadeLength;
  				}
  
  				Game.RunAfterDelay(exitDelay, Game.RestartGame);
  			};
  
  			var button = AddButton("RESTART", "Restart");
  			button.IsDisabled = () => hasError || leaving;
  			button.OnClick = () =>
  			{
  				hideMenu = true;
  				ConfirmationDialogs.ButtonPrompt(
  					title: "Restart",
  					text: "Are you sure you want to restart?",
  					onConfirm: onRestart,
  					onCancel: ShowMenu,
  					confirmText: "Restart",
  					cancelText: "Stay");
  			};
  		}
  
  		void CreateSurrenderButton()
  		{
  			if (world.Type != WorldType.Regular || isSinglePlayer || world.LocalPlayer == null)
  				return;
  
  
gt: 			Action onSurrender = () =>