using System.Collections.Generic;
using System.Linq;

namespace UnityEngine
{
#if UNITY_EDITOR
    using UnityEditor;

    public class InputManagerEntry
    {
        public enum Kind
        {
            KeyOrButton,
            Mouse,
            Axis
        }

        public enum Axis
        {
            X,
            Y,
            Third,
            Fourth,
            Fifth,
            Sixth,
            Seventh,
            Eigth
        }

        public enum Joy
        {
            All,
            First,
            Second
        }

        public string name = "";
        public string desc = "";
        public string btnNegative = "";
        public string btnPositive = "";
        public string altBtnNegative = "";
        public string altBtnPositive = "";
        public float gravity = 0.0f;
        public float deadZone = 0.0f;
        public float sensitivity = 0.0f;
        public bool snap = false;
        public bool invert = false;
        public Kind kind = Kind.Axis;
        public Axis axis = Axis.X;
        public Joy joystick = Joy.All;
    }

    public static class InputRegistering
    {
        static void CopyEntry(SerializedProperty spAxis, InputManagerEntry entry)
        {
            spAxis.FindPropertyRelative("m_Name").stringValue = entry.name;
            spAxis.FindPropertyRelative("descriptiveName").stringValue = entry.desc;
            spAxis.FindPropertyRelative("negativeButton").stringValue = entry.btnNegative;
            spAxis.FindPropertyRelative("altNegativeButton").stringValue = entry.altBtnNegative;
            spAxis.FindPropertyRelative("positiveButton").stringValue = entry.btnPositive;
            spAxis.FindPropertyRelative("altPositiveButton").stringValue = entry.altBtnPositive;
            spAxis.FindPropertyRelative("gravity").floatValue = entry.gravity;
            spAxis.FindPropertyRelative("dead").floatValue = entry.deadZone;
            spAxis.FindPropertyRelative("sensitivity").floatValue = entry.sensitivity;
            spAxis.FindPropertyRelative("snap").boolValue = entry.snap;
            spAxis.FindPropertyRelative("invert").boolValue = entry.invert;
            spAxis.FindPropertyRelative("type").intValue = (int)entry.kind;
            spAxis.FindPropertyRelative("axis").intValue = (int)entry.axis;
            spAxis.FindPropertyRelative("joyNum").intValue = (int)entry.joystick;
        }

        static void AddEntriesWithoutCheck(SerializedProperty spAxes, List<InputManagerEntry> newEntries)
        {
            if (newEntries.Count == 0)
                return;

            int endOfCurrentInputList = spAxes.arraySize;
            spAxes.arraySize = endOfCurrentInputList + newEntries.Count;
            // Assignment to spAxes.arraySize resizes the spAxes array to at least 1 larger than it used to be, and 
            // therefore it is OK to use endOfCurrentInputList ("one-past-end of previous size") to get the array iterator.
            SerializedProperty spAxis = spAxes.GetArrayElementAtIndex(endOfCurrentInputList);
            for (int i = 0; i < newEntries.Count; ++i, spAxis.Next(false))
                CopyEntry(spAxis, newEntries[i]);
        }

        // Get a representation of the already registered inputs
        static List<(string name, InputManagerEntry.Kind kind)> GetCachedInputs(SerializedProperty spAxes)
        {
            int size = spAxes.arraySize;
            List<(string name, InputManagerEntry.Kind kind)> result = new List<(string name, InputManagerEntry.Kind kind)>(size);

            if (size == 0)
                return result;

            SerializedProperty spAxis = spAxes.GetArrayElementAtIndex(0);
            for (int i = 0; i < size; ++i, spAxis.Next(false))
                result.Add((spAxis.FindPropertyRelative("m_Name").stringValue, (InputManagerEntry.Kind)spAxis.FindPropertyRelative("type").intValue));
            return result;
        }

        internal static List<InputManagerEntry> GetEntriesWithoutDuplicates(List<InputManagerEntry> entries)
        {
            return entries
                .GroupBy(x => new { x.name, x.kind }) // Create groups { name, kind }
                .Select(y => y.First()) // Select first entry from each group, ignoring duplicates
                .ToList();
        }

        internal static List<InputManagerEntry> GetEntriesWithoutAlreadyRegistered(List<InputManagerEntry> entries, List<(string name, InputManagerEntry.Kind kind)> cachedEntries)
        {
            return entries
                .Where(entry => !cachedEntries.Any(cachedEntry => cachedEntry.name == entry.name && cachedEntry.kind == entry.kind))
                .ToList();
        }

        static SerializedObject GetInputManagerSO()
        {
            var assets = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/InputManager.asset");

            // Potentially there are multiple objects inside the Asset, we need to make sure we only grab the correct one.
            foreach (var obj in assets)
            {
                // Check the asset name is correct
                if (obj != null && obj.name == "InputManager")
                {
                    // There is an issue where multiple objects can be named InputManager for a
                    // period when they are added to the file, even if they are given unique names.
                    // In the editor we also check the type is InputManager to work around this, however
                    // that type is not available here. So instead we inspect the contents to confirm it's
                    // the object we expect.
                    var soInputManager = new SerializedObject(obj);
                    var spAxes = soInputManager.FindProperty("m_Axes");
                    if (spAxes != null)
                        return soInputManager;
                }
            }
            return null;
        }

        public static void RegisterInputs(List<InputManagerEntry> entries)
        {
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
            Debug.LogWarning("Trying to add entry in the legacy InputManager but using InputSystem package. Skipping.");
            return;
#else
            // Grab reference to input manager
            var soInputManager = GetInputManagerSO();

            // Temporary fix. This happens some time with HDRP init when it's called before asset database is initialized (probably related to package load order).
            if (soInputManager == null)
                return;

            var spAxes = soInputManager.FindProperty("m_Axes");

            // At this point, we assume that entries in spAxes are already unique.

            // Ensure no double entry are tried to be registered (trim early)
            var uniqueEntries = GetEntriesWithoutDuplicates(entries);

            // Cache already existing entries to minimally use serialization
            var cachedEntries = GetCachedInputs(spAxes);

            // And trim pending entries regarding already cached ones.
            var uniqueNewEntries = GetEntriesWithoutAlreadyRegistered(uniqueEntries, cachedEntries);

            // Add now unique entries
            AddEntriesWithoutCheck(spAxes, uniqueNewEntries);

            // Commit
            soInputManager.ApplyModifiedProperties();
#endif
        }
    }
#endif
}