using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Aetherium.Interface.Colors;
using Aetherium.Interface.FontAwesome;
using Aetherium.Utility;
using ImGuiNET;

namespace Aetherium.Interface.Internal.Notifications;

/// <summary>
/// Class handling notifications/toasts in ImGui.
/// Ported from https://github.com/patrickcjk/imgui-notify.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class NotificationManager : IServiceType
{
    /// <summary>
    /// Value indicating the bottom-left X padding.
    /// </summary>
    internal const float NotifyPaddingX = 20.0f;

    /// <summary>
    /// Value indicating the bottom-left Y padding.
    /// </summary>
    internal const float NotifyPaddingY = 20.0f;

    /// <summary>
    /// Value indicating the Y padding between each message.
    /// </summary>
    internal const float NotifyPaddingMessageY = 10.0f;

    /// <summary>
    /// Value indicating the fade-in and out duration.
    /// </summary>
    internal const int NotifyFadeInOutTime = 500;

    /// <summary>
    /// Value indicating the default time until the notification is dismissed.
    /// </summary>
    internal const int NotifyDefaultDismiss = 3000;

    /// <summary>
    /// Value indicating the maximum opacity.
    /// </summary>
    internal const float NotifyOpacity = 0.82f;

    /// <summary>
    /// Value indicating default window flags for the notifications.
    /// </summary>
    internal const ImGuiWindowFlags NotifyToastFlags =
        ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs |
        ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing;

    private readonly List<Notification> notifications = new();

    [ServiceManager.ServiceConstructor]
    private NotificationManager()
    {
    }

    /// <summary>
    /// Add a notification to the notification queue.
    /// </summary>
    /// <param name="content">The content of the notification.</param>
    /// <param name="title">The title of the notification.</param>
    /// <param name="type">The type of the notification.</param>
    /// <param name="msDelay">The time the notification should be displayed for.</param>
    public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss)
    {
        this.notifications.Add(new Notification
        {
            Content = content,
            Title = title,
            NotificationType = type,
            DurationMs = msDelay,
        });
    }

    /// <summary>
    /// Draw all currently queued notifications.
    /// </summary>
    public void Draw()
    {
        var viewportSize = ImGuiHelpers.MainViewport.Size;
        var height = 0f;

        for (var i = 0; i < this.notifications.Count; i++)
        {
            var tn = this.notifications.ElementAt(i);

            if (tn.GetPhase() == Notification.Phase.Expired)
            {
                this.notifications.RemoveAt(i);
                continue;
            }

            var opacity = tn.GetFadePercent();

            var iconColor = tn.Color;
            iconColor.W = opacity;

            var windowName = $"##NOTIFY{i}";

            ImGuiHelpers.ForceNextWindowMainViewport();
            ImGui.SetNextWindowBgAlpha(opacity);
            ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One);
            ImGui.Begin(windowName, NotifyToastFlags);

            ImGui.PushTextWrapPos(viewportSize.X / 3.0f);

            var wasTitleRendered = false;

            if (!tn.Icon.IsNullOrEmpty())
            {
                wasTitleRendered = true;
                ImGui.TextColored(iconColor, tn.Icon);
            }

            var textColor = ImGuiColors.AetheriumWhite;
            textColor.W = opacity;

            ImGui.PushStyleColor(ImGuiCol.Text, textColor);

            if (!tn.Title.IsNullOrEmpty())
            {
                if (!tn.Icon.IsNullOrEmpty())
                {
                    ImGui.SameLine();
                }

                ImGui.TextUnformatted(tn.Title);
                wasTitleRendered = true;
            }
            else if (!tn.DefaultTitle.IsNullOrEmpty())
            {
                if (!tn.Icon.IsNullOrEmpty())
                {
                    ImGui.SameLine();
                }

                ImGui.TextUnformatted(tn.DefaultTitle);
                wasTitleRendered = true;
            }

            if (wasTitleRendered && !tn.Content.IsNullOrEmpty())
            {
                ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f);
            }

            if (!tn.Content.IsNullOrEmpty())
            {
                if (wasTitleRendered)
                {
                    ImGui.Separator();
                }

                ImGui.TextUnformatted(tn.Content);
            }

            ImGui.PopStyleColor();

            ImGui.PopTextWrapPos();

            height += ImGui.GetWindowHeight() + NotifyPaddingMessageY;

            ImGui.End();
        }
    }

    /// <summary>
    /// Container class for notifications.
    /// </summary>
    internal class Notification
    {
        /// <summary>
        /// Possible notification phases.
        /// </summary>
        internal enum Phase
        {
            /// <summary>
            /// Phase indicating fade-in.
            /// </summary>
            FadeIn,

            /// <summary>
            /// Phase indicating waiting until fade-out.
            /// </summary>
            Wait,

            /// <summary>
            /// Phase indicating fade-out.
            /// </summary>
            FadeOut,

            /// <summary>
            /// Phase indicating that the notification has expired.
            /// </summary>
            Expired,
        }

        /// <summary>
        /// Gets the type of the notification.
        /// </summary>
        internal NotificationType NotificationType { get; init; }

        /// <summary>
        /// Gets the title of the notification.
        /// </summary>
        internal string? Title { get; init; }

        /// <summary>
        /// Gets the content of the notification.
        /// </summary>
        internal string Content { get; init; }

        /// <summary>
        /// Gets the duration of the notification in milliseconds.
        /// </summary>
        internal uint DurationMs { get; init; }

        /// <summary>
        /// Gets the creation time of the notification.
        /// </summary>
        internal DateTime CreationTime { get; init; } = DateTime.Now;

        /// <summary>
        /// Gets the default color of the notification.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
        internal Vector4 Color => this.NotificationType switch
        {
            NotificationType.None => ImGuiColors.AetheriumWhite,
            NotificationType.Success => ImGuiColors.HealerGreen,
            NotificationType.Warning => ImGuiColors.AetheriumOrange,
            NotificationType.Error => ImGuiColors.AetheriumBlue,
            NotificationType.Info => ImGuiColors.TankBlue,
            _ => throw new ArgumentOutOfRangeException(),
        };

        /// <summary>
        /// Gets the icon of the notification.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
        internal string? Icon => this.NotificationType switch
        {
            NotificationType.None => null,
            NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(),
            NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(),
            NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(),
            NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(),
            _ => throw new ArgumentOutOfRangeException(),
        };

        /// <summary>
        /// Gets the default title of the notification.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
        internal string? DefaultTitle => this.NotificationType switch
        {
            NotificationType.None => null,
            NotificationType.Success => NotificationType.Success.ToString(),
            NotificationType.Warning => NotificationType.Warning.ToString(),
            NotificationType.Error => NotificationType.Error.ToString(),
            NotificationType.Info => NotificationType.Info.ToString(),
            _ => throw new ArgumentOutOfRangeException(),
        };

        /// <summary>
        /// Gets the elapsed time since creating the notification.
        /// </summary>
        internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime;

        /// <summary>
        /// Gets the phase of the notification.
        /// </summary>
        /// <returns>The phase of the notification.</returns>
        internal Phase GetPhase()
        {
            var elapsed = (int)this.ElapsedTime.TotalMilliseconds;

            if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime)
                return Phase.Expired;
            else if (elapsed > NotifyFadeInOutTime + this.DurationMs)
                return Phase.FadeOut;
            else if (elapsed > NotifyFadeInOutTime)
                return Phase.Wait;
            else
                return Phase.FadeIn;
        }

        /// <summary>
        /// Gets the opacity of the notification.
        /// </summary>
        /// <returns>The opacity, in a range from 0 to 1.</returns>
        internal float GetFadePercent()
        {
            var phase = this.GetPhase();
            var elapsed = this.ElapsedTime.TotalMilliseconds;

            if (phase == Phase.FadeIn)
            {
                return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity;
            }
            else if (phase == Phase.FadeOut)
            {
                return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) /
                                NotifyFadeInOutTime)) * NotifyOpacity;
            }

            return 1.0f * NotifyOpacity;
        }
    }
}
