using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Voile.Utils;

namespace Voile.Input
{
    /// <summary>
    /// A system providing means for collecting user input, as well as defining custom <see cref="InputAction"/> list.
    /// </summary>
    public abstract class InputSystem : IStartableSystem, IDisposable
    {
        /// <summary>
        /// The list of all available input mappings, custom and built-in.
        /// </summary>
        public static IReadOnlyDictionary<string, List<IInputAction>> InputMappings => inputMappings;

        public void Start()
        {
            inputMappings = new Dictionary<string, List<IInputAction>>();
            CreateDefaultMappings();
        }

        /// <summary>
        /// Forces this input system to poll all of its inputs during current frame.<br />
        /// Some backends require inputs to be polled once per specific interval. Override this method to implement this behavior. 
        /// </summary>
        public virtual void Poll() { }
        public void Shutdown() => Dispose();
        public void Dispose() => GC.SuppressFinalize(this);
        public bool Handled { get => _handled; set => _handled = value; }

        public bool IsActionDown(string action)
        {
            List<IInputAction>? mappings;

            if (TryGetInputMappings(action, out mappings))
            {
                foreach (IInputAction inputAction in mappings)
                    if (inputAction.IsDown(this)) return true;
            }

            return false;
        }

        public bool IsActionPressed(string action)
        {
            List<IInputAction>? mappings;

            if (TryGetInputMappings(action, out mappings))
            {
                foreach (IInputAction inputAction in mappings)
                    if (inputAction.IsPressed(this)) return true;
            }

            return false;
        }

        public bool IsActionReleased(string action)
        {
            List<IInputAction>? mappings;

            if (TryGetInputMappings(action, out mappings))
            {
                foreach (IInputAction inputAction in mappings)
                    if (inputAction.IsReleased(this)) return true;
            }

            return false;
        }

        public Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey)
        {
            Vector2 dir = Vector2.Zero;

            if (IsKeyboardKeyDown(leftKey))
                dir.X -= 1;
            if (IsKeyboardKeyDown(rightKey))
                dir.X += 1;
            if (IsKeyboardKeyDown(upKey))
                dir.Y -= 1;
            if (IsKeyboardKeyDown(downKey))
                dir.Y += 1;

            return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
        }
        public Vector2 GetInputDirection(string leftAction, string rightAction, string upAction, string downAction)
        {
            Vector2 dir = Vector2.Zero;

            if (IsActionDown(leftAction))
                dir.X -= 1;
            if (IsActionDown(rightAction))
                dir.X += 1;
            if (IsActionDown(upAction))
                dir.Y -= 1;
            if (IsActionDown(downAction))
                dir.Y += 1;

            return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
        }

        public abstract bool IsKeyboardKeyDown(KeyboardKey key);
        public abstract bool KeyboardKeyJustPressed(KeyboardKey key);
        public abstract bool KeyboardKeyJustReleased(KeyboardKey key);

        public abstract int GetCharPressed();

        public abstract bool IsMouseButtonPressed(MouseButton button);
        public abstract bool IsMouseButtonDown(MouseButton button);
        public abstract bool IsMouseButtonReleased(MouseButton button);
        public abstract float GetMouseWheelMovement();

        public abstract void SetMousePosition(Vector2 position);
        public abstract Vector2 GetMousePosition();
        public abstract void HideCursor();
        public abstract void ShowCursor();
        public abstract bool IsCursorHidden();

        public void SetAsHandled() => _handled = true;

        public void AddInputMapping(string actionName, IEnumerable<IInputAction> inputActions)
        {
            inputMappings.Add(actionName, inputActions.ToList());
        }

        private void CreateDefaultMappings()
        {
            AddInputMapping("up", [
                new KeyInputAction(KeyboardKey.W),
                new KeyInputAction(KeyboardKey.Up),
            ]);
            AddInputMapping("down", [
                new KeyInputAction(KeyboardKey.S),
                new KeyInputAction(KeyboardKey.Down),
            ]);
            AddInputMapping("left", [
                new KeyInputAction(KeyboardKey.Left),
            ]);
            AddInputMapping("right", [
                new KeyInputAction(KeyboardKey.Right),
            ]);

            AddInputMapping("accept", [
                new KeyInputAction(KeyboardKey.Enter),
            ]);
            AddInputMapping("cancel", [
                new KeyInputAction(KeyboardKey.Escape),
            ]);
            AddInputMapping("backspace", [
                new KeyInputAction(KeyboardKey.Backspace)
            ]);
        }

        protected bool TryGetInputMappings(string forAction, [NotNullWhen(true)] out List<IInputAction>? inputActions)
        {
            inputActions = null;

            if (inputMappings.TryGetValue(forAction, out var actions))
            {
                inputActions = actions;
                return true;
            }

            _logger.Error($"The action \"{forAction}\" is not present in the input mappings!");

            return false;
        }

        protected static Dictionary<string, List<IInputAction>> inputMappings = new();
        private bool _handled;
        private Logger _logger = new(nameof(InputSystem));
    }

    public enum KeyboardKey
    {
        Null = 0,
        Back = 4,
        VolumeUp = 24,
        VolumeDown = 25,
        Spacebar = 32,
        Apostrophe = 39,
        Comma = 44,
        Minus = 45,
        Period = 46,
        Slash = 47,
        Zero = 48,
        One = 49,
        Two = 50,
        Three = 51,
        Four = 52,
        Five = 53,
        Six = 54,
        Seven = 55,
        Eight = 56,
        Nine = 57,
        Semicolon = 59,
        Equal = 61,
        A = 65,
        B = 66,
        C = 67,
        D = 68,
        E = 69,
        F = 70,
        G = 71,
        H = 72,
        I = 73,
        J = 74,
        K = 75,
        L = 76,
        M = 77,
        N = 78,
        O = 79,
        P = 80,
        Q = 81,
        R = 82,
        Menu = 82,
        S = 83,
        T = 84,
        U = 85,
        V = 86,
        W = 87,
        X = 88,
        Y = 89,
        Z = 90,
        LeftBracket = 91,
        Backslash = 92,
        RightBracket = 93,
        Grave = 96,
        Escape = 256,
        Enter = 257,
        Tab = 258,
        Backspace = 259,
        Insert = 260,
        Delete = 261,
        Right = 262,
        Left = 263,
        Down = 264,
        Up = 265,
        PageUp = 266,
        PageDown = 267,
        Home = 268,
        End = 269,
        CapsLock = 280,
        ScrollLock = 281,
        NumLock = 282,
        PrintScreen = 283,
        Pause = 284,
        F1 = 290,
        F2 = 291,
        F3 = 292,
        F4 = 293,
        F5 = 294,
        F6 = 295,
        F7 = 296,
        F8 = 297,
        F9 = 298,
        F10 = 299,
        F11 = 300,
        F12 = 301,
        KP0 = 320,
        KP1 = 321,
        KP2 = 322,
        KP3 = 323,
        KP4 = 324,
        KP5 = 325,
        KP6 = 326,
        KP7 = 327,
        KP8 = 328,
        KP9 = 329,
        KPDecimal = 330,
        KPDivide = 331,
        KPMultiply = 332,
        KPSubstract = 333,
        KPAdd = 334,
        KPEnter = 335,
        KPEqual = 336,
        LeftShift = 340,
        LeftControl = 341,
        LeftAlt = 342,
        LeftSuper = 343,
        RightShift = 344,
        RightControl = 345,
        RightAlt = 346,
        RightSuper = 347,
        KBMenu = 348
    }

    [Flags]
    public enum MouseButton
    {
        Left = 0,
        Right = 1,
        Middle = 2,
    }
}