// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Resources.Localisation.Web;
using osuTK;

namespace osu.Game.Overlays.Toolbar
{
    public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable
    {
        public Bindable<ScoreBasedUserStatisticsUpdate?> LatestUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();

        private Statistic<int> globalRank = null!;
        private Statistic<int> pp = null!;

        private ScheduledDelegate? shrinkDelegate;

        [BackgroundDependencyLoader]
        private void load(UserStatisticsWatcher? userStatisticsWatcher)
        {
            RelativeSizeAxes = Axes.Y;

            InternalChild = new FillFlowContainer
            {
                RelativeSizeAxes = Axes.Y,
                AutoSizeAxes = Axes.X,
                Padding = new MarginPadding { Horizontal = 10 },
                Spacing = new Vector2(10),
                Direction = FillDirection.Horizontal,
                Children = new[]
                {
                    globalRank = new Statistic<int>(UsersStrings.ShowRankGlobalSimple, @"#", Comparer<int>.Create((before, after) => before - after)),
                    pp = new Statistic<int>(RankingsStrings.StatPerformance, string.Empty, Comparer<int>.Create((before, after) => Math.Sign(after - before))),
                }
            };

            if (userStatisticsWatcher != null)
                ((IBindable<ScoreBasedUserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
        }

        protected override void LoadComplete()
        {
            base.LoadComplete();

            LatestUpdate.BindValueChanged(val =>
            {
                if (val.NewValue == null)
                    return;

                var update = val.NewValue;

                // null handling here is best effort because it is annoying.

                globalRank.Alpha = update.After.GlobalRank == null ? 0 : 1;
                pp.Alpha = update.After.PP == null ? 0 : 1;

                if (globalRank.Alpha == 0 && pp.Alpha == 0)
                    return;

                FinishTransforms(true);
                shrinkDelegate?.Cancel();

                if (update.After.GlobalRank != null)
                {
                    globalRank.Display(
                        update.Before.GlobalRank ?? update.After.GlobalRank.Value,
                        Math.Abs((update.After.GlobalRank.Value - update.Before.GlobalRank) ?? 0),
                        update.After.GlobalRank.Value);
                }

                if (update.After.PP != null)
                {
                    int before = (int)Math.Round(update.Before.PP ?? update.After.PP.Value);
                    int after = (int)Math.Round(update.After.PP.Value);
                    int delta = Math.Abs(after - before);
                    pp.Display(before, delta, after);
                }

                this.FadeIn(500, Easing.OutQuint);

                AutoSizeAxes = Axes.X;
                AutoSizeDuration = 500;
                AutoSizeEasing = Easing.OutQuint;

                using (BeginDelayedSequence(5000))
                {
                    this.FadeOut(500, Easing.OutQuint);
                    shrinkDelegate = Schedule(() =>
                    {
                        AutoSizeAxes = Axes.None;
                        this.ResizeWidthTo(0, 500, Easing.OutQuint);
                    });
                }
            });
        }

        private partial class Statistic<T> : CompositeDrawable
            where T : struct, IEquatable<T>, IFormattable
        {
            private readonly LocalisableString title;
            private readonly string mainValuePrefix;
            private readonly IComparer<T> valueComparer;

            private Counter<T> mainValue = null!;
            private Counter<T> deltaValue = null!;
            private OsuSpriteText titleText = null!;
            private ScheduledDelegate? valueUpdateSchedule;

            [Resolved]
            private OsuColour colours { get; set; } = null!;

            public Statistic(LocalisableString title, string mainValuePrefix, IComparer<T> valueComparer)
            {
                this.title = title;
                this.mainValuePrefix = mainValuePrefix;
                this.valueComparer = valueComparer;
            }

            [BackgroundDependencyLoader]
            private void load()
            {
                RelativeSizeAxes = Axes.Y;
                AutoSizeAxes = Axes.X;
                InternalChild = new FillFlowContainer
                {
                    AutoSizeAxes = Axes.Both,
                    Anchor = Anchor.CentreRight,
                    Origin = Anchor.CentreRight,
                    Direction = FillDirection.Vertical,
                    Children = new Drawable[]
                    {
                        mainValue = new Counter<T>
                        {
                            ValuePrefix = mainValuePrefix,
                            Anchor = Anchor.CentreRight,
                            Origin = Anchor.CentreRight,
                        },
                        new Container
                        {
                            AutoSizeAxes = Axes.Both,
                            Anchor = Anchor.CentreRight,
                            Origin = Anchor.CentreRight,
                            Children = new Drawable[]
                            {
                                deltaValue = new Counter<T>
                                {
                                    Anchor = Anchor.CentreRight,
                                    Origin = Anchor.CentreRight,
                                    Font = OsuFont.Default.With(size: 12, fixedWidth: true, weight: FontWeight.SemiBold),
                                    AlwaysPresent = true,
                                },
                                titleText = new OsuSpriteText
                                {
                                    Anchor = Anchor.CentreRight,
                                    Origin = Anchor.CentreRight,
                                    Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
                                    Text = title,
                                    AlwaysPresent = true,
                                }
                            }
                        }
                    }
                };
            }

            public void Display(T before, T delta, T after)
            {
                valueUpdateSchedule?.Cancel();
                valueUpdateSchedule = null;

                int comparison = valueComparer.Compare(before, after);

                if (comparison > 0)
                {
                    deltaValue.Colour = colours.Lime1;
                    deltaValue.ValuePrefix = "+";
                }
                else if (comparison < 0)
                {
                    deltaValue.Colour = colours.Red1;
                    deltaValue.ValuePrefix = "-";
                }
                else
                {
                    deltaValue.Colour = Colour4.White;
                    deltaValue.ValuePrefix = string.Empty;
                }

                mainValue.SetCountWithoutRolling(before);
                deltaValue.SetCountWithoutRolling(delta);

                titleText.Alpha = 1;
                deltaValue.Alpha = 0;

                using (BeginDelayedSequence(1200))
                {
                    titleText.FadeOut(250, Easing.OutQuad);
                    deltaValue.FadeIn(250, Easing.OutQuad);

                    using (BeginDelayedSequence(1250))
                    {
                        valueUpdateSchedule = Schedule(() =>
                        {
                            mainValue.Current.Value = after;
                            deltaValue.Current.SetDefault();
                        });
                    }
                }
            }
        }

        private partial class Counter<T> : RollingCounter<T>
            where T : struct, IEquatable<T>, IFormattable
        {
            public FontUsage Font { get; init; } = OsuFont.Default.With(fixedWidth: true);

            public string ValuePrefix
            {
                get => valuePrefix;
                set
                {
                    valuePrefix = value;
                    UpdateDisplay();
                }
            }

            private string valuePrefix = string.Empty;

            protected override LocalisableString FormatCount(T count) => LocalisableString.Format(@"{0}{1:N0}", ValuePrefix, count);

            protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(t =>
            {
                t.Font = Font;
                t.Spacing = new Vector2(-1.5f, 0);
            });

            protected override double RollingDuration => 1500;
        }
    }
}
