using UnityEngine;
using UnityEngine.InputSystem;
using NeonTea.Quakeball.Interface;
using NeonTea.Quakeball.Networking;
using NeonTea.Quakeball.Networking.Packets;

namespace NeonTea.Quakeball.Players {
    /// <summary>A controller class for a local player. Handles input and updates the relevant components.</summary>
    [RequireComponent(typeof(Player))]
    public class LocalPlayer : MonoBehaviour {
        public Transform Camera;
        public bool DisableInput = false;
        public float FullSyncFrequency = 1;

        private float Lean = 0;

        private Player Player;
        public InputAction LookAction { private set; get; }
        private InputAction MoveAction;
        private InputAction CrouchAction;
        private InputAction JumpAction;

        private float PreviousPlayerUpdate = -1;
        private float PreviousPlayerFullSync = -1;
        private bool WantsToJump = false;

        private void Awake() {
            Player = GetComponent<Player>();

            CrouchAction = new InputAction("crouch", binding: "<Gamepad>/buttonEast");
            CrouchAction.AddBinding("<Keyboard>/leftShift");

            JumpAction = new InputAction("crouch", binding: "<Gamepad>/buttonSouth");
            JumpAction.AddBinding("<Keyboard>/space");
            JumpAction.performed += _ => {
                WantsToJump = true;
            };

            LookAction = new InputAction("look", binding: "<Gamepad>/leftStick");
            LookAction.AddBinding("<Mouse>/delta");

            MoveAction = new InputAction("move", binding: "<Gamepad>/rightStick");
            MoveAction.AddCompositeBinding("Dpad")
                .With("Up", "<Keyboard>/w")
                .With("Down", "<Keyboard>/s")
                .With("Left", "<Keyboard>/a")
                .With("Right", "<Keyboard>/d");

            Terminal.Singleton.RegisterCommand("tp", args => {
                if (args.Length != 3) {
                    return false;
                }
                transform.position = new Vector3(float.Parse(args[0]), float.Parse(args[1]), float.Parse(args[2]));
                return true;
            }, "tp x y z - Teleports the local player to the specified coordinates.");
        }

        private void Update() {
            if (DisableInput) {
                CrouchAction.Disable();
                JumpAction.Disable();
                LookAction.Disable();
                MoveAction.Disable();
                Cursor.lockState = CursorLockMode.None;
                Cursor.visible = true;
            } else {
                CrouchAction.Enable();
                JumpAction.Enable();
                LookAction.Enable();
                MoveAction.Enable();
                Cursor.visible = false;
                Cursor.lockState = CursorLockMode.Locked;
            }

            OptionsData Opts = Options.Get();

            Vector2 LookInput = LookAction.ReadValue<Vector2>() * Opts.LookSensitivity;
            Player.Pitch = Mathf.Clamp(Player.Pitch - LookInput.y * (Opts.InvertVerticalLook ? -1 : 1), -90, 90);
            Player.Yaw += LookInput.x;
            float TargetLean = -Vector3.Dot(Player.GroundVelocity / Player.MoveStyle.TargetVelocity, Camera.right) * Player.MoveStyle.LeanDegrees;
            Lean = Mathf.Lerp(Lean, TargetLean, 30f * Time.deltaTime);
            Camera.localEulerAngles = new Vector3(Player.Pitch, Player.Yaw, Lean);

            if (Time.time - PreviousPlayerUpdate >= 1f / Player.UpdateFrequency) {
                PreviousPlayerUpdate = Time.time;

                Vector2 MovementInput = MoveAction.ReadValue<Vector2>();
                Vector3 Move = new Vector3(MovementInput.x, 0, MovementInput.y);
                Move = Quaternion.Euler(0, Player.Yaw, 0) * Move;
                Player.MoveDirection = Move;
                Player.CurrentMoveStyle = CrouchAction.ReadValue<float>() > 0 ? (byte)1 : (byte)0;
                if (Networking.Net.Singleton.Instance != null) {
                    Networking.Net.Singleton.Instance.UpdateLocalPlayer();
                }

                if (WantsToJump) {
                    bool Jumped = Player.Jump();
                    if (Jumped && Networking.Net.Singleton.Instance != null) {
                        Networking.Net.Singleton.Instance.LocalPlayerAction(PlayerAction.Jump);
                    }
                }
                WantsToJump = false;
            }

            if (Time.time - PreviousPlayerFullSync >= 1f / FullSyncFrequency) {
                PreviousPlayerFullSync = Time.time;
                if (Networking.Net.Singleton.Instance != null) {
                    Networking.Net.Singleton.Instance.SendPlayerSync();
                }
            }
        }
    }
}
