using System;
using Movement;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using PlayerInput = Input.PlayerInput;

namespace Player
{
    public class PlayerController : MonoBehaviour, IMovement, PlayerInput.IPlayerControlActions
    {
        private const float DefaultJumpForce = 26.0f;
        private const float DefaultMass = 80.0f;
        private const float MaxSpeed = 700.0f;
        private const float MaxAirSpeed = 500.0f;
        private const float GravityScale = 6.0f;
        private const float AccelerationScale = 40.0f;
        private const float DecelerationScale = 60.0f;
        private const float RespawnLimit = -60.0f;

        //Took from tutorial : https://www.youtube.com/watch?v=7KiK0Aqtmzc
        private const float FallMultiplier = 3.0f;

        private const float LowJumpMultiplier = 40.0f;
        //End of credits

        private PlayerInput _controls;
        private Rigidbody2D _playerRigidbody2D;
        private SpriteRenderer _playerSpriteRenderer;

        private CollisionChecker _playerGroundTrigger;
        private CollisionChecker _playerInteractableTrigger;
        private CollisionChecker _playerWallTriggerLeft;
        private CollisionChecker _playerWallTriggerRight;

        private Orientation _orientation;

        private bool _moveKeyPressed;
        private bool _jumpKeyPressed;

        private float _xAxisValue;
        private float _speed;

        private bool _isOnAir;
        private bool _canJump;
        private bool _afterMoving;

        private void Awake()
        {
            _playerRigidbody2D = GetComponent<Rigidbody2D>();
            _playerSpriteRenderer = GetComponent<SpriteRenderer>();
            _playerGroundTrigger = GameObject.Find("PlayerGroundTrigger").GetComponent<CollisionChecker>();
            _playerInteractableTrigger = GameObject.Find("PlayerInteractableTrigger").GetComponent<CollisionChecker>();
            _playerWallTriggerLeft = GameObject.Find("PlayerWallTriggerLeft").GetComponent<CollisionChecker>();
            _playerWallTriggerRight = GameObject.Find("PlayerWallTriggerRight").GetComponent<CollisionChecker>();
        }

        private void Start()
        {
            _playerRigidbody2D.mass = DefaultMass;
            _playerRigidbody2D.gravityScale = GravityScale;

            _orientation = gameObject.GetComponent<SpriteRenderer>().flipX switch
            {
                true => Orientation.Left,
                false => Orientation.Right
            };

            _canJump = true;
            BaseSpeed = 0.0f;
        }

        private void OnEnable()
        {
            if (_controls == null)
            {
                _controls = new PlayerInput();
                _controls.PlayerControl.SetCallbacks(this);
            }

            _controls.PlayerControl.Enable();
        }

        private void OnDisable()
        {
            _controls.Disable();
        }

        private void FixedUpdate()
        {
            //Took from tutorial : https://www.youtube.com/watch?v=7KiK0Aqtmzc
            switch (_playerRigidbody2D.velocity.y)
            {
                case < 0:
                    _playerRigidbody2D.velocity +=
                        Vector2.up * (Physics2D.gravity.y * (FallMultiplier - 1) * Time.fixedDeltaTime);
                    break;
                case > 0 when !_jumpKeyPressed:
                    _playerRigidbody2D.velocity +=
                        Vector2.up * (Physics2D.gravity.y * (LowJumpMultiplier - 1) * Time.fixedDeltaTime);
                    break;
            }
            //End of credits

            if (_moveKeyPressed)
                Move(_xAxisValue);
            else
                _playerRigidbody2D.velocity = new Vector2(0.0f, _playerRigidbody2D.velocity.y);

            if (_jumpKeyPressed && _playerGroundTrigger.IsCollided && _canJump)
                Jump();

            if (_afterMoving)
                DecelerationAfterMoving();
        }

        private void Update()
        {
            _isOnAir = !_playerGroundTrigger.IsCollided;
            RespawnCheck();
        }

        private void RespawnCheck()
        {
            if (gameObject.transform.position.y < RespawnLimit)
                SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }

        // PAUSE METHODS

        public bool IsPaused { get; private set; }

        public void Pause()
        {
            IsPaused = true;
            _playerRigidbody2D.simulated = IsPaused;
        }

        public void Resume()
        {
            IsPaused = false;
            _playerRigidbody2D.simulated = IsPaused;
        }

        // MOVE METHODS

        public float BaseSpeed
        {
            get => _speed;
            set
            {
                switch (_isOnAir)
                {
                    case false when value > MaxSpeed:
                        _speed = MaxSpeed;
                        break;
                    case true when value > MaxAirSpeed:
                        _speed = MaxAirSpeed;
                        break;
                    default:
                    {
                        if (value < 0)
                            _speed = 0;
                        else
                            _speed = value;
                        break;
                    }
                }
            }
        }

        public void Move(float value)
        {
            switch (_xAxisValue)
            {
                case < 0:
                    _playerSpriteRenderer.flipX = true;
                    _orientation = Orientation.Left;
                    //prevent player from sticking to wall while moving to its direction in the air
                    if (!_playerGroundTrigger.IsCollided && _playerWallTriggerLeft.IsCollided)
                        return;
                    break;
                case > 0:
                    _playerSpriteRenderer.flipX = false;
                    _orientation = Orientation.Right;
                    //prevent player from sticking to wall while moving to its direction in the air
                    if (!_playerGroundTrigger.IsCollided && _playerWallTriggerRight.IsCollided)
                        return;
                    break;
            }

            BaseSpeed += AccelerationScale;
            _playerRigidbody2D.velocity = new Vector2(BaseSpeed * _xAxisValue * Time.fixedDeltaTime,
                _playerRigidbody2D.velocity.y);
        }

        private void DecelerationAfterMoving()
        {
            BaseSpeed -= DecelerationScale;
            _playerRigidbody2D.velocity = new Vector2(BaseSpeed * (int)_orientation * Time.fixedDeltaTime,
                _playerRigidbody2D.velocity.y);
            if (BaseSpeed == 0)
                _afterMoving = false;
        }

        private void Jump()
        {
            BaseSpeed = MaxAirSpeed;
            _playerRigidbody2D.velocity = new Vector2(_playerRigidbody2D.velocity.x, DefaultJumpForce);
            _canJump = false;
        }

        private void Climb()
        {
            throw new NotImplementedException();
        }

        // INPUT HANDLING

        public void OnMove(InputAction.CallbackContext context)
        {
            switch (context.canceled)
            {
                case true:
                    _moveKeyPressed = false;
                    _afterMoving = true;
                    break;
                case false:
                    _afterMoving = false;
                    _moveKeyPressed = true;
                    _xAxisValue = context.ReadValue<float>();
                    break;
            }
        }

        public void OnJump(InputAction.CallbackContext context)
        {
            switch (context.canceled)
            {
                case true:
                    _jumpKeyPressed = false;
                    _canJump = true;
                    break;
                case false:
                    if (_playerGroundTrigger.IsCollided)
                        _jumpKeyPressed = true;
                    break;
            }
        }

        public void OnInteract(InputAction.CallbackContext context)
        {
            if (_playerInteractableTrigger.IsCollided)
            {
            }
        }

        public void OnClimb(InputAction.CallbackContext context)
        {
            Climb();
        }

        private enum Orientation
        {
            Left = -1,
            Right = 1
        }
    }
}