using LeMangeurDEnfants.Core.Scenes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.FourEngine;
using MonoGame.FourEngine.Components;
using MonoGame.FourEngine.Extensions;
using System;
using System.Linq;
using static MonoGame.FourEngine.Components.AnimationComponent;

namespace LeMangeurDEnfants.Core.Entities
{
    public class BigBoss : BaseObject, IHaveFollower
    {
        public override Point Size { get; } = new Point(19);
        public override string FileName { get; } = Resources.Sprites.BigBoss;
        public const float TimeToTurn = 384f;
        public const float Speed = 32f;
        public const int PhysicalRadius = 4;

        private const int RayInterval = PhysicalRadius;
        private const int MaxRayLength = 128;

        public int FollowerCount { get; set; }
        public bool HasPistol { get; set; }

        public IFollower NextFollower { get; set; }
        public Direction CurrentDirection
        {
            get => _direction;
            private set
            {
                _direction = value;
                AnimationComponent.StartAnimation(
                    FramedAnimation.GenerateRange(
                        new Range(0, 8),
                        (int)_direction + 1,
                        160, true));
            }
        }

        public SfxComponent FailSfx { get; }
        public SfxComponent ShootSfx { get; }
        public SfxComponent PickupSfx { get; }
        public SfxComponent OpenSfx { get; }

        public bool CanTurnLeft => CurrentDirection == Direction.Up || CurrentDirection == Direction.Down || !_isMoving;
        public bool CanTurnUp => CurrentDirection == Direction.Left || CurrentDirection == Direction.Right || !_isMoving;
        public bool CanTurnRight => CurrentDirection == Direction.Up || CurrentDirection == Direction.Down || !_isMoving;
        public bool CanTurnDown => CurrentDirection == Direction.Left || CurrentDirection == Direction.Right || !_isMoving;

        private Direction _direction = Direction.Up;
        private Direction _nextDirection = Direction.Up;
        private Vector2 _positionVector;
        private TimeSpan _timeToTurn;
        private bool _isMoving;
        private bool _isDead;

        public BigBoss()
        {
            Components.Add(FailSfx = new SfxComponent(Resources.Sfx.Fail));
            Components.Add(ShootSfx = new SfxComponent(Resources.Sfx.Shoot));
            Components.Add(PickupSfx = new SfxComponent(Resources.Sfx.Pickup));
            Components.Add(OpenSfx = new SfxComponent(Resources.Sfx.Open));
        }

        public override void OnAttach(Scene scene)
        {
            base.OnAttach(scene);
            _positionVector = Position.ToVector2();
        }

        public override void OnUpdate(UpdateEventArgs args)
        {
            base.OnUpdate(args);
            if (_isDead) return;

            if (_timeToTurn > args.DeltaTime)
            {
                _timeToTurn -= args.DeltaTime;
            }
            else
            {
                _timeToTurn = TimeSpan.Zero;
            }

            var left = args.Keyboard.State[Keys.Left].IsPressed
                || args.Keyboard.State[Keys.A].IsPressed
                || args.GamePad.State[Buttons.DPadLeft].IsPressed
                || args.GamePad.LeftStick.X < -0.8;
            var up = args.Keyboard.State[Keys.Up].IsPressed 
                || args.Keyboard.State[Keys.W].IsPressed
                || args.GamePad.State[Buttons.DPadUp].IsPressed
                || args.GamePad.LeftStick.Y > 0.8;
            var right = args.Keyboard.State[Keys.Right].IsPressed 
                || args.Keyboard.State[Keys.D].IsPressed
                || args.GamePad.State[Buttons.DPadRight].IsPressed
                || args.GamePad.LeftStick.X > 0.8;
            var down = args.Keyboard.State[Keys.Down].IsPressed 
                || args.Keyboard.State[Keys.S].IsPressed
                || args.GamePad.State[Buttons.DPadDown].IsPressed
                || args.GamePad.LeftStick.Y < -0.8;

            if (left && CanTurnLeft) _nextDirection = Direction.Left;
            else if (up && CanTurnUp) _nextDirection = Direction.Up;
            else if (right && CanTurnRight) _nextDirection = Direction.Right;
            else if (down && CanTurnDown) _nextDirection = Direction.Down;

            var turned = false;
            if (_timeToTurn == TimeSpan.Zero && CurrentDirection != _nextDirection)
            {
                CurrentDirection = _nextDirection;
                _timeToTurn = TimeSpan.FromMilliseconds(TimeToTurn);
                _isMoving = true;
                turned = true;
            }

            if (_isMoving)
            {
                Move(args);
                CheckCollisions();
            }

            if (turned)
            {
                _positionVector = Position.ToVector2();
                NotifyFollower();
            }

            if (HasPistol)
            {
                foreach (var enemy in Scene.Entities.Filter<Enemy>())
                {
                    if (CanShoot(enemy))
                    {
                        enemy.Die();
                    }
                }
            }
        }

        public void Fail()
        {
            if (_isDead) return;
            _isDead = true;
            AnimationComponent.StopAnimation();
            SpriteComponent.Frame = new Point(1, 0);
            FailSfx.Play();
            NextFollower?.Fail();
        }

        private void NotifyFollower()
        {
            if (NextFollower != null)
            {
                var node = new NavigationNode
                {
                    Direction = CurrentDirection,
                };
                node.Trigger = Position;
                NextFollower.AddNavigationNode(node);
            }
        }

        private void Move(UpdateEventArgs args)
        {
            var speed = Speed * (float)args.DeltaTime.TotalMilliseconds / 1000f;
            switch (CurrentDirection)
            {
                case Direction.Left:
                    _positionVector += new Vector2(-speed, 0);
                    break;
                case Direction.Up:
                    _positionVector += new Vector2(0, -speed);
                    break;
                case Direction.Right:
                    _positionVector += new Vector2(speed, 0);
                    break;
                case Direction.Down:
                    _positionVector += new Vector2(0, speed);
                    break;
            }
            Position = _positionVector.ToPoint();
        }

        private void CheckCollisions()
        {
            if (Scene is GameScene gameScene)
            {
                var center = Position + new Point(0, 5);
                var radius = PhysicalRadius;
                var wall = gameScene.Walls.FirstOrDefault(x => x.Collide(center, radius));
                if (wall != null) Fail();

                if (center.X - radius < gameScene.MapBorder.Left) Fail();
                if (center.Y - radius < gameScene.MapBorder.Top) Fail();
                if (center.X + radius > gameScene.MapBorder.Right) Fail();
                if (center.Y + radius > gameScene.MapBorder.Bottom) Fail();
            }
        }

        private bool CanShoot(Enemy enemy)
        {
            if (Scene is GameScene scene)
            {
                var skip = false;
                var rayStep = Point.Zero;
                var ray = Point.Zero;
                switch (CurrentDirection)
                {
                    case Direction.Left:
                        if (enemy.Position.X > Position.X)
                            skip = true;
                        else
                            rayStep = new Point(-RayInterval, 0);
                        break;
                    case Direction.Up:
                        if (enemy.Position.Y > Position.Y)
                            skip = true;
                        else
                            rayStep = new Point(0, -RayInterval);
                        break;
                    case Direction.Right:
                        if (enemy.Position.X < Position.X)
                            skip = true;
                        else
                            rayStep = new Point(RayInterval, 0);
                        break;
                    case Direction.Down:
                        if (enemy.Position.Y < Position.Y)
                            skip = true;
                        else
                            rayStep = new Point(0, RayInterval);
                        break;
                }
                while (!skip && Math.Abs(ray.X) + Math.Abs(ray.Y) < MaxRayLength)
                {
                    ray += rayStep;

                    var offset1 = CurrentDirection.IsHorizontal() ? new Point(0, -BigBoss.PhysicalRadius) : new Point(-BigBoss.PhysicalRadius, 0);
                    var offset2 = CurrentDirection.IsHorizontal() ? new Point(0, +BigBoss.PhysicalRadius) : new Point(+BigBoss.PhysicalRadius, 0);

                    var rayPoint1 = Position + ray + offset1;
                    var rayPoint2 = Position + ray + offset2;

                    if (scene.Walls.Any(x => x.Collide(rayPoint1)) || scene.Walls.Any(x => x.Collide(rayPoint2)))
                    {
                        return false;
                    }
                    if (enemy.Collide(rayPoint1) || enemy.Collide(rayPoint2))
                    {
                        return true;
                    }
                }
                return false;
            }
            return false;
        }
    }
}