// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Framework.Utils;

namespace osu.Game.Screens.Play
{
    /// <summary>
    /// A clock which is used for gameplay elements that need to follow audio time 1:1.
    /// Exposed via DI by <see cref="GameplayClockContainer"/>.
    /// <remarks>
    /// The main purpose of this clock is to stop components using it from accidentally processing the main
    /// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
    /// </remarks>
    /// </summary>
    public class GameplayClock : IFrameBasedClock
    {
        internal readonly IFrameBasedClock UnderlyingClock;

        public readonly BindableBool IsPaused = new BindableBool();

        /// <summary>
        /// All adjustments applied to this clock which don't come from gameplay or mods.
        /// </summary>
        public virtual IEnumerable<Bindable<double>> NonGameplayAdjustments => Enumerable.Empty<Bindable<double>>();

        public GameplayClock(IFrameBasedClock underlyingClock)
        {
            UnderlyingClock = underlyingClock;
        }

        public double CurrentTime => UnderlyingClock.CurrentTime;

        public double Rate => UnderlyingClock.Rate;

        /// <summary>
        /// The rate of gameplay when playback is at 100%.
        /// This excludes any seeking / user adjustments.
        /// </summary>
        public double TrueGameplayRate
        {
            get
            {
                double baseRate = Rate;

                foreach (var adjustment in NonGameplayAdjustments)
                {
                    if (Precision.AlmostEquals(adjustment.Value, 0))
                        return 0;

                    baseRate /= adjustment.Value;
                }

                return baseRate;
            }
        }

        public bool IsRunning => UnderlyingClock.IsRunning;

        public void ProcessFrame()
        {
            // intentionally not updating the underlying clock (handled externally).
        }

        public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime;

        public double FramesPerSecond => UnderlyingClock.FramesPerSecond;

        public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo;

        public IClock Source => UnderlyingClock;
    }
}
