using System;
using System.Collections.Generic;
using System.Linq;
using RebootKit.Engine.Foundation;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.InputSystem;
using UnityEngine.Profiling;
using Logger = RebootKit.Engine.Foundation.Logger;

namespace RebootKit.Engine.Input {
    public class InputState : IDisposable {
        static readonly Logger s_Logger = new Logger(nameof(InputState));

        public string Name { get; private set; }
        
        bool m_IsActive;
        public bool IsActive {
            get {
                return m_IsActive;
            }
            
            set {
                m_IsActive = value;
                Service?.UpdateUnityInputState();
            }
        }
        
        int m_Priority;
        public int Priority {
            get {
                return m_Priority;
            }

            set {
                m_Priority = value;
                Service?.UpdateUnityInputState();
            }
        }

        
        bool m_HideCursor;
        public bool HideCursor {
            get {
                return m_HideCursor;
            }

            set {
                m_HideCursor = value;
                Service?.UpdateUnityInputState();
            }
        }
        
        bool m_LockCursor;
        public bool LockCursor  {
            get {
                return m_LockCursor;
            }

            set {
                m_LockCursor = value;
                Service?.UpdateUnityInputState();
            }
        }
        
        internal List<string> ActiveMaps = new List<string>(4);
        internal InputService Service;

        internal InputState(string name, InputService service) {
            IsActive = true;
            Name = name;
            Service = service;
        }

        public void Dispose() {
            Service.UnregisterInputState(this);
            Service = null;
        }
        
        public void AddActiveMap(string mapName) {
            if (ActiveMaps.Contains(mapName)) {
                s_Logger.Warning($"Action Map '{mapName}' is already active in InputState '{Name}'");
                return;
            }

            ActiveMaps.Add(mapName);
            Service.UpdateUnityInputState();
        }
        
        public void RemoveActiveMap(string mapName) {
            ActiveMaps.Remove(mapName);
            Service.UpdateUnityInputState();
        }
        
        public bool IsMapActive(string mapName) {
            return ActiveMaps.Contains(mapName);
        }
    }
    
    public class InputService : IService {
        static readonly Logger s_Logger = new Logger(nameof(InputService));
        
        readonly Config m_Config;

        InputActionAsset m_InputActionAsset;

        readonly List<InputState> m_States = new List<InputState>(16);

        public InputService(Config config) {
            m_Config = config;

            m_InputActionAsset = config.inputActionAsset;
        }

        public void Dispose() {
            m_InputActionAsset = null;
        }
 
        public InputState NewInputState(string name) {
            Assert.IsNull(m_States.FirstOrDefault(t => t.Name.Equals(name, StringComparison.Ordinal)), 
                          $"State with name '{name}' already exists in the input service.");

            var inputState = new InputState(name, this);
            m_States.Add(inputState);
            return inputState;
        }

        internal void UnregisterInputState(InputState state) {
            m_States.Remove(state);
        }
        
        internal void UpdateUnityInputState() {
            Profiler.BeginSample("Find Active State");
            InputState activeState = m_States.Where(t => t.IsActive)
                                             .OrderByDescending(t => t.Priority)
                                             .FirstOrDefault();
            Profiler.EndSample();
            
            if (activeState == null) {
                s_Logger.Warning("No Active input state found");
                return;
            }
            
            Cursor.visible = !activeState.HideCursor;
            Cursor.lockState = activeState.LockCursor ? CursorLockMode.Locked : CursorLockMode.None;

            foreach (InputActionMap inputActionMap in m_InputActionAsset.actionMaps) {
                if (activeState.IsMapActive(inputActionMap.name)) {
                    inputActionMap.Enable();
                } else {
                    inputActionMap.Disable();
                }
            }
        }

        public void EnableControls() {
            if (m_InputActionAsset == null) {
                return;
            }

            m_InputActionAsset.Enable();
        }

        public void DisableControls() {
            if (m_InputActionAsset == null) {
                return;
            }
            
            m_InputActionAsset.Disable();
        }

        [Serializable]
        public class Config {
            public InputActionAsset inputActionAsset;
        }
    }
}