using Microsoft.Xna.Framework;
using MonoGame.FourEngine;
using MonoGame.FourEngine.Interfaces;
using MonoGame.FourEngine.Raycast;
using MonoGame.FourEngine.Resources;
using Raycast.Win32.Scenes;
using Raycast.Win32.Services;
using System;
using System.Collections.Generic;
using static MonoGame.FourEngine.ServiceLocator;

namespace Raycast.Win32.Entities
{
    public abstract partial class Enemy : RaycastObject, IHaveSizeAndPosition
    {
        protected static readonly Random Random = new Random(DateTime.UtcNow.Millisecond);

        public abstract string FileName { get; }
        public abstract Point Size { get; }

        public abstract float Radius { get; }
        public abstract float Speed { get; }

        public abstract IDictionary<EnemyState, Range> AnimationMap { get; }

        private int _health;
        public int Health
        {
            get => _health;
            protected set
            {
                if (_health > 0 && value <= 0)
                {
                    OnDie();
                }
                _health = value;
            }
        }

        public virtual float MinVerticalPosition => 0f;

        private Orientation _orientation;
        public Orientation Orientation
        {
            get => _orientation;
            set
            {
                _orientation = value;
                Direction = Orientation.GetDirection();
            }
        }

        private Vector2 _direction;
        public Vector2 Direction
        {
            get => _direction;
            set => _direction = value;
        }

        protected override SpriteDefinition SpriteDefinition
            => new SpriteDefinition
            {
                FileName = FileName,
                Size = Size
            };

        protected Player Player => Scene?.Player;

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

        protected RenderedScene Scene { get; private set; }

        public Enemy()
        {
            InitializeStateMachine();

            State = EnemyState.Chase;
        }

        public virtual void OnAttack() { }
        public virtual void OnHurt() { }
        public virtual void OnDie() { }

        public void Hurt(int damage, Vector2 direction, float speed)
        {
            if (State == EnemyState.Death) return;

            OnHurt();

            Health -= damage;
            if (Health <= 0)
            {
                State = EnemyState.Death;
            }
            else
            {
                var hurtState = (HurtState)StateHandlers[EnemyState.Hurt];
                hurtState.HurtDirection = direction;
                hurtState.HurtSpeed = speed;
                State = EnemyState.Hurt;
            }
        }

        public override void UpdateFrameByCamera(IRaycastCamera camera)
        {
            if (State == EnemyState.Death)
            {
                return;
            }

            var diff = Position - camera.Position;
            // TODO: Support more directions?

            var frame = new Point();
            if (Math.Abs(diff.X) > Math.Abs(diff.Y))
            {
                if (Orientation == Orientation.West) diff.X *= -1;
                frame = new Point(diff.X > 0 ? 3 : 1, Frame.Y);
                if (Orientation == Orientation.South) frame.X--;
                if (Orientation == Orientation.North) frame.X++;
            }
            else
            {
                if (Orientation == Orientation.West) diff.Y *= -1;
                frame = new Point(diff.Y > 0 ? 0 : 2, Frame.Y);
                if (Orientation == Orientation.South) frame.X--;
                if (Orientation == Orientation.North) frame.X++;
            }

            if (frame.X < 0) frame.X += Sprite.FrameCount.X;
            if (frame.X >= Sprite.FrameCount.X) frame.X -= Sprite.FrameCount.X;

            if (frame.Y < 0) frame.Y += Sprite.FrameCount.Y;
            if (frame.Y >= Sprite.FrameCount.Y) frame.Y -= Sprite.FrameCount.Y;

            Frame = frame;
        }

        protected abstract EnemyState Decide();

        public override void OnAttach(Scene scene)
        {
            Scene = scene as RenderedScene;
        }

        public override void OnDetach(Scene scene)
        {
            Scene = null;
        }

        public override void Dispose()
        {
            base.Dispose();
            
            ShutdownStateMachine();
        }

        #region IHaveSizeAndPosition
        Point IHaveSizeAndPosition.Position
        {
            get => Position.ToPoint();
            set => Position = value.ToVector2();
        }
        int IHaveSizeAndPosition.Width { get; set; }
        int IHaveSizeAndPosition.Height { get; set; }
        #endregion

        #region IHaveReadOnlySizeAndPosition
        Point IHaveReadOnlySizeAndPosition.Position => ((IHaveSizeAndPosition)this).Position;
        int IHaveReadOnlySizeAndPosition.Width => ((IHaveSizeAndPosition)this).Width;
        int IHaveReadOnlySizeAndPosition.Height => ((IHaveSizeAndPosition)this).Height;
        #endregion
    }
}