using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;

using Dalamud.Configuration.Internal;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using ImGuiScene;

namespace Dalamud.Interface.Internal.Windows
{
    /// <summary>
    /// Class responsible for drawing the main plugin window.
    /// </summary>
    internal class TitleScreenMenuWindow : Window, IDisposable
    {
        private const float TargetFontSizePt = 18f;
        private const float TargetFontSizePx = TargetFontSizePt * 4 / 3;

        private readonly TextureWrap shadeTexture;

        private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
        private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
        private readonly Dictionary<Guid, InOutCubic> logoEasings = new();
        private readonly Dictionary<string, InterfaceManager.SpecialGlyphRequest> specialGlyphRequests = new();

        private InOutCubic? fadeOutEasing;

        private State state = State.Hide;

        /// <summary>
        /// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
        /// </summary>
        public TitleScreenMenuWindow()
            : base(
                "TitleScreenMenuOverlay",
                ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
                   ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
        {
            this.IsOpen = true;

            this.ForceMainWindow = true;

            this.Position = new Vector2(0, 200);
            this.PositionCondition = ImGuiCond.Always;
            this.RespectCloseHotkey = false;

            var dalamud = Service<Dalamud>.Get();
            var interfaceManager = Service<InterfaceManager>.Get();

            var shadeTex =
                interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png"));
            this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture.");

            var framework = Service<Framework>.Get();
            framework.Update += this.FrameworkOnUpdate;
        }

        private enum State
        {
            Hide,
            Show,
            FadeOut,
        }

        /// <inheritdoc/>
        public override void PreDraw()
        {
            ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0));
            ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
            base.PreDraw();
        }

        /// <inheritdoc/>
        public override void PostDraw()
        {
            ImGui.PopStyleVar(2);
            base.PostDraw();
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            this.shadeTexture.Dispose();
            var framework = Service<Framework>.Get();
            framework.Update -= this.FrameworkOnUpdate;
        }

        /// <inheritdoc/>
        public override void Draw()
        {
            var scale = ImGui.GetIO().FontGlobalScale;

            var tsm = Service<TitleScreenMenu>.Get();

            switch (this.state)
            {
                case State.Show:
                    {
                        for (var i = 0; i < tsm.Entries.Count; i++)
                        {
                            var entry = tsm.Entries[i];

                            if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing))
                            {
                                moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400));
                                this.moveEasings.Add(entry.Id, moveEasing);
                            }

                            if (!moveEasing.IsRunning && !moveEasing.IsDone)
                            {
                                moveEasing.Restart();
                            }

                            if (moveEasing.IsDone)
                            {
                                moveEasing.Stop();
                            }

                            moveEasing.Update();

                            var finalPos = (i + 1) * this.shadeTexture.Height * scale;
                            var pos = moveEasing.Value * finalPos;

                            // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment.
                            if (moveEasing.IsDone)
                            {
                                pos = finalPos;
                            }

                            this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true);

                            var cursor = ImGui.GetCursorPos();
                            cursor.Y = (float)pos;
                            ImGui.SetCursorPos(cursor);
                        }

                        if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
                                                   ImGuiHoveredFlags.AllowWhenOverlapped |
                                                   ImGuiHoveredFlags.AllowWhenBlockedByActiveItem))
                        {
                            this.state = State.FadeOut;
                        }

                        break;
                    }

                case State.FadeOut:
                    {
                        this.fadeOutEasing ??= new InOutCubic(TimeSpan.FromMilliseconds(400))
                        {
                            IsInverse = true,
                        };

                        if (!this.fadeOutEasing.IsRunning && !this.fadeOutEasing.IsDone)
                        {
                            this.fadeOutEasing.Restart();
                        }

                        if (this.fadeOutEasing.IsDone)
                        {
                            this.fadeOutEasing.Stop();
                        }

                        this.fadeOutEasing.Update();

                        ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value);

                        for (var i = 0; i < tsm.Entries.Count; i++)
                        {
                            var entry = tsm.Entries[i];

                            var finalPos = (i + 1) * this.shadeTexture.Height * scale;

                            this.DrawEntry(entry, i != 0, true, i == 0, false);

                            var cursor = ImGui.GetCursorPos();
                            cursor.Y = finalPos;
                            ImGui.SetCursorPos(cursor);
                        }

                        ImGui.PopStyleVar();

                        var isHover = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
                                                            ImGuiHoveredFlags.AllowWhenOverlapped |
                                                            ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);

                        if (!isHover && this.fadeOutEasing!.IsDone)
                        {
                            this.state = State.Hide;
                            this.fadeOutEasing = null;
                        }
                        else if (isHover)
                        {
                            this.state = State.Show;
                            this.fadeOutEasing = null;
                        }

                        break;
                    }

                case State.Hide:
                    {
                        if (this.DrawEntry(tsm.Entries[0], true, false, true, true))
                        {
                            this.state = State.Show;
                        }

                        this.moveEasings.Clear();
                        this.logoEasings.Clear();
                        this.shadeEasings.Clear();
                        break;
                    }
            }

            var srcText = tsm.Entries.Select(e => e.Name).ToHashSet();
            var keys = this.specialGlyphRequests.Keys.ToHashSet();
            keys.RemoveWhere(x => srcText.Contains(x));
            foreach (var key in keys)
            {
                this.specialGlyphRequests[key].Dispose();
                this.specialGlyphRequests.Remove(key);
            }
        }

        private bool DrawEntry(
            TitleScreenMenu.TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha)
        {
            InterfaceManager.SpecialGlyphRequest fontHandle;
            if (this.specialGlyphRequests.TryGetValue(entry.Name, out fontHandle) && fontHandle.Size != TargetFontSizePx)
            {
                fontHandle.Dispose();
                this.specialGlyphRequests.Remove(entry.Name);
                fontHandle = null;
            }

            if (fontHandle == null)
                this.specialGlyphRequests[entry.Name] = fontHandle = Service<InterfaceManager>.Get().NewFontSizeRef(TargetFontSizePx, entry.Name);

            ImGui.PushFont(fontHandle.Font);
            ImGui.SetWindowFontScale(TargetFontSizePx / fontHandle.Size);

            var scale = ImGui.GetIO().FontGlobalScale;

            if (!this.shadeEasings.TryGetValue(entry.Id, out var shadeEasing))
            {
                shadeEasing = new InOutCubic(TimeSpan.FromMilliseconds(350));
                this.shadeEasings.Add(entry.Id, shadeEasing);
            }

            var initialCursor = ImGui.GetCursorPos();

            ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)shadeEasing.Value);
            ImGui.Image(this.shadeTexture.ImGuiHandle, new Vector2(this.shadeTexture.Width * scale, this.shadeTexture.Height * scale));
            ImGui.PopStyleVar();

            var isHover = ImGui.IsItemHovered();
            if (isHover && (!shadeEasing.IsRunning || (shadeEasing.IsDone && shadeEasing.IsInverse)) && !inhibitFadeout)
            {
                shadeEasing.IsInverse = false;
                shadeEasing.Restart();
            }
            else if (!isHover && !shadeEasing.IsInverse && shadeEasing.IsRunning && !inhibitFadeout)
            {
                shadeEasing.IsInverse = true;
                shadeEasing.Restart();
            }

            var isClick = ImGui.IsItemClicked();
            if (isClick)
            {
                entry.Trigger();
            }

            shadeEasing.Update();

            if (!this.logoEasings.TryGetValue(entry.Id, out var logoEasing))
            {
                logoEasing = new InOutCubic(TimeSpan.FromMilliseconds(350));
                this.logoEasings.Add(entry.Id, logoEasing);
            }

            if (!logoEasing.IsRunning && !logoEasing.IsDone)
            {
                logoEasing.Restart();
            }

            if (logoEasing.IsDone)
            {
                logoEasing.Stop();
            }

            logoEasing.Update();

            ImGui.SetCursorPos(initialCursor);
            ImGuiHelpers.ScaledDummy(5);
            ImGui.SameLine();

            if (overrideAlpha)
            {
                ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.Value);
            }
            else if (isFirst)
            {
                ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
            }

            ImGui.Image(entry.Texture.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale));
            if (overrideAlpha || isFirst)
            {
                ImGui.PopStyleVar();
            }

            ImGui.SameLine();

            ImGuiHelpers.ScaledDummy(10);
            ImGui.SameLine();

            var textHeight = ImGui.GetTextLineHeightWithSpacing();
            var cursor = ImGui.GetCursorPos();

            cursor.Y += (entry.Texture.Height * scale / 2) - (textHeight / 2);

            if (overrideAlpha)
            {
                ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f);
            }

            // Drop shadow
            ImGui.PushStyleColor(ImGuiCol.Text, 0xFF000000);
            for (int i = 0, i_ = (int)Math.Ceiling(1 * scale); i < i_; i++)
            {
                ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i));
                ImGui.Text(entry.Name);
            }

            ImGui.PopStyleColor();

            ImGui.SetCursorPos(cursor);
            ImGui.Text(entry.Name);

            if (overrideAlpha)
            {
                ImGui.PopStyleVar();
            }

            initialCursor.Y += entry.Texture.Height * scale;
            ImGui.SetCursorPos(initialCursor);

            ImGui.PopFont();

            return isHover;
        }

        private void FrameworkOnUpdate(Framework framework)
        {
            var clientState = Service<ClientState>.Get();
            this.IsOpen = !clientState.IsLoggedIn;

            var configuration = Service<DalamudConfiguration>.Get();
            if (!configuration.ShowTsm)
                this.IsOpen = false;

            var gameGui = Service<GameGui>.Get();
            var charaSelect = gameGui.GetAddonByName("CharaSelect", 1);
            var charaMake = gameGui.GetAddonByName("CharaMake", 1);
            var titleDcWorldMap = gameGui.GetAddonByName("TitleDCWorldMap", 1);
            if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
                this.IsOpen = false;
        }
    }
}
