// 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.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
using osu.Game.Skinning;
using osu.Game.Users;

namespace osu.Game.Screens.Backgrounds
{
    public class BackgroundScreenDefault : BackgroundScreen
    {
        private Background background;

        private int currentDisplay;
        private const int background_count = 7;
        private IBindable<User> user;
        private Bindable<Skin> skin;
        private Bindable<BackgroundSource> mode;
        private Bindable<IntroSequence> introSequence;
        private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader();

        [Resolved]
        private IBindable<WorkingBeatmap> beatmap { get; set; }

        public BackgroundScreenDefault(bool animateOnEnter = true)
            : base(animateOnEnter)
        {
        }

        [BackgroundDependencyLoader]
        private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config)
        {
            user = api.LocalUser.GetBoundCopy();
            skin = skinManager.CurrentSkin.GetBoundCopy();
            mode = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource);
            introSequence = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence);

            AddInternal(seasonalBackgroundLoader);

            user.ValueChanged += _ => Next();
            skin.ValueChanged += _ => Next();
            mode.ValueChanged += _ => Next();
            beatmap.ValueChanged += _ => Next();
            introSequence.ValueChanged += _ => Next();
            seasonalBackgroundLoader.SeasonalBackgroundChanged += Next;

            currentDisplay = RNG.Next(0, background_count);

            Next();
        }

        private void display(Background newBackground)
        {
            background?.FadeOut(800, Easing.InOutSine);
            background?.Expire();

            AddInternal(background = newBackground);
            currentDisplay++;
        }

        private ScheduledDelegate nextTask;
        private CancellationTokenSource cancellationTokenSource;

        public void Next()
        {
            nextTask?.Cancel();
            cancellationTokenSource?.Cancel();
            cancellationTokenSource = new CancellationTokenSource();
            nextTask = Scheduler.AddDelayed(() => LoadComponentAsync(createBackground(), display, cancellationTokenSource.Token), 100);
        }

        private Background createBackground()
        {
            Background newBackground;
            string backgroundName;

            var seasonalBackground = seasonalBackgroundLoader.LoadNextBackground();

            if (seasonalBackground != null)
            {
                seasonalBackground.Depth = currentDisplay;
                return seasonalBackground;
            }

            switch (introSequence.Value)
            {
                case IntroSequence.Welcome:
                    backgroundName = "Intro/Welcome/menu-background";
                    break;

                default:
                    backgroundName = $@"Menu/menu-background-{currentDisplay % background_count + 1}";
                    break;
            }

            if (user.Value?.IsSupporter ?? false)
            {
                switch (mode.Value)
                {
                    case BackgroundSource.Beatmap:
                        newBackground = new BeatmapBackground(beatmap.Value, backgroundName);
                        break;

                    default:
                        newBackground = new SkinnedBackground(skin.Value, backgroundName);
                        break;
                }
            }
            else
                newBackground = new Background(backgroundName);

            newBackground.Depth = currentDisplay;

            return newBackground;
        }

        private class SkinnedBackground : Background
        {
            private readonly Skin skin;

            public SkinnedBackground(Skin skin, string fallbackTextureName)
                : base(fallbackTextureName)
            {
                this.skin = skin;
            }

            [BackgroundDependencyLoader]
            private void load()
            {
                Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture;
            }
        }
    }
}