using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// A cute little robot who hides under an indestructable hard 
    /// hat. He occasionally sticks his head out to shoot - this 
    /// is the only time he is vulnerable. Metools are to the Mega Man
    /// series as Goombas are to Mario.
    /// </summary>
    class Metool : Enemy
    {
        #region Member Variables

        #region Basic Enemy Fields

        /// <summary>
        /// Holds all possible states that the Metool can be in.
        /// </summary>
        enum State
        {
            Hiding, PoppingUp, Shooting, PoppingDown, Running
        }

        /// <summary>
        /// The Metool's current state.
        /// </summary>
        State _currentState;

        /// <summary>
        /// Hold the previous state so that animations can be restarted
        /// upon switching state.
        /// </summary>
        State _previousState;

        /// <summary>
        /// The entity's texture.
        /// </summary>
        static Texture2D _texture;

        /// <summary>
        /// The list of the entity's animations.
        /// </summary>
        Dictionary<State, Animation> _animationSet;

        /// <summary>
        /// The maximum amount of health the Metool can have.
        /// </summary>
        static int _maxHealth;

        /// <summary>
        /// The amount of damage Metools deal upon collision.
        /// </summary>
        static int _damage;

        /// <summary>
        /// A random number generator.
        /// </summary>
        Random _random;

        /// <summary>
        /// The Metool's horizontal deceleration.
        /// </summary>
        float _friction;

        /// <summary>
        /// Is the Metool touching the floor? If false, it is in the air.
        /// </summary>
        bool _floor;

        #endregion

        #region Metool Specific

        /// <summary>
        /// Determines if this Metool should run after shooting.
        /// </summary>
        bool _shouldRun;

        /// <summary>
        /// Timing variable used for timing things. Useage differs for each state.
        /// </summary>
        int _timer;

        /// <summary>
        /// The length of time, in ticks, to wait whilst the player is out of range.
        /// </summary>
        static int _outOfRangeDuration;

        /// <summary>
        /// The length of time, in ticks, to wait when the player shoots.
        /// </summary>
        static int _shotsFiredDuration;

        /// <summary>
        /// The length of time, in ticks, to wait whilst the Metool pops up/down.
        /// </summary>
        static int _poppingDuration;

        /// <summary>
        /// The horizontal velocity used when running.
        /// </summary>
        static int _runningSpeed;

        /// <summary>
        /// The length of time, in ticks, to run for.
        /// </summary>
        static int _runningDuration;

        /// <summary>
        /// The length of time, in ticks, to wait after the Metool shoots.
        /// </summary>
        static int _timeAfterShootingDuration;

        /// <summary>
        /// The speed of the bullets that Metools shoot.
        /// </summary>
        static int _bulletSpeed;

        /// <summary>
        /// The amount of damage Metool bullets deal.
        /// </summary>
        static int _bulletDamage;

        /// <summary>
        /// Should Metools shoot three bullets?
        /// </summary>
        static bool _bulletTriple;

        #endregion

        #endregion

        #region Properties

        /// <summary>
        /// Accessor for the Metool's hitbox.
        /// </summary>
        public override Rectangle Hitbox
        {
            get
            {
                if (_currentState == State.Hiding || _currentState == State.PoppingDown)
                {
                    return new Rectangle((int)_position.X + 2, (int)_position.Y + 9, 16, 12);
                }
                else
                {
                    return new Rectangle((int)_position.X + 2, (int)_position.Y, 16, 21);
                }
            }
        }

        /// <summary>
        /// Gets the maximum amount of health for Metools.
        /// </summary>
        public override int MaxHealth
        {
            get { return _maxHealth; }
        }

        /// <summary>
        /// Gets the amount of damage Metools deal.
        /// </summary>
        public override int Damage
        {
            get { return _damage; }
        }

        #endregion

        /// <summary>
        /// Creates a new Metool.
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="position">The position to create the Metool at.</param>
        /// <param name="shouldRun">Whether or not the Metool should run
        /// after shooting.</param>
        public Metool(Room room, Vector2 position, bool shouldRun)
            : base(room, position)
        {
            _shouldRun = shouldRun;

            _currentState = State.Hiding;
            _health = _maxHealth;
            _random = new Random();
        }

        #region Methods

        /// <summary>
        /// Initialise the Metool's static properties based on the difficulty setting.
        /// </summary>
        new public static void Initialise(GameDifficulty gameDifficulty)
        {
            switch (gameDifficulty)
            {
                //Easy
                case GameDifficulty.Easy:
                    _outOfRangeDuration = 30;
                    _shotsFiredDuration = 70;
                    _poppingDuration = 30;
                    _runningSpeed = 0;
                    _runningDuration = 0;
                    _timeAfterShootingDuration = 90;
                    _bulletSpeed = 3;
                    _bulletTriple = false;
                    break;
                //Hard
                case GameDifficulty.Hard:
                    _outOfRangeDuration = 20;
                    _shotsFiredDuration = 40;
                    _poppingDuration = 6;
                    _runningSpeed = 5;
                    _runningDuration = 27;
                    _timeAfterShootingDuration = 30;
                    _bulletSpeed = 5;
                    _bulletTriple = true;
                    break;
                //Normal
                default:
                    _outOfRangeDuration = 30;
                    _shotsFiredDuration = 50;
                    _poppingDuration = 10;
                    _runningSpeed = 4;
                    _runningDuration = 35;
                    _timeAfterShootingDuration = 40;
                    _bulletSpeed = 4;
                    _bulletTriple = true;
                    break;
            }
            _maxHealth = 1;
            _damage = 2;
            _bulletDamage = 2;
        }

        /// <summary>
        /// Create all animations associated with the Metool.
        /// </summary>
        protected override void InitialiseAnimations()
        {
            //Create the set of animations.
            _animationSet = new Dictionary<State, Animation>();

            //Hiding.
            Animation hiding = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(0, 11, 20, 12), new Vector2(0, 9), 0), 0);
            _animationSet.Add(State.Hiding, hiding);

            //Popping up.
            Animation poppingUp = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(23, 6, 20, 17), new Vector2(0, 4), 5), 1);
            poppingUp.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(46, 2, 22, 21), new Vector2(-1, 0), 0));
            _animationSet.Add(State.PoppingUp, poppingUp);

            //Shooting.
            Animation shooting = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(71, 0, 20, 23), new Vector2(0, -2), 10), 1);
            shooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(46, 2, 22, 21), new Vector2(-1, 0), 0));
            _animationSet.Add(State.Shooting, shooting);

            //Popping down.
            Animation poppingDown = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(23, 6, 20, 17), new Vector2(0, 4), 5), 1);
            poppingDown.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(0, 11, 20, 12), new Vector2(0, 9), 0));
            _animationSet.Add(State.PoppingDown, poppingDown);

            //Running.
            Animation running = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(194, 1, 23, 22), new Vector2(-1, -1), 6), 0);
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(46, 2, 22, 21), new Vector2(-1, 0), 3));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(244, 2, 20, 21), new Vector2(0, 0), 3));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(244, 2, 20, 21), new Vector2(0, 0), 3));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(267, 1, 20, 22), new Vector2(0, -1), 6));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(244, 2, 20, 21), new Vector2(0, 0), 3));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(244, 2, 20, 21), new Vector2(0, 0), 3));
            _animationSet.Add(State.Running, running);
        }

        /// <summary>
        /// Load all assests asscociated with the Metool.
        /// </summary>
        /// <param name="content">The content manager to load to.</param>
        new public static void LoadContent(ContentManager content)
        {
            //Load in a default texture.
            Metool._texture = content.Load<Texture2D>("Images/Metool");
        }

        /// <summary>
        /// Moves the Metool according to its speed vector. This method also adjusts the
        /// friction and acceleration value dependent on which tiles are being collided with.
        /// </summary>
        private void ApplyMovement()
        {
            //Move horizontally.
            EntityHelper.HorizontalCollision(_room, Hitbox, ref _position, ref _speed);

            //Move vertically and judge the new friction value based on what type of tile
            //is currently being collided with.
            Tile tile = EntityHelper.VerticalCollision(_room, Hitbox, ref _position, ref _speed, ref _floor);

            //If the player is in the air, use the default friction value.
            if (!_floor)
            {
                _friction = Tile.DEFAULT_FRICTION;
            }
            //Otherwise, get the tile's friction value.
            else
            {
                if (tile != null)
                    _friction = tile.Friction;
            }
        }

        /// <summary>
        /// Handles the hiding state - the Metool will pop up providing the
        /// player is in range and he hasn't shot for a while.
        /// </summary>
        /// <param name="detectionRadius"></param>
        private void Hiding(float detectionRadius)
        {
            if (_room.PlayerOne != null)
            {
                //If the player is in range...
                if (EntityHelper.PointDistance(Hitbox.Center, _room.PlayerOne.Hitbox.Center) <= detectionRadius)
                {
                    //Decrease the timer; if it is now zero, pop up.
                    if (--_timer <= 0)
                    {
                        _currentState = State.PoppingUp;

                        //Reset the timer for the popping up state.
                        _timer = _poppingDuration;
                    }
                }
                else
                //Not in range, so reset the timer.
                {
                    _timer = _outOfRangeDuration;
                }

                //If the player has shot, wait even longer!!
                if (_room.PlayerOne.IsShooting)
                {
                    //Add a random number on so that all the Metools don't
                    //shoot at once, creating an ear splitting sound.
                    _timer = _shotsFiredDuration + _random.Next(1, 10);
                }
            }
        }

        /// <summary>
        /// Handles the popping up state - the Metool shoots at the end
        /// of this state.
        /// </summary>
        private void PoppingUp()
        {
            //Decrease the timer; if it is now zero, shoot.
            if (--_timer <= 0)
            {
                _currentState = State.Shooting;

                //Get the current direction and decide if we need to alter the direction
                //that the bullets are shot.
                int _directionChanger = 0;
                if (_direction == Direction.Left) { _directionChanger = 180; }

                //Shoot!!
                _room.Add(new BasicBullet(_room, new Vector2(_position.X + 10, _position.Y + 10),
                    this, _bulletDamage, EntityHelper.RadialToVector(_bulletSpeed, MathHelper.ToRadians(_directionChanger)),
                    false));

                //Only shoot these two extra ones if we're supposed to shoot three bullets.
                if (_bulletTriple)
                {
                    _room.Add(new BasicBullet(_room, new Vector2(_position.X + 10, _position.Y + 10),
                        this, _bulletDamage, EntityHelper.RadialToVector(_bulletSpeed, MathHelper.ToRadians(45 + _directionChanger)),
                        false));
                    _room.Add(new BasicBullet(_room, new Vector2(_position.X + 10, _position.Y + 10),
                        this, _bulletDamage, EntityHelper.RadialToVector(_bulletSpeed, MathHelper.ToRadians(315 + _directionChanger)),
                        false));
                }

                //Play shoot sound.
                Enemy.ShootSound.Play();

                //Reset the timer for the shooting state. If we're supposed
                //to be running next, set the timer for a slightly shorter
                //time instead.
                if (_shouldRun)
                    _timer = (int)(_timeAfterShootingDuration * 0.66);
                else
                    _timer = _timeAfterShootingDuration;
            }
        }

        /// <summary>
        /// Handles the shooting state - at the end of this state, the Metool
        /// will run if its supposed to. Otherwise, it will pop down.
        /// </summary>
        private void Shooting()
        {
            //Decrease the timer; if it is now zero, do something.
            if (--_timer <= 0)
            {
                //If we're set to run, run. Otherwise, pop down.
                if (_shouldRun)
                {
                    _currentState = State.Running;

                    //Reset the timer for the popping down state.
                    _timer = _runningDuration;
                }
                else
                {
                    _currentState = State.PoppingDown;

                    //Reset the timer for the popping down state.
                    _timer = _poppingDuration;
                }
            }
        }

        /// <summary>
        /// Handles the running state - moves horizontally, and pops down
        /// when it has finished running.
        /// </summary>
        private void Running()
        {
            //Move horizontally according to our direction.
            _speed.X = (float)(_runningSpeed + Math.Ceiling(_friction)) * (int)_direction;

            //Decrease the timer; if it is now zero, pop down.
            if (--_timer <= 0)
            {
                _currentState = State.PoppingDown;

                //Reset the timer for the hiding state.
                _timer = _poppingDuration;
            }
        }

        /// <summary>
        /// Handles the popping down state - this is simply an animation
        /// state, and the Metool will resume hiding afterwards.
        /// </summary>
        private void PoppingDown()
        {
            //Decrease the timer; if it is now zero, hide.
            if (--_timer <= 0)
            {
                _currentState = State.Hiding;

                //Reset the timer for the hiding state.
                _timer = _shotsFiredDuration + _timeAfterShootingDuration;
            }
        }

        /// <summary>
        /// Updates the reference to the previous state.
        /// </summary>
        private void UpdatePreviousState()
        {
            //If the state is different to what it was last time, restart the
            //state's animation.
            if (_currentState != _previousState)
            {
                _animationSet[_currentState].ToFrame(0);
            }

            //Update the previous state.
            _previousState = _currentState;
        }

        /// <summary>
        /// Changes whether or not to deflect bullets based on the current state.
        /// </summary>
        private void ChangeDeflectStatus()
        {
            switch (_currentState)
            {
                case State.Hiding:
                case State.PoppingDown:
                    _deflectBullets = true;
                    break;
                default:
                    _deflectBullets = false;
                    break;
            }
        }

        /// <summary>
        /// Performs the Metool's update game loop.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            //Apply any movement.
            _speed = EntityHelper.InduceFriction(_speed, _friction);
            _speed = EntityHelper.InduceGravity(_speed);
            ApplyMovement();

            //Update AI.
            switch (_currentState)
            {
                case State.Hiding:
                    Hiding(210);
                    break;
                case State.PoppingUp:
                    PoppingUp();
                    break;
                case State.Shooting:
                    Shooting();
                    break;
                case State.Running:
                    Running();
                    break;
                case State.PoppingDown:
                    PoppingDown();
                    break;
            }
            ChangeDeflectStatus();
            UpdatePreviousState();

            //Update visuals.
            if (_currentState != State.Running)
                FacePlayer();

            _animationSet[_currentState].Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// Draw the entity to the screen.
        /// </summary>
        /// <param name="spriteBatch">The sprite batch to draw to.</param>
        public override void Draw(SpriteBatch spriteBatch)
        {
            //Only draw the enemy if the modulus of half of the invincibilty timer is
            //0 - this gives a flashing effect when the enemy is in post-hit
            //invincibility mode, and makes it appear normally if not.
            if ((_invincibiltyTimer / 2) % 2 == 0)
            {
                //Get the necessary details from the current animation frame.
                Rectangle currentFrameSource = _animationSet[_currentState].CurrentFrame.Item1;
                Vector2 currentFrameOffset = _animationSet[_currentState].CurrentFrame.Item2;

                //Decide how to flip the Metool based on its direction.
                SpriteEffects directionEffects;
                if (_direction == Direction.Right)
                    directionEffects = SpriteEffects.FlipHorizontally;
                else
                    directionEffects = SpriteEffects.None;

                spriteBatch.Draw(_texture,
                                    _position + new Vector2(currentFrameOffset.X, currentFrameOffset.Y),
                                    currentFrameSource,
                                    Color.White,
                                    0,
                                    Vector2.Zero,
                                    1,
                                    directionEffects,
                                    0.2f);
            }
        }

        #endregion
    }
}
