namespace Marro.PacManUdon
{
    using System;
    using UnityEngine;
    using VRC.SDKBase;
    using VRC.Udon.Common;

    enum InputMethod
    {
        KeyboardMouse,
        Other,
    }

    public class PlayerInput : SyncedObject
    {
        public bool active;
        
        private GameManager gameManager;

        private VRCPlayerApi player;
        private InputMethod inputMethod;

        private Direction resultInput;
        private Vector2 analogInput;
        private bool dirty;
        private HandType lastUsedHand;
        private bool horizontalPriority;

        public void Initialize(GameManager gameManager)
        {
            this.gameManager = gameManager;
            
            player = Networking.LocalPlayer;
            inputMethod = InputMethod.KeyboardMouse;

            resultInput = Direction.Zero;
            analogInput = Vector2.zero;
            dirty = false;
            horizontalPriority = false;

            SubscribeToEvent(NetworkEventType.InputChange);
        }

        void Update()
        {
            if (active)
            {
                if (Input.GetKeyDown(KeyCode.R))
                {
                    gameManager.ResetButtonPressed();
                }

                if (Input.GetKeyDown(KeyCode.G))
                {
                    gameManager.networkManager.DoFullSync();
                }

                if (Input.GetKeyDown(KeyCode.C))
                {
                    gameManager.JumpToTimeSequenceBoardClear();
                }
            }
            else
            {
                if (Input.GetKeyDown(KeyCode.T))
                {
                    gameManager.ResetButtonPressed();
                }
            }
        }

        public override void SyncedUpdate()
        {
            if (dirty) // Update now to ensure input feedback is performed timely
            {
                UpdateResultInput();
            }
        }

        public void Activate()
        {
            Debug.Log($"{gameObject} got activated, {player} was immobilized");
            active = true;
            player.SetWalkSpeed(0);
            player.SetRunSpeed(0);
            player.SetStrafeSpeed(0);

            gameManager.JoystickGrabbed();
        }

        public void Deactivate()
        {
            Debug.Log($"{gameObject} got deactivated, {player} was released");
            player.SetWalkSpeed(2);
            player.SetRunSpeed(4);
            player.SetStrafeSpeed(2);
            active = false;

            gameManager.JoystickReleased();
        }

        public override void InputJump(bool pressed, UdonInputEventArgs args)
        {
            if (!active)
            {
                return;
            }

            if (pressed)
            {
                Deactivate();
            }
        }

        public override void InputMoveHorizontal(float value, UdonInputEventArgs args)
        {
            if (!active)
            {
                return;
            }

            analogInput.x = value;
            lastUsedHand = args.handType;
            dirty = true;
        }

        public override void InputMoveVertical(float value, UdonInputEventArgs args)
        {
            if (!active)
            {
                return;
            }

            analogInput.y = value;
            lastUsedHand = args.handType;
            dirty = true;
        }

        public override void OnInputMethodChanged(VRCInputMethod inputMethod)
        {
            switch (inputMethod)
            {
                case VRCInputMethod.Keyboard:
                case VRCInputMethod.Mouse:
                    this.inputMethod = InputMethod.KeyboardMouse;
                    return;
                default:
                    this.inputMethod = InputMethod.Other;
                    return;
            }
        }

        private void UpdateResultInput()
        {
            dirty = false;
            var newResult = GetResultInput(analogInput);

            if (newResult == resultInput)
            {
                return;
            }

            //Debug.Log($"Switched to input direction {newResult} from analogInput {analogInput} with HorizontalPriority {horizontalPriority}");

            resultInput = newResult;
            networkManager.SendEventSoon(NetworkEventType.InputChange, true);
            PlayHaptics();
        }

        private void PlayHaptics()
        {
            if (inputMethod == InputMethod.KeyboardMouse)
            {
                return;
            }

            VRC_Pickup.PickupHand hand;
            switch (lastUsedHand)
            {
                case HandType.LEFT:
                    hand = VRC_Pickup.PickupHand.Left;
                    break;
                case HandType.RIGHT:
                    hand = VRC_Pickup.PickupHand.Right;
                    break;
                default:
                    return;
            }

            player.PlayHapticEventInHand(hand, 0.1f, 0.15f, 75);
        }

        private Direction GetResultInput(Vector2 analogInput)
        {
            if (analogInput.magnitude < 0.8)
            {
                return Direction.Zero;
            }

            var normalized = analogInput.normalized;

            //Debug.Log($"Checking analogInput {analogInput} with HorizontalPriority {horizontalPriority}, normalized {normalized}");

            const float directionDivider = 0.72f;
            if (normalized.x > directionDivider)
            {
                horizontalPriority = false;
                return Direction.Right;
            }
            else if (normalized.x < -directionDivider)
            {
                horizontalPriority = false;
                return Direction.Left;
            }
            else if (normalized.y > directionDivider)
            {
                horizontalPriority = true;
                return Direction.Up;
            }
            else if (normalized.y < -directionDivider)
            {
                horizontalPriority = true;
                return Direction.Down;
            }

            if (horizontalPriority)
            {
                if (normalized.x > 0)
                {
                    return Direction.Right;
                }
                return Direction.Left;
            }

            if (normalized.y > 0)
            {
                return Direction.Up;
            }
            return Direction.Down;
        }

        public Direction GetDirection()
        {
            if (dirty) // Update now to reduce input delay
            {
                UpdateResultInput();
            }

            return resultInput;
        }

        public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            if (eventType != NetworkEventType.InputChange)
            {
                return;
            }

            ByteUtils.AppendAsByte(data, (int)resultInput, ref index);
        }

        public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            if (eventType != NetworkEventType.InputChange)
            {
                return true;
            }

            resultInput = (Direction)ByteUtils.ReadByte(data, ref index);

            return true;
        }
    }
}