using System;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using Dalamud.Bindings.ImGui;
using System.Collections.Generic;
using System.Numerics;
using HSUI.Config;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;

namespace HSUI.Interface.GeneralElements
{
    public class GCDIndicatorHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
    {
        private GCDIndicatorConfig Config => (GCDIndicatorConfig)_config;
        public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;

        public IGameObject? Actor { get; set; } = null;

        private bool _wasBarEnabled = true;
        private bool _wasCircularModeEnabled = false;
        private float _lastTotalCastTime = 0;

        public GCDIndicatorHud(GCDIndicatorConfig config, string displayName) : base(config, displayName) { }

        protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
        {
            var (pos, size) = GetPositionAndSize(Vector2.Zero);

            if (Config.CircularMode)
            {
                pos -= size / 2f;
            }

            return (new List<Vector2>() { pos }, new List<Vector2>() { size });
        }

        private (Vector2, Vector2) GetPositionAndSize(Vector2 origin)
        {
            Vector2 pos = Config.AnchorToMouse ? ImGui.GetMousePos() + Config.Position : origin + Config.Position;
            Vector2 size = Config.Bar.Size;

            if (Config.CircularMode)
            {
                size = new Vector2(Config.CircleRadius * 2, Config.CircleRadius * 2);
                pos += size / 2f;
            }

            return (pos, size);
        }

        protected override void DrawDraggableArea(Vector2 origin)
        {
            if (Config.AnchorToMouse)
            {
                return;
            }

            base.DrawDraggableArea(origin);
        }

        public override void DrawChildren(Vector2 origin)
        {
            CheckToggles();

            if (!Config.Enabled || Actor == null || Actor is not IPlayerCharacter)
            {
                return;
            }

            GCDHelper.GetGCDInfo((IPlayerCharacter)Actor, out var elapsed, out var total);

            if (!Config.AlwaysShow && total == 0)
            {
                _lastTotalCastTime = 0;
                return;
            }

            if (_lastTotalCastTime == 0 && Utils.IsActorCasting(Actor))
            {
                _lastTotalCastTime = ((IBattleChara)Actor).TotalCastTime;
            }

            var scale = elapsed / total;
            if (scale <= 0)
            {
                _lastTotalCastTime = 0;
                return;
            }

            bool instantGCDsOnly = Config.InstantGCDsOnly && _lastTotalCastTime != 0;
            bool thresholdGCDs = Config.LimitGCDThreshold && _lastTotalCastTime > Config.GCDThreshold;

            if (instantGCDsOnly || thresholdGCDs)
            {
                if (Config.AlwaysShow)
                {
                    elapsed = 0;
                    total = 0;
                }
                else
                {
                    return;
                }
            }

            Config.Bar.Position = Config.Position;
            Config.Bar.Anchor = Config.Anchor;
            Config.Bar.BackgroundColor = Config.BackgroundColor;
            Config.Bar.FillColor = Config.FillColor;
            Config.Bar.DrawBorder = Config.ShowBorder;

            if (Config.Bar.Enabled)
            {
                DrawNormalBar(origin, elapsed, total);
            }
            else
            {
                var (pos, size) = GetPositionAndSize(origin);
                pos = Utils.GetAnchoredPosition(pos, size, Config.Anchor);

                AddDrawAction(_config.StrataLevel, () =>
                {
                    DrawCircularIndicator(pos, Config.CircleRadius, elapsed, total);
                });
            }
        }

        private void CheckToggles()
        {
            bool barEnabledChanged = _wasBarEnabled != Config.Bar.Enabled;
            if (barEnabledChanged)
            {
                Config.CircularMode = !Config.Bar.Enabled;
            }
            else
            {
                bool circularModeChanged = _wasCircularModeEnabled != Config.CircularMode;
                if (circularModeChanged)
                {
                    Config.Bar.Enabled = !Config.CircularMode;
                }
            }

            _wasBarEnabled = Config.Bar.Enabled;
            _wasCircularModeEnabled = Config.CircularMode;
        }

        private void DrawCircularIndicator(Vector2 position, float radius, float current, float total)
        {
            total = Config.AlwaysShow && total == 0 ? 1 : total;
            current = Config.AlwaysShow && current == 0 ? total : current;

            var size = new Vector2(radius * 2);
            DrawHelper.DrawInWindow(ID, position - size / 2, size, false, (drawList) =>
            {
                current = Math.Min(current, total);

                // controls how smooth the arc looks
                const int segments = 100;
                const float queueTime = 0.5f;
                float startAngle = 0f;
                float endAngle = 2f * (float)Math.PI;
                float offset = (float)(-Math.PI / 2f + (Config.CircleStartAngle * (Math.PI / 180f)));

                if (Config.RotateCCW)
                {
                    startAngle *= -1;
                    endAngle *= -1;
                }

                if (Config.AlwaysShow && current == total)
                {
                    drawList.PathArcTo(position, radius, startAngle + offset, endAngle + offset, segments);
                    drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);
                }
                else
                {
                    // always draw until the queue threshold
                    float progressAngle = Math.Min(current, total - (Config.ShowGCDQueueIndicator ? queueTime : 0f)) / total * endAngle;

                    // drawing an arc with thickness to make it look like an annular sector
                    drawList.PathArcTo(position, radius, startAngle + offset, progressAngle + offset, segments);
                    drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);

                    // draw the queue indicator
                    if (Config.ShowGCDQueueIndicator && current > total - queueTime)
                    {
                        float oldAngle = progressAngle - 0.0003f * total * endAngle;
                        progressAngle = current / total * endAngle;
                        drawList.PathArcTo(position, radius, oldAngle + offset, progressAngle + offset, segments);
                        drawList.PathStroke(Config.QueueColor.Base, ImDrawFlags.None, Config.CircleThickness);
                    }

                    // anything that remains is background
                    drawList.PathArcTo(position, radius, progressAngle + offset, endAngle + offset, segments);
                    drawList.PathStroke(Config.BackgroundColor.Base, ImDrawFlags.None, Config.CircleThickness);
                }

                if (Config.ShowBorder)
                {
                    drawList.PathArcTo(position, radius - Config.CircleThickness / 2f, 0, endAngle, segments);
                    drawList.PathStroke(0xFF000000, ImDrawFlags.None, 1);

                    drawList.PathArcTo(position, radius + Config.CircleThickness / 2f, 0, endAngle, segments);
                    drawList.PathStroke(0xFF000000, ImDrawFlags.None, 1);
                }
            });
        }

        private void DrawNormalBar(Vector2 origin, float current, float total)
        {
            GCDBarConfig config = Config.Bar;

            Rect mainRect = BarUtilities.GetFillRect(config.Position, config.Size, config.FillDirection, config.FillColor, current, total, 0);
            BarHud bar = new BarHud(config, null, null);
            bar.AddForegrounds(mainRect);

            float currentPercent = current / total;
            float percentNonQueue = total != 0 ? 1F - (500f / 1000f) / total : 0;

            if (percentNonQueue > 0 && currentPercent >= percentNonQueue && Config.ShowGCDQueueIndicator)
            {
                float scale = 1 - percentNonQueue;
                Vector2 size = config.FillDirection.IsHorizontal() ?
                    new Vector2(config.Size.X * scale, config.Size.Y) :
                    new Vector2(config.Size.X, config.Size.Y * scale);

                Vector2 pos = config.Position;
                if (config.FillDirection == BarDirection.Right)
                {
                    pos.X += config.Size.X * percentNonQueue;
                }
                else if (config.FillDirection == BarDirection.Down)
                {
                    pos.Y += config.Size.Y * percentNonQueue;
                }

                Rect foreground = BarUtilities.GetFillRect(pos, size, config.FillDirection, Config.QueueColor, currentPercent - percentNonQueue, scale, 0);
                bar.AddForegrounds(foreground);
            }

            AddDrawActions(bar.GetDrawActions(origin, _config.StrataLevel));
        }
    }
}
