// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Game.Input.Bindings;

namespace osu.Game.Input
{
    /// <summary>
    /// Track whether the end-user is in an idle state, based on their last interaction with the game.
    /// </summary>
    public partial class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IKeyBindingHandler<GlobalAction>, IHandleGlobalKeyboardInput
    {
        private readonly double timeToIdle;

        private double lastInteractionTime;

        protected double TimeSpentIdle => Clock.CurrentTime - lastInteractionTime;

        /// <summary>
        /// Whether the user is currently in an idle state.
        /// </summary>
        public IBindable<bool> IsIdle => isIdle;

        private readonly BindableBool isIdle = new BindableBool();

        /// <summary>
        /// Whether the game can currently enter an idle state.
        /// </summary>
        protected virtual bool AllowIdle => true;

        /// <summary>
        /// Intstantiate a new <see cref="IdleTracker"/>.
        /// </summary>
        /// <param name="timeToIdle">The length in milliseconds until an idle state should be assumed.</param>
        public IdleTracker(double timeToIdle)
        {
            this.timeToIdle = timeToIdle;
            RelativeSizeAxes = Axes.Both;
        }

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

        protected override void Update()
        {
            base.Update();
            isIdle.Value = TimeSpentIdle > timeToIdle && AllowIdle;
        }

        public bool OnPressed(KeyBindingPressEvent<PlatformAction> e) => updateLastInteractionTime();

        public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e) => updateLastInteractionTime();

        public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) => updateLastInteractionTime();

        public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) => updateLastInteractionTime();

        [Resolved]
        private GameHost host { get; set; } = null!;

        protected override bool Handle(UIEvent e)
        {
            // Even when not active, `MouseMoveEvent`s will arrive.
            // We don't want these to trigger a non-idle state as it's quite often the user interacting
            // with other windows while osu! is in the background.
            if (!host.IsActive.Value)
                return base.Handle(e);

            switch (e)
            {
                case KeyDownEvent:
                case KeyUpEvent:
                case MouseDownEvent:
                case MouseUpEvent:
                case MouseMoveEvent:
                    return updateLastInteractionTime();

                default:
                    return base.Handle(e);
            }
        }

        private bool updateLastInteractionTime()
        {
            lastInteractionTime = Clock.CurrentTime;
            return false;
        }
    }
}