// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using osu.Game.Screens.Select.Details;
using osuTK;
using osuTK.Graphics;

namespace osu.Game.Screens.Select
{
    public class BeatmapDetails : Container
    {
        private const float spacing = 10;
        private const float transition_duration = 250;

        private readonly AdvancedStats advanced;
        private readonly UserRatings ratings;
        private readonly MetadataSection description, source, tags;
        private readonly Container failRetryContainer;
        private readonly FailRetryGraph failRetryGraph;
        private readonly LoadingLayer loading;

        [Resolved]
        private IAPIProvider api { get; set; }

        [Resolved]
        private RulesetStore rulesets { get; set; }

        private BeatmapInfo beatmap;

        public BeatmapInfo Beatmap
        {
            get => beatmap;
            set
            {
                if (value == beatmap) return;

                beatmap = value;

                Scheduler.AddOnce(updateStatistics);
            }
        }

        public BeatmapDetails()
        {
            Children = new Drawable[]
            {
                new Box
                {
                    RelativeSizeAxes = Axes.Both,
                    Colour = Color4.Black.Opacity(0.5f),
                },
                new Container
                {
                    RelativeSizeAxes = Axes.Both,
                    Padding = new MarginPadding { Horizontal = spacing },
                    Children = new Drawable[]
                    {
                        new GridContainer
                        {
                            RelativeSizeAxes = Axes.Both,
                            RowDimensions = new[]
                            {
                                new Dimension(GridSizeMode.AutoSize),
                                new Dimension()
                            },
                            Content = new[]
                            {
                                new Drawable[]
                                {
                                    new FillFlowContainer
                                    {
                                        RelativeSizeAxes = Axes.X,
                                        AutoSizeAxes = Axes.Y,
                                        Direction = FillDirection.Horizontal,
                                        Children = new Drawable[]
                                        {
                                            new FillFlowContainer
                                            {
                                                RelativeSizeAxes = Axes.X,
                                                AutoSizeAxes = Axes.Y,
                                                Width = 0.5f,
                                                Spacing = new Vector2(spacing),
                                                Padding = new MarginPadding { Right = spacing / 2 },
                                                Children = new[]
                                                {
                                                    new DetailBox().WithChild(advanced = new AdvancedStats
                                                    {
                                                        RelativeSizeAxes = Axes.X,
                                                        AutoSizeAxes = Axes.Y,
                                                        Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing },
                                                    }),
                                                    new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
                                                    {
                                                        RelativeSizeAxes = Axes.X,
                                                        Height = 134,
                                                        Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
                                                        Child = ratings = new UserRatings
                                                        {
                                                            RelativeSizeAxes = Axes.Both,
                                                        },
                                                    }),
                                                },
                                            },
                                            new OsuScrollContainer
                                            {
                                                RelativeSizeAxes = Axes.Both,
                                                Width = 0.5f,
                                                ScrollbarVisible = false,
                                                Padding = new MarginPadding { Left = spacing / 2 },
                                                Child = new FillFlowContainer
                                                {
                                                    RelativeSizeAxes = Axes.X,
                                                    AutoSizeAxes = Axes.Y,
                                                    LayoutDuration = transition_duration,
                                                    LayoutEasing = Easing.OutQuad,
                                                    Children = new[]
                                                    {
                                                        description = new MetadataSection(MetadataType.Description),
                                                        source = new MetadataSection(MetadataType.Source),
                                                        tags = new MetadataSection(MetadataType.Tags),
                                                    },
                                                },
                                            },
                                        },
                                    },
                                },
                                new Drawable[]
                                {
                                    failRetryContainer = new OnlineViewContainer("Sign in to view more details")
                                    {
                                        RelativeSizeAxes = Axes.Both,
                                        Children = new Drawable[]
                                        {
                                            new OsuSpriteText
                                            {
                                                Text = "Points of Failure",
                                                Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
                                            },
                                            failRetryGraph = new FailRetryGraph
                                            {
                                                RelativeSizeAxes = Axes.Both,
                                                Padding = new MarginPadding { Top = 14 + spacing / 2 },
                                            },
                                        },
                                    },
                                }
                            }
                        },
                    },
                },
                loading = new LoadingLayer(true)
            };
        }

        private void updateStatistics()
        {
            advanced.Beatmap = Beatmap;
            description.Text = Beatmap?.Version;
            source.Text = Beatmap?.Metadata?.Source;
            tags.Text = Beatmap?.Metadata?.Tags;

            // metrics may have been previously fetched
            if (Beatmap?.BeatmapSet?.Metrics != null && Beatmap?.Metrics != null)
            {
                updateMetrics();
                return;
            }

            // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time).
            if (Beatmap?.OnlineBeatmapID == null || api.State.Value == APIState.Offline)
            {
                updateMetrics();
                return;
            }

            var requestedBeatmap = Beatmap;

            var lookup = new GetBeatmapRequest(requestedBeatmap);

            lookup.Success += res =>
            {
                Schedule(() =>
                {
                    if (beatmap != requestedBeatmap)
                        // the beatmap has been changed since we started the lookup.
                        return;

                    var b = res.ToBeatmap(rulesets);

                    if (requestedBeatmap.BeatmapSet == null)
                        requestedBeatmap.BeatmapSet = b.BeatmapSet;
                    else
                        requestedBeatmap.BeatmapSet.Metrics = b.BeatmapSet.Metrics;

                    requestedBeatmap.Metrics = b.Metrics;

                    updateMetrics();
                });
            };

            lookup.Failure += e =>
            {
                Schedule(() =>
                {
                    if (beatmap != requestedBeatmap)
                        // the beatmap has been changed since we started the lookup.
                        return;

                    updateMetrics();
                });
            };

            api.Queue(lookup);
            loading.Show();
        }

        private void updateMetrics()
        {
            var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false;
            var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false);

            if (hasRatings)
            {
                ratings.Metrics = beatmap.BeatmapSet.Metrics;
                ratings.FadeIn(transition_duration);
            }
            else
            {
                // loading or just has no data server-side.
                ratings.Metrics = new BeatmapSetMetrics { Ratings = new int[10] };
                ratings.FadeTo(0.25f, transition_duration);
            }

            if (hasRetriesFails)
            {
                failRetryGraph.Metrics = beatmap.Metrics;
                failRetryContainer.FadeIn(transition_duration);
            }
            else
            {
                failRetryGraph.Metrics = new BeatmapMetrics
                {
                    Fails = new int[100],
                    Retries = new int[100],
                };
            }

            loading.Hide();
        }

        private class DetailBox : Container
        {
            private readonly Container content;
            protected override Container<Drawable> Content => content;

            public DetailBox()
            {
                RelativeSizeAxes = Axes.X;
                AutoSizeAxes = Axes.Y;

                InternalChildren = new Drawable[]
                {
                    new Box
                    {
                        RelativeSizeAxes = Axes.Both,
                        Colour = Color4.Black.Opacity(0.5f),
                    },
                    content = new Container
                    {
                        RelativeSizeAxes = Axes.X,
                        AutoSizeAxes = Axes.Y,
                    },
                };
            }
        }
    }
}