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;
using System.Diagnostics;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// An enemy that hides away until the player passes above,
    /// at which moment the Pit Lurker will jump up and try to
    /// knock the player into the pit. Very frickin' annoying.
    /// </summary>
    class PitLurker : Enemy
    {
        #region Member Variables

        /// <summary>
        /// The texture of the Pit Lurker.
        /// </summary>
        static Texture2D _texture;

        /// <summary>
        /// The maximum health of Pit Lurkers.
        /// </summary>
        static int _maxHealth;

        /// <summary>
        /// The damage that Pit Lurkers deal.
        /// </summary>
        static int _damage;

        /// <summary>
        /// The states of the Pit Lurker.
        /// </summary>
        enum State
        {
            Waiting, Rising, Falling
        }

        /// <summary>
        /// The current state of this Pit Lurker.
        /// </summary>
        State _currentState;

        /// <summary>
        /// The previous state of this Pit Lurker.
        /// </summary>
        State _previousState;

        /// <summary>
        /// The Pit Lurker's animations.
        /// </summary>
        Dictionary<State, Animation> _animationSet;

        /// <summary>
        /// The original position of this Pit Lurker.
        /// </summary>
        Vector2 _originalPosition;

        /// <summary>
        /// The width of the Pit Lurker's detection hitbox, used when the
        /// Pit Lurker is waiting for its prey.
        /// </summary>
        static int _detectionThreshold;

        /// <summary>
        /// A divisor used in judging the vertical velocity of the jump
        /// depending on the difference between the vertical position of the
        /// player and the Pit Lurker.
        /// </summary>
        const float JumpDivisor = 12.5f;

        /// <summary>
        /// The variable used to make the Pit Lurker oscillate when in the
        /// falling state.
        /// </summary>
        float _theta;

        /// <summary>
        /// Determines the amplitude of the falling oscillation.
        /// </summary>
        const float OscillationAmplitude = 3.5f;

        /// <summary>
        /// Determines the frequency of the falling oscillation, as a
        /// divisor of 1 radian (2 * pi).
        /// </summary>
        const float OscillationFrequency = 40;

        /// <summary>
        /// The maximum descending speed of Pit Lurkers.
        /// </summary>
        const float MaxFallSpeed = 3;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the Pit Lurker's hitbox.
        /// </summary>
        public override Rectangle Hitbox
        {
            get { return new Rectangle((int)_position.X - 11, (int)_position.Y, 23, 32); }
        }

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

        #endregion

        public PitLurker(Room room, Vector2 position)
            : base(room, position)
        {
            _originalPosition = position;

            _currentState = State.Waiting;
            _health = _maxHealth;
        }

        #region Methods

        /// <summary>
        /// Initialises the Pit Lurker's static properties according to the game's difficulty setting.
        /// </summary>
        /// <param name="gameDifficulty">The game's current difficulty setting.</param>
        new public static void Initialise(GameDifficulty gameDifficulty)
        {
            switch (gameDifficulty)
            {
                case GameDifficulty.Easy:
                    _detectionThreshold = 32;
                    break;
                case GameDifficulty.Hard:
                    _detectionThreshold = 15;
                    break;
                default:
                    _detectionThreshold = 22;
                    break;
            }
            _maxHealth = 1;
            _damage = 2;
        }

        /// <summary>
        /// Initialises the Pit Lurker's animations.
        /// </summary>
        protected override void InitialiseAnimations()
        {
            //Create the set of animations.
            _animationSet = new Dictionary<State, Animation>();

            //Waiting.
            Animation waiting = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(), Vector2.Zero, 0), 0);
            _animationSet.Add(State.Waiting, waiting);

            //Rising.
            Animation rising = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(0, 0, 23, 32), new Vector2(-11, 0), 0), 0);
            _animationSet.Add(State.Rising, rising);

            //Falling.
            int animationSpeed = 8;
            Animation falling = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(0, 0, 23, 32), new Vector2(-11, 0), animationSpeed), 0);
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(24, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(48, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(72, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(96, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(120, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(144, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            falling.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(168, 0, 23, 32), new Vector2(-11, 0), animationSpeed));
            _animationSet.Add(State.Falling, falling);
        }

        /// <summary>
        /// Loads the assets required by the Pit Lurker.
        /// </summary>
        /// <param name="content">The game's content manager.</param>
        new public static void LoadContent(ContentManager content)
        {
            _texture = content.Load<Texture2D>("Images\\PitLurker");
        }

        /// <summary>
        /// Handles the waiting state - jumps up when the player intersects
        /// with a detection hitbox.
        /// </summary>
        private void Waiting()
        {
            //Construct the detection hitbox.
            Rectangle detectionHitbox = new Rectangle((int)_position.X - _detectionThreshold,
                                                      (int)_position.Y - _room.Area.Height,
                                                      _detectionThreshold * 2,
                                                      _room.Area.Height);

            //If the player intersects the detection hitbox, jump up.
            if (_room.PlayerOne != null)
            {
                if (_room.PlayerOne.Hitbox.Intersects(detectionHitbox))
                {
                    _currentState = State.Rising;

                    //Set the jump speed dependent on the position of the player relative
                    //to this Pit Lurker's position. However, if the player is jumping,
                    //don't consider the player's vertical speed.
                    if (_room.PlayerOne.CurrentState != Player.State.Jumping
                        || _room.PlayerOne.CurrentState != Player.State.Falling)
                    {
                        _speed.Y = EntityHelper.JumpVelocity(_position,
                            new Vector2(0, _room.PlayerOne.Position.Y - 72));
                    }
                    else
                    {
                        _speed.Y = EntityHelper.JumpVelocity(_position,
                            new Vector2(0, _room.PlayerOne.Position.Y + (_room.PlayerOne.Speed.Y * 4)));
                    }
                }
            }
        }

        /// <summary>
        /// Handles the rising state - this simply induces gravity and changes
        /// to the falling state when the Pit Lurker is falling.
        /// </summary>
        private void Rising()
        {
            //Induce gravity.
            _speed = EntityHelper.InduceGravity(_speed);

            //If the vertical speed is now positive, change to the falling state.
            if (_speed.Y > 0)
            {
                _theta = 0;
                _currentState = State.Falling;
            }
        }

        /// <summary>
        /// Handles the falling state - this makes the Pit Lurker drift back down
        /// into the pit from whence it came in a pretty sine wave motion. It
        /// changes to the waiting state once it's done.
        /// </summary>
        private void Falling()
        {
            //If we're below our original position, change the state and
            //return to the original position.
            if (_position.Y >= _originalPosition.Y)
            {
                _currentState = State.Waiting;
                _speed = Vector2.Zero;
                _position = _originalPosition;
            }
            //Otherwise, move in a pretty sine wave pattern.
            else
            {
                //Increase theta, and change the horizontal position accordingly.
                _theta += (float)(2 * Math.PI) / OscillationFrequency;
                _position.X += OscillationAmplitude * (float)Math.Cos(_theta);

                //Induce gravity and limit the vertical speed.
                _speed = EntityHelper.InduceGravity(_speed);
                _speed.Y = Math.Min(_speed.Y, MaxFallSpeed);
            } 
        }

        /// <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>
        /// Updates the Pit Lurker.
        /// </summary>
        /// <param name="gameTime">The current snapshot of time.</param>
        public override void Update(GameTime gameTime)
        {
            _position += _speed;

            switch (_currentState)
            {
                case State.Waiting:
                    Waiting();
                    break;
                case State.Rising:
                    Rising();
                    break;
                case State.Falling:
                    Falling();
                    break;
            }

            //Update animations.
            UpdatePreviousState();
            _animationSet[_currentState].Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// Draws the Pit Lurker to the screen.
        /// </summary>
        /// <param name="spriteBatch">The sprite batch to draw to.</param>
        public override void Draw(SpriteBatch spriteBatch)
        {
            //Account for post-hit invincibility flashing.
            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;

                spriteBatch.Draw(_texture,
                                 _position + currentFrameOffset,
                                 currentFrameSource,
                                 Color.White);
            }
        }

        #endregion
    }
}
