using LeMangeurDEnfants.Core.Scenes;
using Microsoft.Xna.Framework;
using MonoGame.FourEngine;
using MonoGame.FourEngine.Components;
using MonoGame.FourEngine.Resources;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using static MonoGame.FourEngine.Components.AnimationComponent;

namespace LeMangeurDEnfants.Core.Entities
{
    public class Enemy : BaseObject
    {
        public override Point Size { get; } = new Point(19);
        public override string FileName { get; } = Resources.Sprites.Enemy;
        public const float Speed = 24f;

        private const int RayInterval = BigBoss.PhysicalRadius;
        private const int MaxRayLength = 96;

        private SpriteDefinition ExclamationMarkSpriteDefinition
            => new SpriteDefinition
            {
                FileName = Resources.Sprites.ExclamationMark,
                Size = new Point(6, 13)
            };

        public Direction CurrentDirection
        {
            get => _currentDirection;
            set
            {
                _currentDirection = value;
                AnimationComponent.StartAnimation(
                    FramedAnimation.GenerateRange(
                        new Range(0, 6),
                        (int)_currentDirection,
                        160, true));
            }
        }

        // Sample: R15,15 T300 L10,6
        // Similar to XAML PathGeometry
        public string Path
        {
            get => _path;
            set
            {
                _path = value;
                ParsePath();
            }
        }

        private Queue<NavigationNode> _navigation = new Queue<NavigationNode>();
        private SpriteComponent _mark;
        private Vector2 _positionVector;
        private Direction _currentDirection;
        private string _path;
        private TimeSpan _waited;

        private bool _isMoving = true;
        private bool _isDead;
        
        public override void OnAttach(Scene scene)
        {
            base.OnAttach(scene);
            _positionVector = Position.ToVector2();
        }

        public override void OnUpdate(UpdateEventArgs args)
        {
            base.OnUpdate(args);
            if (_isDead)
            {
                SpriteComponent.Layer = 2;
                return;
            }

            if (!_navigation.Any()) return;

            if (_isMoving)
            {
                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;
                }
            }
            if (_navigation.Any() && _navigation.Peek() is NavigationNode node)
            {
                if (node.Type == NavigationNodeType.Wait)
                {
                    if (_waited > node.Wait)
                    {
                        _navigation.Dequeue();
                        _waited = TimeSpan.Zero;
                        CurrentDirection = node.Direction;
                        if (!_navigation.Any()) ParsePath();
                        _isMoving = true;
                    }
                    else
                    {
                        AnimationComponent.StopAnimation();
                        SpriteComponent.Frame = new Point(6, (int)_currentDirection);
                        _waited += args.DeltaTime;
                        _isMoving = false;
                    }
                }
                else
                {
                    if (CurrentDirection != node.Direction || !AnimationComponent.IsRunning)
                        CurrentDirection = node.Direction;

                    switch (CurrentDirection)
                    {
                        case Direction.Left:
                            if (Position.X <= node.Trigger.X)
                            {
                                _navigation.Dequeue();
                                if (!_navigation.Any()) ParsePath();
                            }
                            break;
                        case Direction.Up:
                            if (Position.Y <= node.Trigger.Y)
                            {
                                _navigation.Dequeue();
                                if (!_navigation.Any()) ParsePath();
                            }
                            break;
                        case Direction.Right:
                            if (Position.X >= node.Trigger.X)
                            {
                                _navigation.Dequeue();
                                if (!_navigation.Any()) ParsePath();
                            }
                            break;
                        case Direction.Down:
                            if (Position.Y >= node.Trigger.Y)
                            {
                                _navigation.Dequeue();
                                if (!_navigation.Any()) ParsePath();
                            }
                            break;
                    }
                }
            }
            Position = _positionVector.ToPoint();
            if (Scene is GameScene gameScene && gameScene.BigBoss != null)
            {
                LookAround(gameScene);
            }
        }

        public void Die()
        {
            if (_isDead) return;
            _isDead = true;
            AnimationComponent.StopAnimation();
            SpriteComponent.Frame = new Point(7, (int)_currentDirection);
            var boss = ((GameScene)Scene).BigBoss;
            boss.HasPistol = false;
            boss.ShootSfx.Play();
            for (var i = 0; i < 5; i++)
            {
                Scene.AddEntity(new Blood { Position = Position });
            }
        }

        private void ParsePath()
        {
            void FillTriggerNode(string query, NavigationNode node, Point fromPosition)
            {
                var args = query.Split(',');
                node.Type = NavigationNodeType.Trigger;
                node.Trigger = new Point(
                    int.Parse(args[0]) * GameScene.TileSize.X,
                    int.Parse(args[1]) * GameScene.TileSize.Y)
                    + fromPosition;
            }
            var absolutePosition = Position;
            try
            {
                _navigation.Clear();
                var elements = Path.Split(' ').Select(x => x.ToUpperInvariant()).ToList();
                foreach (var element in elements)
                {
                    var node = new NavigationNode();
                    var kindCode = element[0];
                    var query = element.Remove(0, 1);
                    switch (kindCode)
                    {
                        case 'T':
                            node.Direction = _navigation?.LastOrDefault()?.Direction ?? Direction.Down;
                            node.Type = NavigationNodeType.Wait;
                            node.Wait = TimeSpan.FromMilliseconds(float.Parse(query));
                            break;
                        case 'L':
                            node.Direction = Direction.Left;
                            FillTriggerNode(query, node, absolutePosition);
                            absolutePosition = node.Trigger;
                            break;
                        case 'U':
                            node.Direction = Direction.Up;
                            FillTriggerNode(query, node, absolutePosition);
                            absolutePosition = node.Trigger;
                            break;
                        case 'R':
                            node.Direction = Direction.Right;
                            FillTriggerNode(query, node, absolutePosition);
                            absolutePosition = node.Trigger;
                            break;
                        case 'D':
                            node.Direction = Direction.Down;
                            FillTriggerNode(query, node, absolutePosition);
                            absolutePosition = node.Trigger;
                            break;
                    }
                    _navigation.Enqueue(node);
                }
                if (_navigation.FirstOrDefault() is NavigationNode navigationNode)
                {
                    CurrentDirection = navigationNode.Direction;
                }
            }
            catch (Exception exception)
            {
                Debug.Fail(exception.ToString());
                throw;
            }
        }

        private void LookAround(GameScene scene)
        {
            var point = Position - scene.BigBoss.Position;
            var distance = Math.Abs(point.ToVector2().Length());
            if (distance < BigBoss.PhysicalRadius)
            {
                FoundBoss(scene.BigBoss);
                scene.BigBoss.Fail();
            }
            if (DoesSee(scene, scene.BigBoss))
                FoundBoss(scene.BigBoss);
            var follower = scene.BigBoss as IHaveFollower;
            while (follower?.NextFollower is BaseObject @object)
            {
                follower = (IFollower)@object;
                if (DoesSee(scene, @object))
                    FoundFollower();
            }
        }

        private bool DoesSee(GameScene scene, BaseObject @object)
        {
            var skip = false;
            var rayStep = Point.Zero;
            var ray = Point.Zero;
            switch (CurrentDirection)
            {
                case Direction.Left:
                    if (@object.Position.X > Position.X)
                        skip = true;
                    else
                        rayStep = new Point(-RayInterval, 0);
                    break;
                case Direction.Up:
                    if (@object.Position.Y > Position.Y)
                        skip = true;
                    else
                        rayStep = new Point(0, -RayInterval);
                    break;
                case Direction.Right:
                    if (@object.Position.X < Position.X)
                        skip = true;
                    else
                        rayStep = new Point(RayInterval, 0);
                    break;
                case Direction.Down:
                    if (@object.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 (@object.Collide(rayPoint1) || @object.Collide(rayPoint2))
                {
                    return true;
                }
            }
            return false;
        }

        private void FoundBoss(BigBoss boss)
        {
            if (boss.HasPistol && boss.CurrentDirection == CurrentDirection.GetOpposite())
            {
                Die();
            }
            else
            {
                FoundFollower();
            }
        }

        private void FoundFollower()
        {
            _isDead = true;
            AnimationComponent.StopAnimation();
            SpriteComponent.Frame = new Point(6, (int)_currentDirection);
            _mark = new SpriteComponent(ExclamationMarkSpriteDefinition)
            {
                Position = Position + new Point(-2, -20),
                Layer = int.MaxValue - 1
            };
            Components.Add(_mark);
            _mark.BeforeDraw += Mark_BeforeDraw;
            ((GameScene)Scene).BigBoss.Fail();
        }

        private void Mark_BeforeDraw(SpriteDrawEventArgs args)
        {
            ((GameScene)Scene).Camera.Transform(args);
        }

        public override void Dispose()
        {
            base.Dispose();
            if (_mark != null)
                _mark.BeforeDraw -= Mark_BeforeDraw;
        }
    }
}