// 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.Threading.Tasks;
using osu.Framework.Graphics;
using osu.Framework.Threading;

namespace osu.Game.Online
{
    /// <summary>
    /// A component which requires a constant polling process.
    /// </summary>
    public abstract class PollingComponent : Component
    {
        private double? lastTimePolled;

        private ScheduledDelegate scheduledPoll;

        private bool pollingActive;

        private double timeBetweenPolls;

        /// <summary>
        /// The time in milliseconds to wait between polls.
        /// Setting to zero stops all polling.
        /// </summary>
        public double TimeBetweenPolls
        {
            get => timeBetweenPolls;
            set
            {
                timeBetweenPolls = value;
                scheduledPoll?.Cancel();
                pollIfNecessary();
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops all polling.</param>
        protected PollingComponent(double timeBetweenPolls = 0)
        {
            TimeBetweenPolls = timeBetweenPolls;
        }

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

        private bool pollIfNecessary()
        {
            // we must be loaded so we have access to clock.
            if (!IsLoaded) return false;

            // there's already a poll process running.
            if (pollingActive) return false;

            // don't try polling if the time between polls hasn't been set.
            if (timeBetweenPolls == 0) return false;

            if (!lastTimePolled.HasValue)
            {
                doPoll();
                return true;
            }

            if (Time.Current - lastTimePolled.Value > timeBetweenPolls)
            {
                doPoll();
                return true;
            }

            // not ennough time has passed since the last poll. we do want to schedule a poll to happen, though.
            scheduleNextPoll();
            return false;
        }

        private void doPoll()
        {
            scheduledPoll = null;
            pollingActive = true;
            Poll().ContinueWith(_ => pollComplete());
        }

        /// <summary>
        /// Performs a poll. Implement but do not call this.
        /// </summary>
        protected virtual Task Poll()
        {
            return Task.CompletedTask;
        }

        /// <summary>
        /// Immediately performs a <see cref="Poll"/>.
        /// </summary>
        public void PollImmediately()
        {
            lastTimePolled = Time.Current - timeBetweenPolls;
            scheduleNextPoll();
        }

        /// <summary>
        /// Call when a poll operation has completed.
        /// </summary>
        private void pollComplete()
        {
            lastTimePolled = Time.Current;
            pollingActive = false;

            if (scheduledPoll == null)
                pollIfNecessary();
        }

        private void scheduleNextPoll()
        {
            scheduledPoll?.Cancel();

            double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;

            scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration));
        }
    }
}