using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.FourEngine;
using MonoGame.FourEngine.Extensions;
using Raycast.Win32.Services;
using System;
using System.Linq;
using static MonoGame.FourEngine.ServiceLocator;

namespace Raycast.Win32.Entities
{
    public partial class Player
    {
        public const bool MouseLookEnabled = true;

        public const float MaxMoveSpeed = 0.24f;
        public const float MaxRotationSpeed = 0.12f;

        public const float StrafeCompensation = 0.75f;

        public const float RotationSpeedDelta = 0.008f;

        public Vector2 CurrentSpeed { get; private set; }
        public float RotationSpeed { get; private set; }

        public float RotationSpeedLimit { get; private set; }

        private IPathfindingService Pathfinding => LazyResolver<IPathfindingService>.Resolve();

        private void Control(UpdateEventArgs args)
        {
            if (_scene == null) return;

            var keyboardState = args.Keyboard.State;
            var gamepad = args.GamePad;

            if (Health <= 0)
            {
                var restart =
                    keyboardState[Keys.Space].HasClicked ||
                    keyboardState[Keys.Enter].HasClicked ||
                    gamepad.State[Buttons.Start].HasClicked ||
                    gamepad.State[Buttons.Back].HasClicked ||
                    gamepad.State[Buttons.A].HasClicked;
                if (restart)
                {
                    MessageCenter.TriggerRestartCommand();
                }

                return;
            }

            var turn = 0f;
            var move = 0f;
            var strafe = 0f;

            if (keyboardState[Keys.Left].IsPressed) turn += 1f;
            if (keyboardState[Keys.Right].IsPressed) turn -= 1f;
            if (keyboardState[Keys.Up].IsPressed || keyboardState[Keys.W].IsPressed) move += 1;
            if (keyboardState[Keys.Down].IsPressed || keyboardState[Keys.S].IsPressed) move -= 1;
            if (keyboardState[Keys.A].IsPressed) strafe -= 1;
            if (keyboardState[Keys.D].IsPressed) strafe += 1;
            var shoot = keyboardState[Keys.Space].IsPressed;
            var use = keyboardState[Keys.E].HasClicked || keyboardState[Keys.F].HasClicked;

            RotationSpeedLimit = MaxRotationSpeed;

            var mLook = false;

            #region GamePad
            if (turn == 0f)
            {
                turn = -args.GamePad.RightStick.X;
                if (turn != 0f)
                {
                    RotationSpeedLimit *= Math.Abs(turn);
                }
            }
            if (move == 0f && strafe == 0f)
            {
                move = gamepad.LeftStick.Y;
                strafe = gamepad.LeftStick.X;
            }
            if (!shoot)
            {
                shoot = gamepad.State[Buttons.RightTrigger].IsPressed;
            }
            if (!use)
            {
                use = gamepad.State[Buttons.A].HasClicked || gamepad.State[Buttons.X].HasClicked;
            }
            #endregion

            #region Mouse
            if (MouseLookEnabled)
            {
                if (turn == 0f)
                {
                    var center = (Configuration.Size.ToVector2() / 2).ToPoint();

                    var diff = (center.X - Mouse.GetState().X) / 1.5f;

                    if (diff != 0)
                    {
                        turn = diff * Configuration.MouseSensitivity;
                        mLook = true;
                    }

                    RotationSpeedLimit = MaxRotationSpeed;

                    Mouse.SetPosition(center.X, center.Y);
                }
                if (!shoot)
                {
                    shoot = args.Pointer.IsPrimaryPressed;
                }
            }
            #endregion

            var moveSpeed = MaxMoveSpeed;
            if (Math.Abs(move) > StrafeCompensation && Math.Abs(strafe) > StrafeCompensation)
                moveSpeed *= StrafeCompensation;

            Move(move * moveSpeed, strafe * moveSpeed);

            if (mLook)
                RotateRaw(turn * RotationSpeedDelta);
            else
                Rotate(turn * RotationSpeedDelta);

            if (shoot) Shoot();

            if (use) Use();
        }

        private void Move(float x, float y)
        {
            bool CheckMove(float moveX, float moveY)
            {
                var vector = new Vector2(moveX, moveY);
                var point = vector.ToPoint();
                return Pathfinding.CanPassThrough(point);
            }

            var diff = new Vector2(Direction.X * x + Direction.Y * y, Direction.Y * x - Direction.X * y);
            var radius = new Vector2(diff.X > 0 ? Radius : -Radius, diff.Y > 0 ? Radius : -Radius);

            var oldPosition = Position;

            if (CheckMove(Position.X + diff.X + radius.X, Position.Y + Radius) &&
                CheckMove(Position.X + diff.X + radius.X, Position.Y - Radius))
                _position.X += diff.X;

            if (CheckMove(Position.X + Radius, Position.Y + diff.Y + radius.Y) &&
                CheckMove(Position.X - Radius, Position.Y + diff.Y + radius.Y))
                _position.Y += diff.Y;

            diff = _position - oldPosition;

            CurrentSpeed = new Vector2(
                Direction.Y * diff.X - Direction.X * diff.Y,
                -(Direction.X * diff.X + Direction.Y * diff.Y));
        }

        public void Rotate(float angle)
        {
            if (angle == 0)
            {
                RotationSpeed *= 0.8f;
                return;
            }
            else if (angle > 0 && RotationSpeed < RotationSpeedLimit)
            {
                RotationSpeed += angle;

                if (RotationSpeed < 0) RotationSpeed = 0;
            }
            else if (angle < 0 && RotationSpeed > -RotationSpeedLimit)
            {
                RotationSpeed += angle;

                if (RotationSpeed > 0) RotationSpeed = 0;
            }

            var oldDir = Direction;
            var oldPlane = Plane;

            _direction.X = (float)(Direction.X * Math.Cos(RotationSpeed) - _direction.Y * Math.Sin(RotationSpeed));
            _direction.Y = (float)(oldDir.X * Math.Sin(RotationSpeed) + _direction.Y * Math.Cos(RotationSpeed));

            _plane.X = (float)(Plane.X * Math.Cos(RotationSpeed) - Plane.Y * Math.Sin(RotationSpeed));
            _plane.Y = (float)(oldPlane.X * Math.Sin(RotationSpeed) + Plane.Y * Math.Cos(RotationSpeed));
        }

        public void RotateRaw(float angle)
        {
            var virtAngle
                = angle > Configuration.MouseSensitivity ? 1f
                : angle < -Configuration.MouseSensitivity ? -1f
                : Math.Abs(angle) < 0.02f ? 0f
                : angle / Configuration.MouseSensitivity;

            if (virtAngle == 0)
            {
                RotationSpeed *= 0.8f;
                return;
            }
            else if (virtAngle > 0 && RotationSpeed < RotationSpeedLimit)
            {
                RotationSpeed += virtAngle;

                if (RotationSpeed < 0) RotationSpeed = 0;
            }
            else if (virtAngle < 0 && RotationSpeed > -RotationSpeedLimit)
            {
                RotationSpeed += virtAngle;

                if (RotationSpeed > 0) RotationSpeed = 0;
            }

            if (RotationSpeed > +RotationSpeedLimit) RotationSpeed = +RotationSpeedLimit;
            if (RotationSpeed < -RotationSpeedLimit) RotationSpeed = -RotationSpeedLimit;

            var oldDir = Direction;
            var oldPlane = Plane;

            _direction.X = (float)(Direction.X * Math.Cos(angle) - _direction.Y * Math.Sin(angle));
            _direction.Y = (float)(oldDir.X * Math.Sin(angle) + _direction.Y * Math.Cos(angle));

            _plane.X = (float)(Plane.X * Math.Cos(angle) - Plane.Y * Math.Sin(angle));
            _plane.Y = (float)(oldPlane.X * Math.Sin(angle) + Plane.Y * Math.Cos(angle));
        }

        private void Shoot()
        {
            MessageCenter.TriggerPlayerShoot(this, null);
        }

        private void Use()
        {
            var door = _scene.Entities
                .Filter<Door>()
                .FirstOrDefault(x => Math.Abs((Position - x.Position).LengthSquared()) <= 2f);
            door?.Open();
        }
    }
}