#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
using System;
using System.Collections.Generic;
using UnityEngine.UIElements;

namespace UnityEngine.InputSystem.Editor
{
    internal interface IViewStateSelector<out TViewState>
    {
        bool HasStateChanged(InputActionsEditorState state);
        TViewState GetViewState(InputActionsEditorState state);
    }

    internal interface IView
    {
        void UpdateView(InputActionsEditorState state);
        void DestroyView();
    }

    internal abstract class ViewBase<TViewState> : IView
    {
        protected ViewBase(VisualElement root, StateContainer stateContainer)
        {
            this.rootElement = root;
            this.stateContainer = stateContainer;
            m_ChildViews = new List<IView>();
        }

        protected void OnStateChanged(InputActionsEditorState state, UIRebuildMode editorRebuildMode)
        {
            // Return early if rebuilding the editor UI isn't required (ISXB-1171)
            if (editorRebuildMode == UIRebuildMode.None)
                return;

            UpdateView(state);
        }

        public void UpdateView(InputActionsEditorState state)
        {
            if (m_ViewStateSelector == null)
            {
                Debug.LogWarning(
                    $"View '{GetType().Name}' has no selector and will not render. Create a selector for the " +
                    $"view using the CreateSelector method.");
                return;
            }

            if (m_ViewStateSelector.HasStateChanged(state) || m_IsFirstUpdate)
                RedrawUI(m_ViewStateSelector.GetViewState(state));

            m_IsFirstUpdate = false;
            foreach (var view in m_ChildViews)
            {
                view.UpdateView(state);
            }

            // We can execute UI Commands now that the UI is fully updated
            // NOTE: This isn't used with Input Commands
            allowUICommandExecution = true;
        }

        public TView CreateChildView<TView>(TView view) where TView : IView
        {
            m_ChildViews.Add(view);
            return view;
        }

        public void DestroyChildView<TView>(TView view) where TView : IView
        {
            if (view == null)
                return;

            m_ChildViews.Remove(view);
            view.DestroyView();
        }

        public void Dispatch(Command command, UIRebuildMode editorRebuildMode = UIRebuildMode.Rebuild)
        {
            stateContainer.Dispatch(command, editorRebuildMode);
        }

        public abstract void RedrawUI(TViewState viewState);

        /// <summary>
        /// Called when a parent view is destroying this view to give it an opportunity to clean up any
        /// resources or event handlers.
        /// </summary>
        public virtual void DestroyView()
        {
        }

        protected void CreateSelector(Func<InputActionsEditorState, TViewState> selector)
        {
            m_ViewStateSelector = new ViewStateSelector<TViewState>(selector);
        }

        protected void CreateSelector<T1>(
            Func<InputActionsEditorState, T1> func1,
            Func<T1, InputActionsEditorState, TViewState> selector)
        {
            m_ViewStateSelector = new ViewStateSelector<T1, TViewState>(func1, selector);
        }

        protected void CreateSelector<T1, T2>(
            Func<InputActionsEditorState, T1> func1,
            Func<InputActionsEditorState, T2> func2,
            Func<T1, T2, InputActionsEditorState, TViewState> selector)
        {
            m_ViewStateSelector = new ViewStateSelector<T1, T2, TViewState>(func1, func2, selector);
        }

        protected void CreateSelector<T1, T2, T3>(
            Func<InputActionsEditorState, T1> func1,
            Func<InputActionsEditorState, T2> func2,
            Func<InputActionsEditorState, T3> func3,
            Func<T1, T2, T3, InputActionsEditorState, TViewState> selector)
        {
            m_ViewStateSelector = new ViewStateSelector<T1, T2, T3, TViewState>(func1, func2, func3, selector);
        }

        protected readonly VisualElement rootElement;
        protected readonly StateContainer stateContainer;
        protected bool allowUICommandExecution { get; set; } = true;

        protected IViewStateSelector<TViewState> ViewStateSelector => m_ViewStateSelector;
        private IViewStateSelector<TViewState> m_ViewStateSelector;
        private IList<IView> m_ChildViews;
        private bool m_IsFirstUpdate = true;
    }

    internal class ViewStateSelector<TReturn> : IViewStateSelector<TReturn>
    {
        private readonly Func<InputActionsEditorState, TReturn> m_Selector;

        public ViewStateSelector(Func<InputActionsEditorState, TReturn> selector)
        {
            m_Selector = selector;
        }

        public bool HasStateChanged(InputActionsEditorState state)
        {
            return true;
        }

        public TReturn GetViewState(InputActionsEditorState state)
        {
            return m_Selector(state);
        }
    }

    // TODO: Make all args to view state selectors IEquatable<T>?
    internal class ViewStateSelector<T1, TReturn> : IViewStateSelector<TReturn>
    {
        private readonly Func<InputActionsEditorState, T1> m_Func1;
        private readonly Func<T1, InputActionsEditorState, TReturn> m_Selector;

        private T1 m_PreviousT1;

        public ViewStateSelector(Func<InputActionsEditorState, T1> func1,
                                 Func<T1, InputActionsEditorState, TReturn> selector)
        {
            m_Func1 = func1;
            m_Selector = selector;
        }

        public bool HasStateChanged(InputActionsEditorState state)
        {
            var valueOne = m_Func1(state);

            if (valueOne is IViewStateCollection collection)
            {
                if (collection.SequenceEqual((IViewStateCollection)m_PreviousT1))
                    return false;
            }
            else if (valueOne.Equals(m_PreviousT1))
            {
                return false;
            }

            m_PreviousT1 = valueOne;
            return true;
        }

        public TReturn GetViewState(InputActionsEditorState state)
        {
            return m_Selector(m_PreviousT1, state);
        }
    }

    internal class ViewStateSelector<T1, T2, TReturn> : IViewStateSelector<TReturn>
    {
        private readonly Func<InputActionsEditorState, T1> m_Func1;
        private readonly Func<InputActionsEditorState, T2> m_Func2;
        private readonly Func<T1, T2, InputActionsEditorState, TReturn> m_Selector;

        private T1 m_PreviousT1;
        private T2 m_PreviousT2;

        public ViewStateSelector(Func<InputActionsEditorState, T1> func1,
                                 Func<InputActionsEditorState, T2> func2,
                                 Func<T1, T2, InputActionsEditorState, TReturn> selector)
        {
            m_Func1 = func1;
            m_Func2 = func2;
            m_Selector = selector;
        }

        public bool HasStateChanged(InputActionsEditorState state)
        {
            var valueOne = m_Func1(state);
            var valueTwo = m_Func2(state);

            var valueOneHasChanged = false;
            var valueTwoHasChanged = false;

            if (valueOne is IViewStateCollection collection && !collection.SequenceEqual((IViewStateCollection)m_PreviousT1) ||
                !valueOne.Equals(m_PreviousT1))
                valueOneHasChanged = true;

            if (valueTwo is IViewStateCollection collection2 && !collection2.SequenceEqual((IViewStateCollection)m_PreviousT2) ||
                !valueTwo.Equals(m_PreviousT2))
                valueTwoHasChanged = true;

            if (!valueOneHasChanged && !valueTwoHasChanged)
                return false;

            m_PreviousT1 = valueOne;
            m_PreviousT2 = valueTwo;
            return true;
        }

        public TReturn GetViewState(InputActionsEditorState state)
        {
            return m_Selector(m_PreviousT1, m_PreviousT2, state);
        }
    }

    internal class ViewStateSelector<T1, T2, T3, TReturn> : IViewStateSelector<TReturn>
    {
        private readonly Func<InputActionsEditorState, T1> m_Func1;
        private readonly Func<InputActionsEditorState, T2> m_Func2;
        private readonly Func<InputActionsEditorState, T3> m_Func3;
        private readonly Func<T1, T2, T3, InputActionsEditorState, TReturn> m_Selector;

        private T1 m_PreviousT1;
        private T2 m_PreviousT2;
        private T3 m_PreviousT3;

        public ViewStateSelector(Func<InputActionsEditorState, T1> func1,
                                 Func<InputActionsEditorState, T2> func2,
                                 Func<InputActionsEditorState, T3> func3,
                                 Func<T1, T2, T3, InputActionsEditorState, TReturn> selector)
        {
            m_Func1 = func1;
            m_Func2 = func2;
            m_Func3 = func3;
            m_Selector = selector;
        }

        public bool HasStateChanged(InputActionsEditorState state)
        {
            var valueOne = m_Func1(state);
            var valueTwo = m_Func2(state);
            var valueThree = m_Func3(state);

            var valueOneHasChanged = false;
            var valueTwoHasChanged = false;
            var valueThreeHasChanged = false;

            if (valueOne is IViewStateCollection collection && !collection.SequenceEqual((IViewStateCollection)m_PreviousT1) ||
                !valueOne.Equals(m_PreviousT1))
                valueOneHasChanged = true;

            if (valueTwo is IViewStateCollection collection2 && !collection2.SequenceEqual((IViewStateCollection)m_PreviousT2) ||
                !valueTwo.Equals(m_PreviousT2))
                valueTwoHasChanged = true;

            if (valueThree is IViewStateCollection collection3 && !collection3.SequenceEqual((IViewStateCollection)m_PreviousT3) ||
                !valueThree.Equals(m_PreviousT3))
                valueThreeHasChanged = true;

            if (!valueOneHasChanged && !valueTwoHasChanged && !valueThreeHasChanged)
                return false;

            m_PreviousT1 = valueOne;
            m_PreviousT2 = valueTwo;
            m_PreviousT3 = valueThree;
            return true;
        }

        public TReturn GetViewState(InputActionsEditorState state)
        {
            return m_Selector(m_PreviousT1, m_PreviousT2, m_PreviousT3, state);
        }
    }
}

#endif