using Barotrauma.Extensions;
using Barotrauma.Media;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Barotrauma
{
    sealed class LoadingScreen
    {
        private readonly Sprite defaultBackgroundTexture, overlay;
        private readonly SpriteSheet decorativeGraph, decorativeMap;
        private Sprite currentBackgroundTexture;
        private readonly Sprite noiseSprite;

        private string randText = "";

        private Sprite languageSelectionCursor;
        private ScalableFont languageSelectionFont, languageSelectionFontCJK;

        private Video currSplashScreen;
        private DateTime videoStartTime;

        public struct PendingSplashScreen
        {
            public string Filename;
            public float Gain;
            public PendingSplashScreen(string filename, float gain)
            {
                Filename = filename;
                Gain = gain;
            }
        }

        /// <summary>
        /// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
        /// </summary>
        public readonly ConcurrentQueue<PendingSplashScreen> PendingSplashScreens = new ConcurrentQueue<PendingSplashScreen>();

        public bool PlayingSplashScreen
        {
            get
            {
                return currSplashScreen != null || PendingSplashScreens.Count > 0;
            }
        }

        private RichString selectedTip;

        private string selectedTipString;
        private ImmutableArray<RichTextData>? selectedTipRichTextData;

        private void SetSelectedTip(LocalizedString tip)
        {
            selectedTip = RichString.Rich(tip);
            selectedTipString = string.Empty;
            selectedTipRichTextData = null;
        }

        public float LoadState;

        public bool WaitForLanguageSelection
        {
            get;
            set;
        }

        public LanguageIdentifier[] AvailableLanguages = null;

        public LoadingScreen(GraphicsDevice graphics)
        {
            defaultBackgroundTexture = new Sprite("Content/Map/LocationPortraits/MainMenu1.png", Vector2.Zero);

            decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
            decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));

            overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
            noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);

            SetSelectedTip(TextManager.Get("LoadingScreenTip"));
        }

        public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
        {
            if (GameSettings.CurrentConfig.EnableSplashScreen)
            {
                try
                {
                    DrawSplashScreen(spriteBatch, graphics);
                    if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
                }
                catch (Exception e)
                {
                    DebugConsole.ThrowError("Playing splash screen video failed", e);
                    DisableSplashScreen();
                }
            }

            drawn = true;

            currentBackgroundTexture ??= defaultBackgroundTexture;

            float overlayScale = Math.Min(GameMain.GraphicsWidth / overlay.size.X, GameMain.GraphicsHeight / overlay.size.Y);

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

            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);

            GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
            overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);

            double noiseT = Timing.TotalTime * 0.02f;
            float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
            float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
            noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
                startOffset: new Vector2(Rand.Range(0.0f, noiseSprite.SourceRect.Width), Rand.Range(0.0f, noiseSprite.SourceRect.Height)),
                color: Color.White * noiseStrength * 0.1f,
                textureScale: Vector2.One * noiseScale);

            Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.75f));
            if (WaitForLanguageSelection)
            {
                DrawLanguageSelectionPrompt(spriteBatch, graphics);
            }
            else
            {
                LocalizedString loadText;
                var loadState = LoadState; // avoid multiple reads here to prevent jank
                if (loadState >= 100.0f)
                {
#if DEBUG
                    if (GameSettings.CurrentConfig.AutomaticQuickStartEnabled || GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled || (GameSettings.CurrentConfig.TestScreenEnabled && GameMain.FirstLoad))
                    {
                        loadText = "QUICKSTARTING ...";
                    }
                    else
                    {
#endif
                        loadText = TextManager.Get("PressAnyKey");
#if DEBUG
                    }
#endif
                }
                else
                {
                    loadText = TextManager.Get("Loading");
                    if (loadState >= 0f)
                    {
                        loadText += $" {loadState:N0} %";
                    }

#if DEBUG
                    if (GameMain.FirstLoad && GameMain.CancelQuickStart)
                    {
                        loadText += " (Quickstart aborted)";
                    }
#endif
                }

                if (GUIStyle.LargeFont.HasValue)
                {
                    GUIStyle.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
                        textPos,
                        Color.White);
                    textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
                }

                if (GUIStyle.Font.HasValue && selectedTip != null && !selectedTip.SanitizedValue.IsNullOrEmpty())
                {
                    //store the string value of the LocalizedString to prevent the text from changing if/when new text packs are loaded during the loading screen
                    if (selectedTipString.IsNullOrEmpty())
                    {
                        selectedTipString = selectedTip.SanitizedValue;
                        selectedTipRichTextData = selectedTip.RichTextData;
                    }

                    string wrappedTip = ToolBox.WrapText(selectedTipString, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
                    string[] lines = wrappedTip.Split('\n');
                    float lineHeight = GUIStyle.Font.MeasureString(selectedTipString).Y;

                    if (selectedTipRichTextData != null)
                    {
                        int rtdOffset = 0;
                        for (int i = 0; i < lines.Length; i++)
                        {
                            GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
                                new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
                                Color.White,
                                0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTipRichTextData.Value, rtdOffset);
                            rtdOffset += lines[i].Length;
                        }
                    }
                    else
                    {
                        for (int i = 0; i < lines.Length; i++)
                        {
                            GUIStyle.Font.DrawString(spriteBatch, lines[i],
                                new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
                                new Color(228, 217, 167, 255));
                        }
                    }
                }
            }
            GUI.DrawMessageBoxesOnly(spriteBatch);
            spriteBatch.End();

            spriteBatch.Begin(blendState: BlendState.Additive);

            Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);

            float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
            if (!WaitForLanguageSelection)
            {
                decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
                    new Vector2(GameMain.GraphicsWidth * 0.001f, textPos.Y),
                    Color.White, new Vector2(0, decorativeMap.FrameSize.Y), 0.0f, decorativeScale, SpriteEffects.FlipVertically);
            }

            decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
                new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.01f),
                Color.White, new Vector2(decorativeMap.FrameSize.X, 0), 0.0f, decorativeScale, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically);

            if (noiseVal < 0.2f)
            {
                //SCP-CB reference
                randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandomUnsynced();
            }
            else if (noiseVal < 0.3f)
            {
                randText = ToolBox.RandomSeed(9);
            }
            else if (noiseVal < 0.5f)
            {
                randText =
                    Rand.Int(100).ToString().PadLeft(2, '0') + " " +
                    Rand.Int(100).ToString().PadLeft(2, '0') + " " +
                    Rand.Int(100).ToString().PadLeft(2, '0') + " " +
                    Rand.Int(100).ToString().PadLeft(2, '0');
            }

            if (GUIStyle.LargeFont.HasValue)
            {
                Vector2 textSize = GUIStyle.LargeFont.MeasureString(randText);
                GUIStyle.LargeFont.DrawString(spriteBatch, randText,
                    new Vector2(GameMain.GraphicsWidth * 0.95f - textSize.X, GameMain.GraphicsHeight * 0.06f),
                    Color.White * (1.0f - noiseVal));
            }

            spriteBatch.End();
        }

        private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
        {
            if (AvailableLanguages is null) { return; }

            if (languageSelectionFont == null)
            {
                languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf",
                    (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
            }
            if (languageSelectionFontCJK == null)
            {
                languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
                    (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true);
            }
            if (languageSelectionCursor == null)
            {
                languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
            }

            Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.3f));
            Vector2 textSpacing = new Vector2(0.0f, GameMain.GraphicsHeight * 0.5f / AvailableLanguages.Length);
            foreach (LanguageIdentifier language in AvailableLanguages)
            {
                string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
                var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont;

                Vector2 textSize = font.MeasureString(localizedLanguageName);
                bool hover =
                    PlayerInput.MousePosition.X > textPos.X && PlayerInput.MousePosition.X < textPos.X + textSize.X &&
                    PlayerInput.MousePosition.Y > textPos.Y && PlayerInput.MousePosition.Y < textPos.Y + textSize.Y;

                font.DrawString(spriteBatch, localizedLanguageName, textPos,
                    hover ? Color.White : Color.White * 0.6f);
                if (hover && PlayerInput.PrimaryMouseButtonClicked())
                {
                    var config = GameSettings.CurrentConfig;
                    config.Language = language;
                    GameSettings.SetCurrentConfig(config);
                    //reload tip in the selected language
                    SetSelectedTip(TextManager.Get("LoadingScreenTip"));
                    WaitForLanguageSelection = false;
                    languageSelectionFont?.Dispose(); languageSelectionFont = null;
                    languageSelectionFontCJK?.Dispose(); languageSelectionFontCJK = null;
                    break;
                }

                textPos += textSpacing;
            }

            languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, scale: 0.5f);
        }

        private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
        {
            if (currSplashScreen == null)
            {
                if (!PendingSplashScreens.TryDequeue(out var newSplashScreen)) { return; }
                string fileName = newSplashScreen.Filename;
                try
                {
                    currSplashScreen = Video.Load(graphics, GameMain.SoundManager, fileName);
                    currSplashScreen.AudioGain = newSplashScreen.Gain;
                    videoStartTime = DateTime.Now;
                }
                catch (Exception e)
                {
                    DisableSplashScreen();
                    DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
                    PendingSplashScreens.Clear();
                    currSplashScreen = null;
                }
            }

            if (currSplashScreen == null) { return; }

            if (currSplashScreen.IsPlaying)
            {
                graphics.Clear(Color.Black);
                float videoAspectRatio = (float)currSplashScreen.Width / (float)currSplashScreen.Height;
                int width; int height;
                if (GameMain.GraphicsHeight * videoAspectRatio > GameMain.GraphicsWidth)
                {
                    width = GameMain.GraphicsWidth;
                    height = (int)(GameMain.GraphicsWidth / videoAspectRatio);
                }
                else
                {
                    width = (int)(GameMain.GraphicsHeight * videoAspectRatio);
                    height = GameMain.GraphicsHeight;
                }

                spriteBatch.Begin();
                spriteBatch.Draw(
                    currSplashScreen.GetTexture(),
                    destinationRectangle: new Rectangle(
                        GameMain.GraphicsWidth / 2 - width / 2,
                        GameMain.GraphicsHeight / 2 - height / 2,
                        width,
                        height),
                    sourceRectangle: new Rectangle(0, 0, currSplashScreen.Width, currSplashScreen.Height),
                    Color.White,
                    rotation: 0.0f,
                    origin: Vector2.Zero,
                    SpriteEffects.None,
                    layerDepth: 0.0f);
                spriteBatch.End();

                if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500)
                    && GameMain.WindowActive
                    && (PlayerInput.KeyHit(Keys.Escape)
                        || PlayerInput.KeyHit(Keys.Space)
                        || PlayerInput.KeyHit(Keys.Enter)
                        || PlayerInput.PrimaryMouseButtonDown()))
                {
                    currSplashScreen.Dispose(); currSplashScreen = null;
                }
            }
            else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 1500))
            {
                currSplashScreen.Dispose(); currSplashScreen = null;
            }
        }

        private void DisableSplashScreen()
        {
            var config = GameSettings.CurrentConfig;
            config.EnableSplashScreen = false;
            GameSettings.SetCurrentConfig(config);
        }
        
        bool drawn;
        public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader)
        {
            drawn = false;
            LoadState = -1f;
            SetSelectedTip(TextManager.Get("LoadingScreenTip"));
            currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue));
            if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))
            {
                currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
            }

            while (!drawn)
            {
                yield return CoroutineStatus.Running;
            }

            CoroutineManager.StartCoroutine(loader);

            yield return CoroutineStatus.Running;

            while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
            {
                yield return CoroutineStatus.Running;
            }

            LoadState = 100.0f;

            yield return CoroutineStatus.Success;
        }
    }
}
