// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests;

namespace osu.Game.Overlays.Direct
{
    /// <summary>
    /// A component which tracks a beatmap through potential download/import/deletion.
    /// </summary>
    public abstract class DownloadTrackingComposite : CompositeDrawable
    {
        public readonly Bindable<BeatmapSetInfo> BeatmapSet = new Bindable<BeatmapSetInfo>();

        private BeatmapManager beatmaps;

        /// <summary>
        /// Holds the current download state of the beatmap, whether is has already been downloaded, is in progress, or is not downloaded.
        /// </summary>
        protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();

        protected readonly Bindable<double> Progress = new Bindable<double>();

        protected DownloadTrackingComposite(BeatmapSetInfo beatmapSet = null)
        {
            BeatmapSet.Value = beatmapSet;
        }

        [BackgroundDependencyLoader(true)]
        private void load(BeatmapManager beatmaps)
        {
            this.beatmaps = beatmaps;

            BeatmapSet.BindValueChanged(setInfo =>
            {
                if (setInfo.NewValue == null)
                    attachDownload(null);
                else if (beatmaps.GetAllUsableBeatmapSetsEnumerable().Any(s => s.OnlineBeatmapSetID == setInfo.NewValue.OnlineBeatmapSetID))
                    State.Value = DownloadState.LocallyAvailable;
                else
                    attachDownload(beatmaps.GetExistingDownload(setInfo.NewValue));
            }, true);

            beatmaps.BeatmapDownloadBegan += download =>
            {
                if (download.BeatmapSet.OnlineBeatmapSetID == BeatmapSet.Value?.OnlineBeatmapSetID)
                    attachDownload(download);
            };

            beatmaps.ItemAdded += setAdded;
            beatmaps.ItemRemoved += setRemoved;
        }

        #region Disposal

        protected override void Dispose(bool isDisposing)
        {
            base.Dispose(isDisposing);

            if (beatmaps != null)
            {
                beatmaps.BeatmapDownloadBegan -= attachDownload;
                beatmaps.ItemAdded -= setAdded;
            }

            State.UnbindAll();

            attachDownload(null);
        }

        #endregion

        private DownloadBeatmapSetRequest attachedRequest;

        private void attachDownload(DownloadBeatmapSetRequest request)
        {
            if (attachedRequest != null)
            {
                attachedRequest.Failure -= onRequestFailure;
                attachedRequest.DownloadProgressed -= onRequestProgress;
                attachedRequest.Success -= onRequestSuccess;
            }

            attachedRequest = request;

            if (attachedRequest != null)
            {
                if (attachedRequest.Progress == 1)
                {
                    State.Value = DownloadState.Downloaded;
                    Progress.Value = 1;
                }
                else
                {
                    State.Value = DownloadState.Downloading;
                    Progress.Value = attachedRequest.Progress;

                    attachedRequest.Failure += onRequestFailure;
                    attachedRequest.DownloadProgressed += onRequestProgress;
                    attachedRequest.Success += onRequestSuccess;
                }
            }
            else
            {
                State.Value = DownloadState.NotDownloaded;
            }
        }

        private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Downloaded);

        private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress);

        private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));

        private void setAdded(BeatmapSetInfo s, bool existing) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);

        private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);

        private void setDownloadStateFromManager(BeatmapSetInfo s, DownloadState state) => Schedule(() =>
        {
            if (s.OnlineBeatmapSetID != BeatmapSet.Value?.OnlineBeatmapSetID)
                return;

            State.Value = state;
        });
    }
}