// 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 osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;

namespace osu.Game.Graphics.UserInterface
{
    /// <summary>
    /// An <see cref="OsuMenuItem"/> which contains and displays a state.
    /// </summary>
    public abstract class StatefulMenuItem : OsuMenuItem
    {
        /// <summary>
        /// The current state that should be displayed.
        /// </summary>
        public readonly Bindable<object> State = new Bindable<object>();

        /// <summary>
        /// Creates a new <see cref="StatefulMenuItem"/>.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
        /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
        protected StatefulMenuItem(LocalisableString text, Func<object, object> changeStateFunc, MenuItemType type = MenuItemType.Standard)
            : this(text, changeStateFunc, type, null)
        {
        }

        /// <summary>
        /// Creates a new <see cref="StatefulMenuItem"/>.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
        /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
        /// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
        protected StatefulMenuItem(LocalisableString text, Func<object, object>? changeStateFunc, MenuItemType type, Action<object>? action)
            : base(text, type)
        {
            Action.Value = () =>
            {
                State.Value = changeStateFunc?.Invoke(State.Value) ?? State.Value;
                action?.Invoke(State.Value);
            };
        }

        /// <summary>
        /// Retrieves the icon to be displayed for a state.
        /// </summary>
        /// <param name="state">The state to retrieve the relevant icon for.</param>
        /// <returns>The icon to be displayed for <paramref name="state"/>.</returns>
        public abstract IconUsage? GetIconForState(object state);
    }

    public abstract class StatefulMenuItem<T> : StatefulMenuItem
        where T : struct
    {
        /// <summary>
        /// The current state that should be displayed.
        /// </summary>
        public new readonly Bindable<T> State = new Bindable<T>();

        /// <summary>
        /// Creates a new <see cref="StatefulMenuItem"/>.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
        /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
        protected StatefulMenuItem(LocalisableString text, Func<T, T>? changeStateFunc, MenuItemType type = MenuItemType.Standard)
            : this(text, changeStateFunc, type, null)
        {
        }

        /// <summary>
        /// Creates a new <see cref="StatefulMenuItem"/>.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
        /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
        /// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
        protected StatefulMenuItem(LocalisableString text, Func<T, T>? changeStateFunc, MenuItemType type, Action<T>? action)
            : base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o))
        {
            base.State.BindValueChanged(state =>
            {
                if (state.NewValue == null)
                    base.State.Value = default(T);

                State.Value = (T)base.State.Value;
            }, true);

            State.BindValueChanged(state => base.State.Value = state.NewValue);
        }

        public sealed override IconUsage? GetIconForState(object state) => GetIconForState((T)state);

        /// <summary>
        /// Retrieves the icon to be displayed for a state.
        /// </summary>
        /// <param name="state">The state to retrieve the relevant icon for.</param>
        /// <returns>The icon to be displayed for <paramref name="state"/>.</returns>
        public abstract IconUsage? GetIconForState(T state);
    }
}