//#define TEST_REMOTE_CONTENT

using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Tutorials;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Barotrauma.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.Steam;

namespace Barotrauma
{
    sealed class MainMenuScreen : Screen
    {
        public static HashSet<Identifier> DismissedNotifications = new HashSet<Identifier>();

        private enum Tab
        {
            NewGame = 0,
            LoadGame = 1,
            HostServer = 2,
            Settings = 3,
            Tutorials = 4,
            JoinServer = 5,
            CharacterEditor = 6,
            SubmarineEditor = 7,
            Mods = 8,
            Credits = 9,
            Empty = 10
        }

        private readonly GUIComponent buttonsParent;

        private readonly Dictionary<Tab, GUIFrame> menuTabs;

        private SinglePlayerCampaignSetupUI campaignSetupUI;

        private GUITextBox serverNameBox, passwordBox, maxPlayersBox;
        private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
        private GUIDropDown languageDropdown, serverExecutableDropdown;
        private readonly GUIButton joinServerButton, hostServerButton;

        private readonly GUIFrame modsButtonContainer;
        private readonly GUIButton modsButton, modUpdatesButton;
        private (DateTime WhenToRefresh, int Count) modUpdateStatus = (DateTime.Now, 0);
        private static readonly TimeSpan ModUpdateInterval = TimeSpan.FromSeconds(60.0f);
        
        private readonly GameMain game;

        private GUIImage playstyleBanner;
        private GUITextBlock playstyleDescription;

        private static string RemoteContentUrl => GameSettings.CurrentConfig.RemoteMainMenuContentUrl;

        private readonly GUIComponent remoteContentContainer;
        private XDocument remoteContentDoc;

        private Tab selectedTab = Tab.Empty;

        private Sprite backgroundSprite;

        private readonly GUIComponent titleText;

        private readonly CreditsPlayer creditsPlayer;

        public static readonly Queue<ulong> WorkshopItemsToUpdate = new Queue<ulong>();

        private GUIImage tutorialBanner;
        private GUITextBlock tutorialHeader, tutorialDescription;
        private GUIListBox tutorialList;

        private readonly GUITextBlock gameAnalyticsStatusText;

        private readonly GUILayoutGroup leftTextFooterLayout;
        private readonly GUILayoutGroup rightTextFooterLayout;

        private GUIComponent versionMismatchWarning;

        #region Creation
        public MainMenuScreen(GameMain game) : base()
        {
            leftTextFooterLayout = createTextFooter();
            rightTextFooterLayout = createTextFooter();

            gameAnalyticsStatusText = createLeftText(TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.Consent.Unknown}"));
            createLeftText("Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");

            var privacyPolicyText = createRightText(TextManager.Get("privacypolicy").Fallback("Privacy policy"));
            (Rectangle Rect, bool MouseOn) getPrivacyPolicyHoverRect()
            {
                var textSize = privacyPolicyText.Font.MeasureString(privacyPolicyText.Text);
                var bottomRight = privacyPolicyText.Rect.Location.ToVector2()
                              + privacyPolicyText.TextPos
                              + privacyPolicyText.TextOffset;
                var rect = new Rectangle((bottomRight - textSize).ToPoint(), textSize.ToPoint());
                bool mouseOn = rect.Contains(PlayerInput.LatestMousePosition) && GUI.IsMouseOn(privacyPolicyText);
                return (rect, mouseOn);
            }
            new GUICustomComponent(new RectTransform(Vector2.One, privacyPolicyText.RectTransform),
                onUpdate: (dt, component) =>
                {
                    var (_, mouseOn) = getPrivacyPolicyHoverRect();
                    if (mouseOn && PlayerInput.PrimaryMouseButtonClicked())
                    {
                        GameMain.ShowOpenUriPrompt("https://privacypolicy.daedalic.com");
                    }
                },
                onDraw: (sb, component) =>
                {
                    var (rect, mouseOn) = getPrivacyPolicyHoverRect();
                    Color color = mouseOn ? Color.White : Color.White * 0.7f;
                    privacyPolicyText.TextColor = color;
                    GUI.DrawLine(sb, new Vector2(rect.Left, rect.Bottom), new Vector2(rect.Right, rect.Bottom), color);
                });
            
            createRightText("© " + DateTime.Now.Year + " Undertow Games & FakeFish. All rights reserved.");
            createRightText("© " + DateTime.Now.Year + " Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved.");

            GUILayoutGroup createTextFooter()
                => new GUILayoutGroup(new RectTransform((1.0f, 0.06f), Frame.RectTransform, Anchor.BottomCenter))
                {
                    ChildAnchor = Anchor.BottomLeft
                };

            GUITextBlock createTextInFooter(GUILayoutGroup footer, LocalizedString str, Alignment textAlignment)
            {
                var textBlock = new GUITextBlock(
                    rectT: new RectTransform((1.0f, 0.3f), footer.RectTransform),
                    text: str,
                    textAlignment: textAlignment,
                    font: GUIStyle.SmallFont,
                    textColor: Color.White * 0.7f);
                textBlock.RectTransform.SetAsFirstChild();
                return textBlock;
            }

            GUITextBlock createLeftText(LocalizedString str)
                => createTextInFooter(leftTextFooterLayout, str, Alignment.BottomLeft);
            GUITextBlock createRightText(LocalizedString str)
                => createTextInFooter(rightTextFooterLayout, str, Alignment.BottomRight);

            GameMain.Instance.ResolutionChanged += () =>
            {
                SetMenuTabPositioning();
                CreateHostServerFields();
                bool prevMenuOpen = GUI.SettingsMenuOpen;
                SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
                GUI.SettingsMenuOpen = prevMenuOpen;
                if (remoteContentDoc?.Root != null)
                {
                    remoteContentContainer.ClearChildren();
                    try
                    {
                        foreach (var subElement in remoteContentDoc.Root.Elements())
                        {
                            GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
                        }
                    }
                    catch (Exception e)
                    {
#if DEBUG
                        DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
#endif
                        GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentParse:Exception", GameAnalyticsManager.ErrorSeverity.Error,
                            "Reading received remote main menu content failed. " + e.Message);
                    }
                }
            };

            versionMismatchWarning = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.065f), Frame.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(15)) }, style: "InnerFrame", color: GUIStyle.Red)
            {
                IgnoreLayoutGroups = true,
                Visible = false
            };
            var versionMismatchContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), versionMismatchWarning.RectTransform, Anchor.Center), isHorizontal: true)
            {
                RelativeSpacing = 0.05f,
            };
            new GUIImage(new RectTransform(new Vector2(1.0f), versionMismatchContent.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUINotificationButton")
            {
                Color = GUIStyle.Orange
            };
            new GUITextBlock(new RectTransform(new Vector2(0.85f, 1.0f), versionMismatchContent.RectTransform),
                TextManager.GetWithVariables("versionmismatchwarning",
                    ("[gameversion]", GameMain.Version.ToString()),
                    ("[contentversion]", ContentPackageManager.VanillaCorePackage.GameVersion.ToString())),
                wrap: true)
            {
                TextColor = GUIStyle.Orange
            };

            new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
            { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) },
                style: "TitleText")
            {
                Color = Color.Black * 0.5f,
                CanBeFocused = false
            };
            titleText = new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
            { RelativeOffset = new Vector2(0.08f, 0.05f) },
                style: "TitleText");

            buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft)
            {
                AbsoluteOffset = new Point(50, 0)
            })
            {
                Stretch = true,
                RelativeSpacing = 0.02f
            };

            remoteContentContainer = new GUIFrame(new RectTransform(Vector2.One, parent: Frame.RectTransform), style: null)
            {
                CanBeFocused = false
            };

#if TEST_REMOTE_CONTENT

            var doc = XMLExtensions.TryLoadXml("Content/UI/MenuContent.xml");
            if (doc?.Root != null)
            {
                foreach (var subElement in doc?.Root.Elements())
                {
                    GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
                }
            }   
#else
            SpamServerFilters.RequestGlobalSpamFilter();
            FetchRemoteContent();
#endif

            float labelHeight = 0.18f;


            // === CAMPAIGN
            var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true);
       
            new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon")
            {
                CanBeFocused = false
            };

            //spacing
            new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), campaignHolder.RectTransform), style: null);

            var campaignNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: campaignHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });

            new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), campaignNavigation.RectTransform),
                TextManager.Get("CampaignLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };

            var campaignButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: campaignNavigation.RectTransform), style: "MainMenuGUIFrame");

            var campaignList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: campaignButtons.RectTransform))
            {
                Stretch = false,
                RelativeSpacing = 0.035f
            };

            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("TutorialButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.Tutorials,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };

            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("LoadGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.LoadGame,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };

            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("NewGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.NewGame,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };

            // === MULTIPLAYER
            var multiplayerHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, isHorizontal: true);

            new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), multiplayerHolder.RectTransform), "MainMenuMultiplayerIcon")
            {
                CanBeFocused = false
            };

            //spacing
            new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), multiplayerHolder.RectTransform), style: null);

            var multiplayerNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: multiplayerHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });

            new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), multiplayerNavigation.RectTransform),
                TextManager.Get("MultiplayerLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };

            var multiplayerButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: multiplayerNavigation.RectTransform), style: "MainMenuGUIFrame");

            var multiplayerList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: multiplayerButtons.RectTransform))
            {
                Stretch = false,
                RelativeSpacing = 0.035f
            };

            joinServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("JoinServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.JoinServer,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };
            hostServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.HostServer,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };

            // === CUSTOMIZE
            var customizeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.15f, 0.0f) }, isHorizontal: true);

            new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), customizeHolder.RectTransform), "MainMenuCustomizeIcon")
            {
                CanBeFocused = false
            };

            //spacing
            new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), customizeHolder.RectTransform), style: null);

            var customizeNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: customizeHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });

            new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), customizeNavigation.RectTransform),
                TextManager.Get("CustomizeLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };

            var customizeButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: customizeNavigation.RectTransform), style: "MainMenuGUIFrame");

            var customizeList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: customizeButtons.RectTransform))
            {
                Stretch = false,
                RelativeSpacing = 0.035f
            };

            modsButtonContainer = new GUIFrame(new RectTransform(Vector2.One, customizeList.RectTransform),
                style: null);
            
            modsButton = new GUIButton(new RectTransform(Vector2.One, modsButtonContainer.RectTransform),
                TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                Enabled = true,
                UserData = Tab.Mods,
                OnClicked = SelectTab
            };

            modUpdatesButton = new GUIButton(new RectTransform(Vector2.One * 0.95f, modsButtonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
                style: "GUIUpdateButton")
            {
                ToolTip = TextManager.Get("ModUpdatesAvailable"),
                OnClicked = (_, _) =>
                {
                    BulkDownloader.PrepareUpdates();
                    return false;
                },
                Visible = false
            };

            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.SubmarineEditor,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };

            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("CharacterEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.CharacterEditor,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    return true;
                }
            };

            // === OPTION
            var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), parent: buttonsParent.RectTransform), isHorizontal: true);

            new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon")
            {
                CanBeFocused = false
            };

            //spacing
            new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null);

            var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) });

            var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.25f), parent: optionButtons.RectTransform))
            {
                Stretch = false,
                RelativeSpacing = 0.035f
            };

            var settingsButtonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), style: null);

            new GUIButton(new RectTransform(Vector2.One, settingsButtonContainer.RectTransform), TextManager.Get("SettingsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.Settings,
                OnClicked = SelectTab
            };

            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("EditorDisclaimerWikiLink"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                OnClicked = (button, userData) =>
                {
                    string url = TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value;
                    GameMain.ShowOpenUriPrompt(url, promptExtensionTag: "wikinotice");
                    return true;
                }
            };
            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                UserData = Tab.Credits,
                OnClicked = SelectTab
            };
            new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("QuitButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
            {
                ForceUpperCase = ForceUpperCase.Yes,
                OnClicked = QuitClicked
            };

            //debug button for quickly starting a new round
#if DEBUG
            new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 80) },
                "Quickstart (dev)", style: "GUIButtonLarge", color: GUIStyle.Red)
            {
                IgnoreLayoutGroups = true,
                UserData = Tab.Empty,
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);

                    QuickStart();

                    return true;
                }
            };
            new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 130) },
                "Profiling", style: "GUIButtonLarge", color: GUIStyle.Red)
            {
                IgnoreLayoutGroups = true,
                UserData = Tab.Empty,
                ToolTip = "Enables performance indicators and starts the game with a fixed sub, crew and level to make it easier to compare the performance between sessions.",
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);

                    QuickStart(fixedSeed: true);
                    GameMain.ShowPerf = true;
                    GameMain.ShowFPS = true;

                    return true;
                }
            };
            new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 180) },
                "Join Localhost", style: "GUIButtonLarge", color: GUIStyle.Red)
            {
                IgnoreLayoutGroups = true,
                UserData = Tab.Empty,
                ToolTip = "Connects to a locally hosted dedicated server, assuming default port.",
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);

                    GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
                                                     new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());

                    return true;
                }
            };
            
            new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 230) },
                "Local MP Quickstart", style: "GUIButtonLarge", color: GUIStyle.Red)
            {
                IgnoreLayoutGroups = true,
                UserData = Tab.Empty,
                ToolTip = "Starts a server and another client and connects both to localhost, using names 'client1' and 'client2'.",
                OnClicked = (tb, userdata) =>
                {
                    SelectTab(tb, userdata);
                    
                    DebugConsole.StartLocalMPSession(numClients: 2);

                    return true;
                }
            };
#endif
            new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(40, 50) },
    $"Open LuaCs Settings", style: "MainMenuGUIButton", color: GUIStyle.Red)
            {
                IgnoreLayoutGroups = true,
                OnClicked = (tb, userdata) =>
                {
                    LuaCsSettingsMenu.Open(Frame.RectTransform);
                    return true;
                }
            };

            string version = File.Exists(LuaCsSetup.VersionFile) ? File.ReadAllText(LuaCsSetup.VersionFile) : "Github";

            new GUITextBlock(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, $"Using LuaCsForBarotrauma revision {AssemblyInfo.GitRevision} version {version}", Color.Red)
            {
                IgnoreLayoutGroups = false
            };

            var minButtonSize = new Point(120, 20);
            var maxButtonSize = new Point(480, 80);

            var relativeSize = new Vector2(0.6f, 0.65f);
            var minSize = new Point(600, 400);
            var maxSize = new Point(2000, 1500);
            var anchor = Anchor.Center;
            var pivot = Pivot.Center;
            Vector2 relativeOffset = new Vector2(0.05f, 0.0f);

            menuTabs = new Dictionary<Tab, GUIFrame>
            {
                [Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset },
                style: null)
                {
                    CanBeFocused = false
                },
                [Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }),
                [Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
            };

            CreateCampaignSetupUI();

            var hostServerScale = new Vector2(0.7f, 1.2f);
            menuTabs[Tab.HostServer] = new GUIFrame(new RectTransform(
                    Vector2.Multiply(relativeSize, hostServerScale), Frame.RectTransform, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))
            { RelativeOffset = relativeOffset });

            CreateHostServerFields();

            //----------------------------------------------------------------------

            menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset });
            CreateTutorialTab();

            this.game = game;

            menuTabs[Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, Frame.RectTransform, Anchor.Center), style: null)
            {
                CanBeFocused = false
            };
            var blockerFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
            {
                CanBeFocused = false
            };
            blockerFrame.RectTransform.RelativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);

            var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
            creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
            creditsPlayer.CloseButton.OnClicked = (btn, userdata) =>
            {
                SelectTab(Tab.Empty);
                return true;
            };

            SetMenuTabPositioning();
            SelectTab(Tab.Empty);
        }

        public static void AddDismissedNotification(Identifier id)
        {
            DismissedNotifications.Add(id);
            GameSettings.SaveCurrentConfig();
        }

        private void SetMenuTabPositioning()
        {
            foreach (GUIFrame menuTab in menuTabs.Values)
            {
                var anchor = GUI.IsUltrawide ? Anchor.Center : Anchor.CenterRight;
                var pivot = GUI.IsUltrawide ? Pivot.Center : Pivot.CenterRight;
                Vector2 relativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
                menuTab.RectTransform.SetPosition(anchor, pivot);
                menuTab.RectTransform.RelativeOffset = relativeOffset;
            }
        }

        private void CreateTutorialTab()
        {
            var tutorialInnerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.Tutorials].RectTransform, Anchor.Center), style: "InnerFrame");
            var tutorialContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tutorialInnerFrame.RectTransform, Anchor.Center), isHorizontal: true) { RelativeSpacing = 0.02f, Stretch = true };

            tutorialList = new GUIListBox(new RectTransform(new Vector2(0.4f, 1.0f), tutorialContent.RectTransform))
            {
                PlaySoundOnSelect = true,
                OnSelected = (component, obj) =>
                {
                    SelectTutorial(obj as Tutorial);
                    return true;
                }
            };
            var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
            var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
            tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent);

            var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
            var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
            {
                AbsoluteSpacing = GUI.IntScale(10)
            };

            tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont);
            tutorialDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, wrap: true);

            var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton")) 
            { 
                IgnoreLayoutGroups = true,
                OnClicked = (component, obj) =>
                {
                    (tutorialList.SelectedData as Tutorial)?.Start();
                    return true;
                }
            };

            Tutorial firstTutorial = null;
            foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order))
            {
                var tutorial = new Tutorial(tutorialPrefab);
                firstTutorial ??= tutorial;
                var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), tutorialList.Content.RectTransform), tutorial.DisplayName)
                {
                    Padding = new Vector4(30.0f * GUI.Scale, 0,0,0),
                    UserData = tutorial
                };
                tutorialText.RectTransform.MinSize = new Point(0, (int)(tutorialText.TextSize.Y * 2));
            }
            GUITextBlock.AutoScaleAndNormalize(tutorialList.Content.Children.Select(c => c as GUITextBlock));
            tutorialList.Select(firstTutorial);
        }

        private void SelectTutorial(Tutorial tutorial)
        {
            tutorialHeader.Text = tutorial.DisplayName;
            tutorialHeader.CalculateHeightFromText();
            tutorialDescription.Text = tutorial.Description;
            tutorialDescription.CalculateHeightFromText();
            (tutorialDescription.Parent as GUILayoutGroup)?.Recalculate();
            tutorial.TutorialPrefab.Banner?.EnsureLazyLoaded();
            tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner;
            tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White;
        }

        public static void UpdateInstanceTutorialButtons()
        {
            if (GameMain.MainMenuScreen is not MainMenuScreen menuScreen) { return; }
            menuScreen.tutorialList.ClearChildren();
            menuScreen.CreateTutorialTab();
        }

        #endregion

        #region Selection
        public override void Select()
        {
            GameMain.LuaCs.Stop();

            ResetModUpdateButton();

            if (WorkshopItemsToUpdate.Any())
            {
                while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId))
                {
                    SteamManager.Workshop.OnItemDownloadComplete(workshopId, forceInstall: true);
                }
            }
            
            GUI.PreventPauseMenuToggle = false;

            base.Select();

            if (GameMain.Client != null)
            {
                GameMain.Client.Quit();
                GameMain.Client = null;
            }

            GameMain.SubEditorScreen?.ClearBackedUpSubInfo();
            Submarine.Unload();

            versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion;

            ResetButtonStates(null);

            Eos.EosAccount.ExecuteAfterLogin(AchievementManager.SyncBetweenPlatforms);
        }

        public override void Deselect()
        {
            base.Deselect();
            SelectTab(null, 0);
        }

        private bool SelectTab(GUIButton button, object obj)
        {
            titleText.Visible = true;
            if (obj is Tab tab)
            {
                SelectTab(tab);
            }
            else
            {
                SelectTab(Tab.Empty);
            }
            return true;
        }

        private bool SelectTab(Tab tab)
        {
            titleText.Visible = true;
            SettingsMenu.Instance?.Close();
            #warning TODO: reimplement settings confirmation dialog

            switch (tab)
            {
                case Tab.NewGame:
                    if (GameSettings.CurrentConfig.TutorialSkipWarning)
                    {
                        selectedTab = Tab.Empty;
                        ShowTutorialSkipWarning(Tab.NewGame);
                        return true;
                    }
                    campaignSetupUI.RandomizeCrew();
                    campaignSetupUI.SetPage(0);
                    campaignSetupUI.CreateDefaultSaveName();
                    campaignSetupUI.RandomizeSeed();
                    campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines);
                    break;
                case Tab.LoadGame:
                    campaignSetupUI.CreateLoadMenu();
                    break;
                case Tab.Settings:
                    SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
                    break;
                case Tab.JoinServer:
                    if (GameSettings.CurrentConfig.TutorialSkipWarning)
                    {
                        selectedTab = Tab.Empty;
                        ShowTutorialSkipWarning(Tab.JoinServer);
                        return true;
                    }
                    GameMain.ServerListScreen.Select();
                    break;
                case Tab.HostServer:
                    if (GameSettings.CurrentConfig.TutorialSkipWarning)
                    {
                        selectedTab = Tab.Empty;
                        ShowTutorialSkipWarning(tab);
                        return true;
                    }
                    serverExecutableDropdown.ListBox.Content.Children.ToArray()
                        .Where(c => c.UserData is ServerExecutableFile f && !ContentPackageManager.EnabledPackages.All.Contains(f.ContentPackage))
                        .ForEach(serverExecutableDropdown.ListBox.RemoveChild);
                    var newServerExes
                        = ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<ServerExecutableFile>())
                            .Where(f => serverExecutableDropdown.ListBox.Content.Children.None(c => c.UserData == f))
                            .ToArray();
                    foreach (var newServerExe in newServerExes)
                    {
                        var serverExeEntry = serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe);
                        if (newServerExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
                        {
                            serverExeEntry.ToolTip =
                                TextManager.GetWithVariables("versionmismatchwarning",
                                    ("[gameversion]", newServerExe.ContentPackage.GameVersion.ToString()),
                                    ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString()));
                            if (serverExeEntry is GUITextBlock serverExeText)
                            {
                                serverExeText.TextColor = GUIStyle.Red;
                            }
                        }
                    }
                    serverExecutableDropdown.ListBox.Content.Children.ForEach(c =>
                    {
                        c.RectTransform.RelativeSize = (1.0f, c.RectTransform.RelativeSize.Y);
                        c.ForceLayoutRecalculation();
                    });
                    bool serverExePickable = serverExecutableDropdown.ListBox.Content.CountChildren > 1;
                    bool wasPickable = serverExecutableDropdown.Parent.Visible;
                    if (wasPickable != serverExePickable)
                    {
                        serverExecutableDropdown.Parent.Visible = serverExePickable;
                        serverExecutableDropdown.Parent.IgnoreLayoutGroups = !serverExePickable;
                        (serverExecutableDropdown.Parent.Parent as GUILayoutGroup)?.Recalculate();
                        if (serverExecutableDropdown.SelectedComponent is null)
                        {
                            serverExecutableDropdown.Select(0);
                        }
                    }
                    break;
                case Tab.Tutorials:
                    UpdateTutorialList();
                    break;
                case Tab.CharacterEditor:
                    Submarine.MainSub = null;
                    CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.CharacterEditorScreen));
                    break;
                case Tab.SubmarineEditor:
                    CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.SubEditorScreen));
                    break;
                case Tab.Mods:
                    var settings = SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
                    settings.SelectTab(SettingsMenu.Tab.Mods);
                    tab = Tab.Settings;
                    break;
                case Tab.Credits:
                    titleText.Visible = false;
                    creditsPlayer.Restart();
                    break;
                case Tab.Empty:
                    titleText.Visible = true;
                    selectedTab = Tab.Empty;
                    break;
            }

            selectedTab = tab;
            leftTextFooterLayout.Visible = tab != Tab.Credits;
            rightTextFooterLayout.Visible = tab != Tab.Credits;

            foreach (var tabFrame in menuTabs.Values)
            {
                tabFrame.Visible = false;
            }
            if (menuTabs.TryGetValue(selectedTab, out var visibleTab)) { visibleTab.Visible = true; }

            return true;
        }

        private IEnumerable<CoroutineStatus> SelectScreenWithWaitCursor(Screen screen)
        {
            GUI.SetCursorWaiting();
            //tiny delay to get the cursor to render
            yield return new WaitForSeconds(0.02f);
            GUI.ClearCursorWait();
            screen.Select();
            yield return CoroutineStatus.Success;
        }

        public bool ReturnToMainMenu(GUIButton button, object obj)
        {
            GUI.PreventPauseMenuToggle = false;

            if (Selected != this)
            {
                Select();
            }
            else
            {
                ResetButtonStates(button);
            }

            SelectTab(null, 0);

            return true;
        }

        private void ResetButtonStates(GUIButton button)
        {
            foreach (GUIComponent child in buttonsParent.Children)
            {
                GUIButton otherButton = child as GUIButton;
                if (otherButton == null || otherButton == button) continue;

                otherButton.Selected = false;
            }
        }
#endregion

        public void ResetModUpdateButton()
        {
            modUpdateStatus = (DateTime.Now, 0);
            modUpdatesButton.Visible = false;
        }

        public void QuickStart(bool fixedSeed = false, Identifier sub = default, float difficulty = 50, LevelGenerationParams levelGenerationParams = null)
        {
            if (fixedSeed)
            {
                Rand.SetSyncedSeed(1);
                Rand.SetLocalRandom(1);
            }

            SubmarineInfo selectedSub = null;
            Identifier subName = sub.IfEmpty(GameSettings.CurrentConfig.QuickStartSub);
            if (!subName.IsEmpty)
            {
                DebugConsole.NewMessage($"Loading the predefined quick start sub \"{subName}\"", Color.White);

                selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
                if (selectedSub == null)
                {
                    DebugConsole.NewMessage($"Cannot find a sub that matches the name \"{subName}\".", Color.Red);
                }
            }
            if (selectedSub == null)
            {
                DebugConsole.NewMessage("Loading a random sub.", Color.White);
                var subs = SubmarineInfo.SavedSubmarines.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.Shuttle) && !s.HasTag(SubmarineTag.HideInMenus));
                selectedSub = subs.ElementAt(Rand.Int(subs.Count()));
            }
            var gamesession = new GameSession(
                selectedSub,
                Option.None,
                GameModePreset.DevSandbox,
                missionPrefabs: null);
            //(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8));
            gamesession.StartRound(fixedSeed ? "abcd" : ToolBox.RandomSeed(8), difficulty, levelGenerationParams);
            GameMain.GameScreen.Select();
            // TODO: modding support
            Identifier[] jobIdentifiers = new Identifier[] { 
                "captain".ToIdentifier(), 
                "engineer".ToIdentifier(), 
                "mechanic".ToIdentifier(), 
                "securityofficer".ToIdentifier(), 
                "medicaldoctor".ToIdentifier() };
            foreach (Identifier job in jobIdentifiers)
            {
                var jobPrefab = JobPrefab.Get(job);
                var variant = Rand.Range(0, jobPrefab.Variants);
                var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant);
                if (characterInfo.Job == null)
                {
                    DebugConsole.ThrowError("Failed to find the job \"" + job + "\"!");
                }
                gamesession.CrewManager.AddCharacterInfo(characterInfo);
                characterInfo.SetNameBasedOnJob();
            }
            gamesession.CrewManager.InitSinglePlayerRound();
        }

        private void ShowTutorialSkipWarning(Tab tabToContinueTo)
        {
            var tutorialSkipWarning = new GUIMessageBox("", TextManager.Get("tutorialskipwarning"), new LocalizedString[] { TextManager.Get("tutorialwarningskiptutorials"), TextManager.Get("tutorialwarningplaytutorials") });
            
            GUIButton.OnClickedHandler proceedToTab(Tab tab)
                => (btn, userdata) =>
                {
                    var config = GameSettings.CurrentConfig;
                    config.TutorialSkipWarning = false;
                    GameSettings.SetCurrentConfig(config);
                    GameSettings.SaveCurrentConfig();
                    tutorialSkipWarning.Close();
                    SelectTab(tab);
                    return true;
                };

            tutorialSkipWarning.Buttons[0].OnClicked += proceedToTab(tabToContinueTo);
            tutorialSkipWarning.Buttons[1].OnClicked += proceedToTab(Tab.Tutorials);
        }

        public override void AddToGUIUpdateList()
        {
            base.AddToGUIUpdateList();
            switch (selectedTab)
            {
                case Tab.NewGame:
                    campaignSetupUI.CharacterMenus?.ForEach(static m => m.AddToGUIUpdateList());
                    break;
            }            
        }

        private void UpdateTutorialList()
        {
            foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
            {
                var tutorial = (Tutorial)tutorialText.UserData;
                if (CompletedTutorials.Instance.Contains(tutorial.Identifier) && tutorialText.GetChild<GUIImage>() == null)
                {
                    new GUIImage(new RectTransform(new Point((int)(tutorialText.Padding.X * 0.8f)), tutorialText.RectTransform, Anchor.CenterLeft), style: "ObjectiveIndicatorCompleted");
                }
            }
        }

        private bool ChangeMaxPlayers(GUIButton button, object obj)
        {
            int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
            currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers + (int)button.UserData, 1, NetConfig.MaxPlayers);
            maxPlayersBox.Text = currMaxPlayers.ToString();
            return true;
        }

        private void TryStartServer()
        {
            if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
            {
                var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
                var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
                waitBox.Buttons[0].OnClicked += (btn, userdata) =>
                {
                    CoroutineManager.StopCoroutines(waitCoroutine);
                    return true;
                };
            }
            else
            {
                StartServer();
            }
        }

        private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
        {
            LocalizedString originalText = messageBox.Text.Text;
            int doneCount = 0;
            do
            {
                doneCount = SubmarineInfo.SavedSubmarines.Count(s => !s.CalculatingHash);
                messageBox.Text.Text = originalText + $" ({doneCount}/{SubmarineInfo.SavedSubmarines.Count()})";
                yield return CoroutineStatus.Running;
            } while (doneCount < SubmarineInfo.SavedSubmarines.Count());
            messageBox.Close();
            StartServer();
            yield return CoroutineStatus.Success;
        }

        private void StartServer()
        {
            string name = serverNameBox.Text;

            GameMain.ResetNetLobbyScreen();
            try
            {
                string fileName;
                if (serverExecutableDropdown.SelectedComponent?.UserData is ServerExecutableFile f && 
                    f.ContentPackage != GameMain.VanillaContent)
                {
                    fileName = Path.Combine(
                        Path.GetDirectoryName(f.Path.Value),
                        Path.GetFileNameWithoutExtension(f.Path.Value));
#if WINDOWS
                    fileName += ".exe";
#endif
                }
                else
                {
#if WINDOWS
                    fileName = "DedicatedServer.exe";
#else
                    fileName = "./DedicatedServer";
#endif
                }

                var arguments = new List<string>
                {
                    "-name", name,
                    "-public", isPublicBox.Selected.ToString(),
                    "-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(),
                    "-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(),
                    "-karmaenabled", (!karmaBox.Selected).ToString(),
                    "-maxplayers", maxPlayersBox.Text,
                    "-language", languageDropdown.SelectedData.ToString()
                };

                if (!string.IsNullOrWhiteSpace(passwordBox.Text))
                {
                    arguments.Add("-password");
                    arguments.Add(passwordBox.Text);
                }
                else
                {
                    arguments.Add("-nopassword");
                }

                var puids = EosInterface.IdQueries.GetLoggedInPuids();

                var endpoints = new List<Endpoint>();
                if (SteamManager.GetSteamId().TryUnwrap(out var steamId))
                {
                    endpoints.Add(new SteamP2PEndpoint(steamId));
                }
                if (puids.Length > 0)
                {
                    endpoints.Add(new EosP2PEndpoint(puids[0]));
                }
                if (endpoints.Count == 0)
                {
                    endpoints.Add(new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort));
                }
                
                if (endpoints.First() is P2PEndpoint firstEndpoint)
                {
                    arguments.Add("-endpoint");
                    arguments.Add(firstEndpoint.StringRepresentation);
                }
                int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
                arguments.Add("-ownerkey");
                arguments.Add(ownerKey.ToString());

                var processInfo = new ProcessStartInfo
                {
                    FileName = fileName,
                    WorkingDirectory = Directory.GetCurrentDirectory(),
#if !DEBUG
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    WindowStyle = ProcessWindowStyle.Hidden
#endif
                };
                arguments.ForEach(processInfo.ArgumentList.Add);
                ChildServerRelay.Start(processInfo);
                Thread.Sleep(1000); //wait until the server is ready before connecting

                GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(
                    SteamManager.GetUsername().FallbackNullOrEmpty(name)),
                    endpoints.ToImmutableArray(),
                    name,
                    Option.Some(ownerKey));
            }
            catch (Exception e)
            {
                DebugConsole.ThrowError("Failed to start server", e);
            }
        }

        private bool QuitClicked(GUIButton button, object obj)
        {
            game.Exit();
            return true;
        }

        private void UpdateOutOfDateWorkshopItemCount()
        {
            if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; }
            if (!SteamManager.IsInitialized) { return; }

            var installedPackages = ContentPackageManager.WorkshopPackages;

            var ids = SteamManager.Workshop.GetSubscribedItemIds()
                .Select(id => id.Value)
                .Union(installedPackages
                    .Select(pkg => pkg.UgcId)
                    .NotNone()
                    .OfType<SteamWorkshopId>()
                    .Select(id => id.Value));
            var count = ids
                // Deliberately construct Steamworks.Ugc.Item directly
                // to not immediately generate a Workshop data request
                .Select(id => new Steamworks.Ugc.Item(id))
                .Count(item =>
                    installedPackages.FirstOrDefault(p
                        => p.UgcId.TryUnwrap(out SteamWorkshopId id) && id.Value == item.Id)
                        is { } pkg
                    // Checking that this item is downloading, waiting to be downloaded
                    // or is newer than the currently installed copy should be good enough,
                    // and should still not make a Workshop data request
                    && (item.IsDownloading
                        || item.IsDownloadPending
                        || (item.InstallTime.TryGetValue(out var workshopInstallTime)
                            && pkg.InstallTime.TryUnwrap(out var localInstallTime)
                            && localInstallTime.ToUtcValue() < workshopInstallTime)));

            modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
        }

        private static bool CanHostServer()
            => EosInterface.IdQueries.IsLoggedIntoEosConnect
                || SteamManager.IsInitialized
                || AssemblyInfo.CurrentConfiguration == AssemblyInfo.Configuration.Debug;

        public override void Update(double deltaTime)
        {
            hostServerButton.Enabled = CanHostServer();

            gameAnalyticsStatusText.Text = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");

            UpdateOutOfDateWorkshopItemCount();
            modUpdatesButton.Visible = modUpdateStatus.Count > 0;

            if (modUpdatesButton.Visible)
            {
                var modButtonLabelSize =
                    modsButton.Font.MeasureString(modsButton.Text).ToPoint()
                    + new Point(GUI.IntScale(25));
                modUpdatesButton.RectTransform.AbsoluteOffset =
                    (modButtonLabelSize.X, modsButton.Rect.Height / 2 - modUpdatesButton.Rect.Height / 2);
            }
            
            switch (selectedTab)
            {
                case Tab.NewGame:
                    campaignSetupUI.Update();
                    break;
            }
        }

        public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
        {
            graphics.Clear(Color.Black);

            if (backgroundSprite == null)
            {
#if UNSTABLE
                backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
#endif
                if (GUIStyle.GetComponentStyle("MainMenuBackground") is { } mainMenuStyle &&
                     mainMenuStyle.Sprites.TryGetValue(GUIComponent.ComponentState.None, out var sprites))
                {
                    backgroundSprite = sprites.GetRandomUnsynced()?.Sprite;
                }
                backgroundSprite ??= LocationType.Prefabs.GetRandomUnsynced()?.GetPortrait(0);
            }

            var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
            float vignetteScale = Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y);

            Rectangle drawArea = new Rectangle(
                (int)(vignette.size.X * vignetteScale / 2), 0,
                (int)(GameMain.GraphicsWidth - vignette.size.X * vignetteScale / 2), GameMain.GraphicsHeight);

            if (backgroundSprite?.Texture != null)
            {
                GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White, drawArea);
            }

            if (vignette != null)
            {
                vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, vignetteScale);
            }
        }

        public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
        {
            spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);

            DrawBackground(graphics, spriteBatch);

            GUI.Draw(Cam, spriteBatch);

            spriteBatch.End();
        }

        private void StartGame(SubmarineInfo selectedSub, string savePath, string mapSeed, CampaignSettings settings)
        {
            if (string.IsNullOrEmpty(savePath)) { return; }

            var existingSaveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
            if (existingSaveFiles.Any(s => s.FilePath == savePath))
            {
                new GUIMessageBox(TextManager.Get("SaveNameInUseHeader"), TextManager.Get("SaveNameInUseText"));
                return;
            }

            if (selectedSub == null)
            {
                new GUIMessageBox(TextManager.Get("SubNotSelected"), TextManager.Get("SelectSubRequest"));
                return;
            }

            if (!Directory.Exists(SaveUtil.TempPath))
            {
                Directory.CreateDirectory(SaveUtil.TempPath, catchUnauthorizedAccessExceptions: true);
            }

            try
            {
                File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), overwrite: true, catchUnauthorizedAccessExceptions: false);
            }
            catch (System.IO.IOException e)
            {
                DebugConsole.ThrowError("Copying the file \"" + selectedSub.FilePath + "\" failed. The file may have been deleted or in use by another process. Try again or select another submarine.", e);
                GameAnalyticsManager.AddErrorEventOnce(
                    "MainMenuScreen.StartGame:IOException" + selectedSub.Name,
                    GameAnalyticsManager.ErrorSeverity.Error,
                    "Copying a submarine file failed. " + e.Message + "\n" + Environment.StackTrace.CleanupStackTrace());
                return;
            }

            GameMain.LuaCs.CheckInitialize();

            selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
            
            GameMain.GameSession = new GameSession(selectedSub, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.SinglePlayerCampaign, settings, mapSeed);
            GameMain.GameSession.CrewManager.ClearCharacterInfos();
            foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
            {
                GameMain.GameSession.CrewManager.AddCharacterInfo(characterInfo);
            }
            ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
        }

        private void LoadGame(string path, Option<uint> backupIndex)
        {
            if (string.IsNullOrWhiteSpace(path)) return;

            GameMain.LuaCs.CheckInitialize();

            try
            {
                CampaignDataPath dataPath =
                    backupIndex.TryUnwrap(out uint index)
                        ? new CampaignDataPath(loadPath: SaveUtil.GetBackupPath(path, index), path)
                        : CampaignDataPath.CreateRegular(path);

                SaveUtil.LoadGame(dataPath);
            }
            catch (Exception e)
            {
                DebugConsole.ThrowError("Loading save \"" + path + "\" failed", e);
                GameMain.GameSession = null;
                return;
            }
        }

#region UI Methods
        private void CreateCampaignSetupUI()
        {
            menuTabs[Tab.NewGame].ClearChildren();
            menuTabs[Tab.LoadGame].ClearChildren();

            var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.NewGame].RectTransform, Anchor.Center))
            {
                Stretch = true,
                RelativeSpacing = 0.02f
            };
            var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center),
                style: "InnerFrame");

            var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) },
                style: null);

            campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame)
            {
                LoadGame = LoadGame,
                StartNewGame = StartGame
            };
        }

        private void CreateHostServerFields()
        {
            menuTabs[Tab.HostServer].ClearChildren();

            var serverSettings = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile, out _)?.Root ?? new XElement("serversettings");

            var name = serverSettings.GetAttributeString(nameof(ServerSettings.ServerName), "");
            var password = serverSettings.GetAttributeString("password", "");
            var isPublic = serverSettings.GetAttributeBool("IsPublic", true);
            var banAfterWrongPassword = serverSettings.GetAttributeBool("banafterwrongpassword", false);

            int maxPlayersElement = serverSettings.GetAttributeInt("maxplayers", 8);
            if (maxPlayersElement > NetConfig.MaxPlayers)
            {
                DebugConsole.AddWarning($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.");
            }
            int maxPlayers = Math.Clamp(maxPlayersElement, min: 1, max: NetConfig.MaxPlayers);

            var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", true);
            var selectedPlayStyle = serverSettings.GetAttributeEnum("playstyle", PlayStyle.Casual);

            Vector2 textLabelSize = new Vector2(1.0f, 0.05f);
            Alignment textAlignment = Alignment.CenterLeft;
            Vector2 textFieldSize = new Vector2(0.5f, 1.0f);
            Vector2 tickBoxSize = new Vector2(0.4f, 0.04f);
            var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.95f), menuTabs[Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
            {
                RelativeSpacing = 0.01f,
                Stretch = true
            };
            GUIComponent parent = content;

            var header = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) { ForceUpperCase = ForceUpperCase.Yes };
            header.RectTransform.IsFixedSize = true;

            //play style -----------------------------------------------------

            var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);

            playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform), 
                GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent)
            {
                UserData = PlayStyle.Serious
            };
            float bannerAspectRatio = (float) playstyleBanner.Sprite.SourceRect.Width / playstyleBanner.Sprite.SourceRect.Height;
            playstyleBanner.RectTransform.NonScaledSize = new Point(playstyleBanner.Rect.Width, (int)(playstyleBanner.Rect.Width / bannerAspectRatio));
            playstyleBanner.RectTransform.IsFixedSize = true;
            new GUIFrame(new RectTransform(playstyleBanner.Rect.Size + new Point(1), playstyleBanner.RectTransform, Anchor.Center), "InnerGlow", color: Color.Black);

            new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.05f), playstyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.03f) },
                "playstyle name goes here", font: GUIStyle.SmallFont, textAlignment: Alignment.Center, textColor: Color.White, style: "GUISlopedHeader");
            
            new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterLeft) 
                { RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) }, 
                style: "UIToggleButton")
            {
                OnClicked = (btn, userdata) =>
                {
                    int playStyleIndex = (int)playstyleBanner.UserData - 1;
                    if (playStyleIndex < 0) { playStyleIndex = Enum.GetValues(typeof(PlayStyle)).Length - 1; }
                    SetServerPlayStyle((PlayStyle)playStyleIndex);
                    return true;
                }
            }.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);

            new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterRight) 
                { RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
                style: "UIToggleButton")
            {
                OnClicked = (btn, userdata) =>
                {
                    int playStyleIndex = (int)playstyleBanner.UserData + 1;
                    if (playStyleIndex >= Enum.GetValues(typeof(PlayStyle)).Length) { playStyleIndex = 0; }
                    SetServerPlayStyle((PlayStyle)playStyleIndex);
                    return true;
                }
            };

            LocalizedString longestPlayStyleStr = "";
            foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
            {
                LocalizedString playStyleStr = TextManager.Get("servertagdescription." + playStyle);
                if (playStyleStr.Length > longestPlayStyleStr.Length) { longestPlayStyleStr = playStyleStr; }
            }

            playstyleDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), playstyleContainer.RectTransform, Anchor.BottomCenter),
                longestPlayStyleStr, style: null, wrap: true)
            {
                Color = Color.Black * 0.8f,
                TextColor = GUIStyle.GetComponentStyle("GUITextBlock").TextColor
            };
            playstyleDescription.Padding = Vector4.One * 10.0f * GUI.Scale;
            playstyleDescription.CalculateHeightFromText(padding: (int)(15 * GUI.Scale));
            playstyleDescription.RectTransform.NonScaledSize = new Point(playstyleDescription.Rect.Width, playstyleDescription.Rect.Height);
            playstyleDescription.RectTransform.IsFixedSize = true;
            playstyleContainer.RectTransform.NonScaledSize = new Point(playstyleContainer.Rect.Width, playstyleBanner.Rect.Height + playstyleDescription.Rect.Height);
            playstyleContainer.RectTransform.IsFixedSize = true;

            SetServerPlayStyle(selectedPlayStyle);

            //other settings -----------------------------------------------------

            //spacing
            new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);

            var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment);
            serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: name, textAlignment: textAlignment)
            { 
                MaxTextLength = NetConfig.ServerNameMaxLength,
                OverflowClip = true
            };
            label.RectTransform.IsFixedSize = true;

            var maxPlayersLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("MaxPlayers"), textAlignment: textAlignment);
            var buttonContainer = new GUILayoutGroup(new RectTransform(textFieldSize, maxPlayersLabel.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
            {
                Stretch = true,
                RelativeSpacing = 0.1f
            };
            new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center)
            {
                UserData = -1,
                OnClicked = ChangeMaxPlayers,
                ClickSound = GUISoundType.Decrease
            };
            maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center)
            {
                Text = maxPlayers.ToString()                
            };
            maxPlayersBox.OnEnterPressed += (GUITextBox sender, string text) =>
            {
                maxPlayersBox.Deselect();
                return true;
            };
            maxPlayersBox.OnDeselected += (GUITextBox sender, Microsoft.Xna.Framework.Input.Keys key) =>
            {
                int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
                currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers, 1, NetConfig.MaxPlayers);
                maxPlayersBox.Text = currMaxPlayers.ToString();
            };
            new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
            {
                UserData = 1,
                OnClicked = ChangeMaxPlayers,
                ClickSound = GUISoundType.Increase
            };
            maxPlayersLabel.RectTransform.IsFixedSize = true;

            label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("Password"), textAlignment: textAlignment);
            passwordBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: password, textAlignment: textAlignment)
            {
                Censor = true
            };
            label.RectTransform.IsFixedSize = true;

            var languageLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
                TextManager.Get("Language"), textAlignment: textAlignment);
            languageDropdown = new GUIDropDown(new RectTransform(textFieldSize, languageLabel.RectTransform, Anchor.CenterRight));
            foreach (var language in ServerLanguageOptions.Options)
            {
                languageDropdown.AddItem(language.Label, language.Identifier);
            }
            var defaultLanguage = ServerLanguageOptions.PickLanguage(GameSettings.CurrentConfig.Language);
            var settingsLanguage = serverSettings.GetAttributeIdentifier("language", defaultLanguage.Value).ToLanguageIdentifier();
            if (!ServerLanguageOptions.Options.Any(o => o.Identifier == settingsLanguage))
            {
                settingsLanguage = defaultLanguage;
            }
            languageDropdown.Select(ServerLanguageOptions.Options.FindIndex(o => o.Identifier == settingsLanguage));

            var serverExecutableLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
                TextManager.Get("ServerExecutable"), textAlignment: textAlignment);
            const string vanillaServerOption = "Vanilla";
            serverExecutableDropdown
                = new GUIDropDown(new RectTransform(textFieldSize, serverExecutableLabel.RectTransform, Anchor.CenterRight),
                    vanillaServerOption);
            var listBoxSize = serverExecutableDropdown.ListBox.RectTransform.RelativeSize;
            serverExecutableDropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.5f, listBoxSize.Y);
            serverExecutableDropdown.AddItem(vanillaServerOption, userData: null);
            serverExecutableDropdown.OnSelected = (selected, userData) =>
            {
                if (userData != null)
                {
                    var warningBox = new GUIMessageBox(headerText: TextManager.Get("Warning"),
                        text: TextManager.GetWithVariable("ModServerExesAtYourOwnRisk", "[exename]", serverExecutableDropdown.Text),
                        new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
                    warningBox.Buttons[0].OnClicked = (_, __) =>
                    {
                        warningBox.Close();
                        return false;
                    };
                    warningBox.Buttons[1].OnClicked = (_, __) =>
                    {
                        serverExecutableDropdown.Select(0);
                        warningBox.Close();
                        return false;
                    };
                }
                
                serverExecutableDropdown.Text = ToolBox.LimitString(serverExecutableDropdown.Text,
                    serverExecutableDropdown.Font, serverExecutableDropdown.Rect.Width * 8 / 10);

                return true;
            };
            serverExecutableLabel.RectTransform.IsFixedSize = true;

            // tickbox upper ---------------

            var tickboxAreaUpper = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);

            isPublicBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("PublicServer"))
            {
                Selected = isPublic,
                ToolTip = TextManager.Get("PublicServerToolTip")
            };

            wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"))
            {
                Selected = banAfterWrongPassword
            };

            tickboxAreaUpper.RectTransform.IsFixedSize = true;

            // tickbox lower ---------------

            var tickboxAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);

            karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("HostServerKarmaSetting"))
            {
                Selected = !karmaEnabled,
                ToolTip = TextManager.Get("hostserverkarmasettingtooltip")
            };

            tickboxAreaLower.RectTransform.IsFixedSize = true;

            //spacing
            new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), style: null);

            new GUIButton(new RectTransform(new Vector2(0.4f, 0.07f), content.RectTransform), TextManager.Get("StartServerButton"), style: "GUIButtonLarge")
            {
                OnClicked = (btn, userdata) =>
                {
                    CheckServerName();
                    return true;
                }
            };

            void CheckServerName()
            {
                string name = serverNameBox.Text;
                if (string.IsNullOrEmpty(name))
                {
                    serverNameBox.Flash();
                    return;
                }
                if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord))
                {
                    var msgBox = new GUIMessageBox("",
                        TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)),
                        new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
                    msgBox.Buttons[0].OnClicked += (_, __) =>
                    {
                        CheckServerExe();
                        msgBox.Close();
                        return true;
                    };
                    msgBox.Buttons[1].OnClicked += msgBox.Close;
                    return;
                }
                CheckServerExe();                
            }

            void CheckServerExe()
            {
                if (serverExecutableDropdown?.SelectedData is ServerExecutableFile serverExe &&
                    serverExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
                {
                    var msgBox = new GUIMessageBox(string.Empty,
                        TextManager.GetWithVariables("versionmismatchwarning",
                            ("[gameversion]", serverExe.ContentPackage.GameVersion.ToString()),
                            ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())) + "\n\n"+ 
                        TextManager.GetWithVariable("versionmismatch.verifylaunch", "[exename]", serverExe.ContentPackage.Name),
                        new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
                    msgBox.Buttons[0].OnClicked += (_, __) =>
                    {
                        TryStartServer();
                        msgBox.Close();
                        return true;
                    };
                    msgBox.Buttons[1].OnClicked += msgBox.Close;
                    return;
                }
                TryStartServer();                
            }
        }

        private void SetServerPlayStyle(PlayStyle playStyle)
        {
            playstyleBanner.Sprite = GUIStyle
                .GetComponentStyle($"PlayStyleBanner.{playStyle}")
                .GetSprite(GUIComponent.ComponentState.None);
            playstyleBanner.UserData = playStyle;

            var nameText = playstyleBanner.GetChild<GUITextBlock>();
            nameText.Text = TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag." + playStyle));
            nameText.Color = playstyleBanner.Sprite
                .SourceElement.GetAttributeColor("BannerColor") ?? Color.White;
            nameText.RectTransform.NonScaledSize = (nameText.Font.MeasureString(nameText.Text) + new Vector2(25, 10) * GUI.Scale).ToPoint();

            playstyleDescription.Text = TextManager.Get("servertagdescription." + playStyle);
            playstyleDescription.TextAlignment = playstyleDescription.WrappedText.Contains('\n') ?
               Alignment.CenterLeft : Alignment.Center;
        }
#endregion

        private void FetchRemoteContent()
        {
            string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
            if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
            try
            {
                var client = new RestClient(remoteContentUrl);
                var request = new RestRequest("MenuContent.xml", Method.GET);
                TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request),
                    RemoteContentReceived);
            }

            catch (Exception e)
            {
#if DEBUG
                DebugConsole.ThrowError("Fetching remote content to the main menu failed.", e);
#endif
                GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.FetchRemoteContent:Exception", GameAnalyticsManager.ErrorSeverity.Error,
                    "Fetching remote content to the main menu failed. " + e.Message);
                return;
            }
        }

        private void RemoteContentReceived(Task t)
        {
            try
            {
                if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
                if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
                {
                    DebugConsole.AddWarning(
                        "Failed to receive remote main menu content. " +
                        "There may be an issue with your internet connection, or the master server might be temporarily unavailable " +
                        $"(error code: {remoteContentResponse.StatusCode})");
                    return;
                }
                string xml = remoteContentResponse.Content;
                int index = xml.IndexOf('<');
                if (index > 0) { xml = xml.Substring(index, xml.Length - index); }
                if (!string.IsNullOrWhiteSpace(xml))
                {
                    remoteContentDoc = XDocument.Parse(xml);
                    foreach (var subElement in remoteContentDoc?.Root.Elements())
                    {
                        GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
                    }
                }
            }

            catch (Exception e)
            {
#if DEBUG
                DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
#endif
                GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentReceived:Exception", GameAnalyticsManager.ErrorSeverity.Error,
                    "Reading received remote main menu content failed. " + e.Message);
            }
        }
    }
}
