using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Diagnostics;
using MegaManRipoff.UI;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// The handler for the event called when the player has picked up
    /// a health item.
    /// </summary>
    /// <param name="sender">The object calling the event.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void StartHealing(object sender, EventArgs e);

    /// <summary>
    /// The handler for the event called when the player has finished healing
    /// himself.
    /// </summary>
    /// <param name="sender">The object calling the event.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void FinishHealing(object sender, EventArgs e);

    /// <summary>
    /// The handler for the event called when the player has died.
    /// </summary>
    /// <param name="sender">The object calling the event..</param>
    /// <param name="e">Event arguments.</param>
    public delegate void Death(object sender, EventArgs e);

    /// <summary>
    /// The handler for the event called when the player collects the cake.
    /// </summary>
    /// <param name="sender">The object calling the event.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void CakeCollect(object sender, EventArgs e);

    /// <summary>
    /// The character the user controls in the game. In this game
    /// it's Mega Man, a robot fighting for peace in 20XX.
    /// </summary>
    class Player : HealthyEntity, IFocusable
    {
        #region Member Variables

        /// <summary>
        /// A reference to the current level.
        /// </summary>
        protected Level _level;

        /// <summary>
        /// Holds all possible states that the player can be in.
        /// </summary>
        public enum State
        {
            Standing, Running, Jumping, Falling, Hurt, Sliding, Dying, Dead
        }

        /// <summary>
        /// The player'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 player's texture.
        /// </summary>
        static Texture2D _texture;

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

        #region Physics Variables

        /// <summary>
        /// A vector representing the absolute maximum speed achievable in each direction.
        /// </summary>
        Vector2 _maxSpeed;

        /// <summary>
        /// The player's horizontal acceleration, used during user input. This is added with the current
        /// friction value before use.
        /// </summary>
        float _accel;

        /// <summary>
        /// The player's default horizontal acceleration.
        /// </summary>
        const float DefaultAccel = 0.9f;

        /// <summary>
        /// The player's horizontal acceleration when on an ice tile.
        /// </summary>
        const float IceAccel = 0.2f;

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

        /// <summary>
        /// The player's default horizontal deceleration, used in the event that there
        /// is no vertical collision with a tile.
        /// </summary>
        const float DefaultFriction = 0.7f;

        /// <summary>
        /// Is the player touching the floor? If false, they are in the air.
        /// </summary>
        bool _floor;

        /// <summary>
        /// The vertical speed added to the player when he jumps.
        /// </summary>
        const int JumpSpeed = -12;

        /// <summary>
        /// Has the player pressed jump?
        /// </summary>
        bool _hasJumped;

        #endregion

        #region Gameplay Variables

        /// <summary>
        /// Amount of health the player currently has.
        /// </summary>
        int _health;

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

        /// <summary>
        /// The new amount of health. This is used when the player is healing.
        /// </summary>
        int _newHealth;

        /// <summary>
        /// Keeps track of how many bullets the player has currently shot.
        /// </summary>
        int _numberOfBullets;

        /// <summary>
        /// The maximum amount of bullets the player can shoot at one time.
        /// </summary>
        static int _numberOfBulletsMax;

        /// <summary>
        /// The number of lives the player has.
        /// </summary>
        static int _lives;

        #endregion

        #region Timing Variables

        /// <summary>
        /// Keeps track of how long the player should remain in the "hurt" state.
        /// </summary>
        int _hurtTimer;

        /// <summary>
        /// The length of time, in ticks, to be in the "hurt" state upon being hurt.
        /// </summary>
        static int _hurtDuration;

        /// <summary>
        /// Keeps track of how long the player should remain in the flashing,
        /// post-hit invincibility state.
        /// </summary>
        int _invincibiltyTimer;

        /// <summary>
        /// The length of time, in ticks, to be in the post-hit invincibility state upon
        /// recovering from being hurt.
        /// </summary>
        static int _invincibilityDuration;

        /// <summary>
        /// Keeps track of how long the player should remain in the shooting sub-state.
        /// </summary>
        int _shootTimer;

        /// <summary>
        /// The length of time, in ticks, to be in the shooting sub-state.
        /// </summary>
        const int ShootDuration = 20;

        /// <summary>
        /// The speed at which the player's bullets move.
        /// </summary>
        const int BulletSpeed = 6;

        /// <summary>
        /// Keeps track of how long the player should remain in the sliding state.
        /// </summary>
        int _slideTimer;

        /// <summary>
        /// The length of time, in ticks, to be in the sliding state.
        /// </summary>
        const int SlideDuration = 25;

        /// <summary>
        /// Keeps track of how long to wait before incrementing the health and making
        /// the healing sound.
        /// </summary>
        int _healingGapTimer;

        /// <summary>
        /// The length of time, in ticks, between each health incrementation when the
        /// player is healing.
        /// </summary>
        const int HealingGapDuration = 4;

        #endregion

        #region Sound Effects

        /// <summary>
        /// Sound effect used when the player jumps.
        /// </summary>
        static SoundEffect _jumpSound;

        /// <summary>
        /// Sound effect used when the player lands on the floor.
        /// </summary>
        static SoundEffect _landSound;

        /// <summary>
        /// Sound effect used when the player slides.
        /// </summary>
        static SoundEffect _slideSound;

        /// <summary>
        /// Sound effect used when the player shoots.
        /// </summary>
        static SoundEffect _shootSound;

        /// <summary>
        /// Sound effect used when the player gets hurt.
        /// </summary>
        static SoundEffect _hurtSound;

        /// <summary>
        /// Sound effect used when the player dies.
        /// </summary>
        static SoundEffect _deathSound;

        /// <summary>
        /// Sound effect used when the player is healing.
        /// </summary>
        static SoundEffect _healingSound;

        /// <summary>
        /// Sound effect used when the player collects an extra life.
        /// </summary>
        static SoundEffect _extraLifeSound;

        #endregion

        #endregion

        #region Event Declarations

        /// <summary>
        /// The event called when the player has picked up a health item.
        /// </summary>
        public event StartHealing OnStartHealing;

        /// <summary>
        /// The event called when the player has finished healing himself.
        /// </summary>
        public event FinishHealing OnFinishHealing;

        /// <summary>
        /// The event called when the player dies.
        /// </summary>
        public event Death OnDeath;

        /// <summary>
        /// The event called when the player collects the cake.
        /// </summary>
        public event CakeCollect OnCakeCollect;

        #endregion

        #region Properties

        /// <summary>
        /// The point to focus the camera upon.
        /// </summary>
        public Vector2 FocusPoint
        {
            get { return new Vector2(_position.X + 17, _position.Y + 20); }
        }

        /// <summary>
        /// Accessor for the player's hitbox.
        /// </summary>
        public override Rectangle Hitbox
        {
            get
            {
                if (_currentState != State.Sliding)
                {
                    return new Rectangle((int)_position.X + 8, (int)_position.Y + 3, 18, 36);
                }
                else
                {
                    return new Rectangle((int)_position.X + 1, (int)_position.Y + 23, 32, 16);
                }
            }
        }

        /// <summary>
        /// Gets or sets the amount of health the player currently has.
        /// </summary>
        public override int Health
        {
            get { return _health; }
            set { _health = Math.Min(value, _maxHealth); }
        }

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

        /// <summary>
        /// Gets the amount of lives the player has.
        /// </summary>
        public static int Lives
        {
            get { return _lives; }
        }

        /// <summary>
        /// Gets the player's current state.
        /// </summary>
        public State CurrentState
        {
            get { return _currentState; }
        }

        /// <summary>
        /// Accessor for the player's previous state.
        /// </summary>
        public State PreviousState
        {
            get { return _currentState; }
        }

        /// <summary>
        /// Accessor for whether or not the player is shooting.
        /// </summary>
        public bool IsShooting
        {
            get { return (_shootTimer > 0); }
        }

        #endregion

        /// <summary>
        /// Creates a player.
        /// </summary>
        /// <param name="level">The level currently being played.</param>
        /// <param name="room">The room to spawn the player in.</param>
        /// <param name="position">The position to spawn the player at,
        /// relative to the room.</param>
        public Player(Level level, Room room, Vector2 position)
            : base(room, position)
        {
            _level = level;

            //Set the default physics variables.
            _maxSpeed = new Vector2(4, 12);
            _friction = DefaultFriction;
            _accel = DefaultAccel;
            _floor = true;
            _hasJumped = false;
            _currentState = State.Standing;
            _previousState = State.Standing;
            _direction = Direction.Right;

            //Set the default gameplay variables.
            _health = _maxHealth;
        }

        #region Methods

        /// <summary>
        /// Initialise all variables required by the player.
        /// </summary>
        new public static void Initialise(GameDifficulty gameDifficulty)
        {
            switch (gameDifficulty)
            {
                //Easy
                case GameDifficulty.Easy:
                    _maxHealth = 28;
                    _hurtDuration = 20;
                    _invincibilityDuration = 90;
                    _numberOfBulletsMax = 3;
                    break;
                //Hard
                case GameDifficulty.Hard:
                    _maxHealth = 14;
                    _hurtDuration = 30;
                    _invincibilityDuration = 20;
                    _numberOfBulletsMax = 3;
                    break;
                //Normal
                default:
                    _maxHealth = 28;
                    _hurtDuration = 30;
                    _invincibilityDuration = 60;
                    _numberOfBulletsMax = 3;
                    break;
            }
            _lives = 3;
        }

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

            //Standing.
            Animation standing = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(209, 11, 34, 40), Vector2.Zero, 120), 0);
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(253, 11, 34, 40), Vector2.Zero, 5));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(209, 11, 34, 40), Vector2.Zero, 100));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(253, 11, 34, 40), Vector2.Zero, 5));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(209, 11, 34, 40), Vector2.Zero, 15));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(47, 61, 34, 40), Vector2.Zero, 3));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(87, 61, 34, 40), Vector2.Zero, 15));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(127, 61, 34, 40), Vector2.Zero, 3));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(167, 61, 34, 40), Vector2.Zero, 3));
            standing.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(208, 61, 34, 40), Vector2.Zero, 15));
            _animationSet.Add(new Tuple<State, bool>(State.Standing, false), standing);

            //Standing and shooting.
            Animation standingShooting = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(38, 348, 45, 38), new Vector2(0, 2), 0), 0);
            _animationSet.Add(new Tuple<State, bool>(State.Standing, true), standingShooting);

            //Running.
            Animation running = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(40, 107, 26, 37), new Vector2(1, 1), 3), 0);
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(74, 107, 33, 37), new Vector2(-5, 1), 4));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(117, 107, 44, 38), new Vector2(-10, 1), 6));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(170, 107, 34, 36), new Vector2(-7, 2), 4));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(216, 107, 27, 37), new Vector2(1, 2), 3));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(253, 109, 25, 35), new Vector2(2, 4), 3));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(293, 107, 26, 37), new Vector2(1, 2), 4));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(334, 109, 37, 35), new Vector2(-5, 2), 6));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(384, 107, 28, 37), new Vector2(-1, 2), 4));
            running.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(427, 109, 25, 35), new Vector2(2, 4), 3));
            _animationSet.Add(new Tuple<State, bool>(State.Running, false), running);

            //Running and shooting.
            Animation runningShooting = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(36, 159, 36, 37), new Vector2(1, 1), 3), 0);
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(75, 159, 42, 37), new Vector2(-5, 1), 4));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(122, 158, 47, 38), new Vector2(-10, 1), 6));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(174, 160, 42, 36), new Vector2(-7, 2), 4));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(221, 159, 37, 37), new Vector2(1, 2), 3));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(263, 161, 35, 35), new Vector2(2, 4), 3));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(304, 159, 37, 37), new Vector2(1, 2), 4));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(346, 161, 43, 35), new Vector2(-5, 2), 6));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(395, 159, 39, 37), new Vector2(-1, 2), 4));
            runningShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(441, 161, 36, 35), new Vector2(2, 4), 3));
            _animationSet.Add(new Tuple<State, bool>(State.Running, true), runningShooting);

            //Jumping.
            Animation jumping = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(48, 226, 26, 37), new Vector2(3, 2), 2), 0);
            jumping.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(82, 215, 30, 48), new Vector2(0, 0), 2));
            jumping.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(117, 212, 29, 51), new Vector2(2, -1), 2));
            jumping.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(155, 209, 30, 54), new Vector2(1, -2), 3));
            jumping.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(196, 210, 31, 53), new Vector2(1, -1), 0));
            _animationSet.Add(new Tuple<State, bool>(State.Jumping, false), jumping);

            //Jumping and shooting.
            Animation jumpingShooting = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(248, 222, 36, 37), new Vector2(3, 2), 2), 0);
            jumpingShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(289, 211, 40, 48), new Vector2(0, 0), 2));
            jumpingShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(336, 208, 38, 51), new Vector2(2, -1), 2));
            jumpingShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(381, 205, 39, 54), new Vector2(1, -2), 3));
            jumpingShooting.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(428, 206, 38, 53), new Vector2(1, -1), 0));
            _animationSet.Add(new Tuple<State, bool>(State.Jumping, true), jumpingShooting);

            //Falling.
            Animation falling = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(196, 210, 31, 53), new Vector2(1, -1), 0), 0);
            _animationSet.Add(new Tuple<State, bool>(State.Falling, false), falling);

            //Falling.
            Animation fallingShooting = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(428, 206, 38, 53), new Vector2(1, -1), 0), 0);
            _animationSet.Add(new Tuple<State, bool>(State.Falling, true), fallingShooting);

            //Hurt.
            Animation hurt = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(70, 405, 32, 43), new Vector2(0, -2), 2), 2);
            hurt.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(112, 397, 48, 56), new Vector2(-8, -10), 1));
            hurt.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(167, 398, 36, 48), new Vector2(-2, -4), 2));
            hurt.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(210, 399, 36, 48), new Vector2(-2, -4), 2));
            _animationSet.Add(new Tuple<State, bool>(State.Hurt, false), hurt);
            _animationSet.Add(new Tuple<State, bool>(State.Hurt, true), hurt);

            //Dying.
            Animation dying = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(210, 399, 36, 48), new Vector2(-2, -4), 0), 0);
            _animationSet.Add(new Tuple<State, bool>(State.Dying, false), dying);
            _animationSet.Add(new Tuple<State, bool>(State.Dying, true), dying);

            //Sliding.
            Animation sliding = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(48, 291, 29, 36), new Vector2(3, 4), 2), 2);
            sliding.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(86, 299, 48, 28), new Vector2(-5, 13), 3));
            sliding.AddFrame(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(145, 300, 44, 28), new Vector2(-1, 13), 0));
            _animationSet.Add(new Tuple<State, bool>(State.Sliding, false), sliding);
            _animationSet.Add(new Tuple<State, bool>(State.Sliding, true), sliding);

            //Dead (invisible).
            Animation dead = new Animation(new Tuple<Rectangle, Vector2, int>
                (new Rectangle(), Vector2.Zero, 0), 0);
            _animationSet.Add(new Tuple<State, bool>(State.Dead, false), dead);
            _animationSet.Add(new Tuple<State, bool>(State.Dead, true), dead);
        }

        /// <summary>
        /// Load all assests asscociated with this entity.
        /// </summary>
        /// <param name="content">The content manager to load to.</param>
        new public static void LoadContent(ContentManager content)
        {
            //Load in the player texture.
            _texture = content.Load<Texture2D>("Images\\MegaMan7");

            //Load all sound effects used by the player.
            _jumpSound = content.Load<SoundEffect>("Sounds\\PlayerJump");
            _landSound = content.Load<SoundEffect>("Sounds\\PlayerLand");
            _slideSound = content.Load<SoundEffect>("Sounds\\PlayerSlide");
            _shootSound = content.Load<SoundEffect>("Sounds\\PlayerShoot");
            _hurtSound = content.Load<SoundEffect>("Sounds\\PlayerHurt");
            _deathSound = content.Load<SoundEffect>("Sounds\\PlayerDeath");
            _healingSound = content.Load<SoundEffect>("Sounds\\Healing");
            _extraLifeSound = content.Load<SoundEffect>("Sounds\\PlayerExtraLife");
        }

        /// <summary>
        /// Accepts input from the user user's input acts accordingly by starting the relevant
        /// timers/states/movement/etc.
        /// </summary>
        private void InduceInput()
        {
            //Don't bother doing anything if we're in the hurt state.
            if (_currentState != State.Hurt)
            {
                //Leftwards movement.
                if (InputHelper.IsLeftHeld() && !InputHelper.IsRightHeld())
                {
                    //Set speed only if we're not sliding.
                    if (_currentState != State.Sliding)
                    {
                        _speed.X = Math.Max(_speed.X - (_accel + _friction), -_maxSpeed.X);
                    }
                    _direction = Direction.Left;
                }
                else
                {
                    //Rightwards movement.
                    if (InputHelper.IsRightHeld() && !InputHelper.IsLeftHeld())
                    {
                        //Set speed only if we're not sliding.
                        if (_currentState != State.Sliding)
                        {
                            _speed.X = Math.Min(_speed.X + (_accel + _friction), _maxSpeed.X);
                        }
                        _direction = Direction.Right;
                    }
                }

                //Jump when the player is on the ground, and the jump button is pressed but the
                //down button isn't.
                if (InputHelper.IsJumpPressed() && !InputHelper.IsDownHeld() && _floor && !_hasJumped)
                {
                    //If we're sliding, we need to do an additional check to see that the player is not
                    //going to stand up in a thin passageway that the player is sliding through.
                    if (_currentState != State.Sliding || (_currentState == State.Sliding
                        && EntityHelper.CheckForSolidTiles(_room, new Rectangle((int)_position.X + 8, (int)_position.Y + 3, 18, 36)) == null))
                    {
                        _hasJumped = true;
                        _speed.Y = JumpSpeed;
                        _slideTimer = 0;
                    }
                }
                if (!InputHelper.IsJumpHeld())
                {
                    //If the player is in mid-jump and the user lets go of the jump key, stop 
                    //moving upwards. This just adds a bit more jumping control, which is always
                    //nice <3
                    if (_speed.Y < 0)
                    {
                        _speed.Y = Math.Min(_speed.Y + 4, 0);
                    }

                    //The user has let go of the jump button, so let the user be able to jump again.
                    _hasJumped = false;
                }

                //Sliding.
                if (InputHelper.IsJumpPressed() && InputHelper.IsDownHeld())
                {
                    //Only slide if we're not already sliding.
                    if (_slideTimer <= 0)
                    {
                        _slideTimer = SlideDuration;
                    }
                }

                //Shooting.
                if (InputHelper.IsShootPressed() && _currentState != State.Hurt && _currentState != State.Dying
                    && _currentState != State.Sliding)
                {
                    if (_numberOfBullets < _numberOfBulletsMax)
                    {
                        //Create a new bullet.
                        BasicBullet bullet = new BasicBullet(_room,
                                                             new Vector2(Hitbox.Center.X + (18 * (int)_direction),
                                                                         Hitbox.Center.Y - 5),
                                                             this,
                                                             1,
                                                             new Vector2(BulletSpeed * (int)_direction,
                                                                         0),
                                                             true);
                        _room.Add(bullet);

                        //Set our state to shooting and add one to the number of bullets shot.
                        _shootTimer = ShootDuration;
                        _numberOfBullets++;

                        //Subscribe to the new bullet's removal event.
                        bullet.OnBulletRemove += new BulletRemove(bullet_OnBulletRemove);
                    }
                }
            }
        }

        /// <summary>
        /// Changes the current speed vector by inducing friction. This method does not acutally
        /// move the player - use ApplyMovement() for that.
        /// </summary>
        private void InduceFriction()
        {
            //Don't update the friction while we're in the hurt or sliding state.
            if (_currentState != State.Hurt && _currentState != State.Sliding)
            {
                _speed = EntityHelper.InduceFriction(_speed, _friction);
            }
        }

        /// <summary>
        /// Calculates pixel-perfect movement for the player and applies it to its position
        /// vector. This method should only be called after a new speed vector is calculated.
        /// 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 air friction value.
            if (!_floor)
            {
                _friction = DefaultFriction;
                _accel = DefaultAccel;
            }
            //Otherwise, get the tile's friction value.
            else
            {
                if (tile != null)
                {
                    _friction = tile.Friction;

                    //Recalculate the acceleration based on the colliding tile's type.
                    if (tile is SolidTile)
                        _accel = DefaultAccel;
                    else
                        if (tile is IceTile)
                            _accel = IceAccel;
                }
            }
        }

        /// <summary>
        /// Updates the player's state by checking what he is currently doing.
        /// </summary>
        private void UpdateCurrentState()
        {
            //Don't bother doing anything if we're dying.
            if (_currentState != State.Dying)
            {
                //If there's no time on the hurt timer...
                if (_hurtTimer <= 0)
                {
                    //If we're in the air...
                    if (!_floor)
                    {
                        //If going up, we're jumping; otherwise we're falling.
                        if (_speed.Y < 0)
                        {
                            _currentState = State.Jumping;
                        }
                        else
                        {
                            _currentState = State.Falling;
                        }

                        //Stop sliding while we're in the air.
                        _slideTimer = 0;
                    }
                    //If we're on the ground...
                    else
                    {
                        //If there's no time on the slide timer, then we could be running or
                        //standing.
                        if (_slideTimer <= 0)
                        {
                            //If the left/right key is being pressed, we're running; 
                            //otherwise, we're standing.
                            if ((InputHelper.IsLeftHeld() ^ InputHelper.IsRightHeld())
                                && _level.CurrentState != Level.State.BossIntroduction
                                && _level.CurrentState != Level.State.BossDeath
                                && _level.CurrentState != Level.State.BossDeathAftermath)
                            {
                                _currentState = State.Running;
                            }
                            else
                            {
                                _currentState = State.Standing;
                            }
                        }
                        //If there is time on the slide counter, obviously we're sliding.
                        else
                        {
                            _currentState = State.Sliding;

                            //Cancel the shoot timer - we can't slide and shoot.
                            _shootTimer = 0;

                            //Always move the player whilst he is in the sliding state.
                            _speed.X = (int)_direction * (_maxSpeed.X + 1);

                            //Decrement the slide timer; if it is now zero, check to see if the
                            //player would stand up into a tile - if that is the case, keep
                            //sliding.
                            if (--_slideTimer == 0)
                            {
                                if (EntityHelper.CheckForSolidTiles(_room, new Rectangle((int)_position.X + 8, (int)_position.Y + 3, 18, 36)) != null)
                                    _slideTimer = 1;
                            }
                        }
                    }

                    //Decrement the post-hit invincibilty timer if needbe.
                    if (_invincibiltyTimer > 0)
                    {
                        _invincibiltyTimer--;
                    }
                }
                //Otherwise, we're being hurt, so update the hurt timers.
                else
                {
                    //Put ourselves in the hurt state if, for some reason, we weren't
                    //already in it.
                    if (_currentState != State.Hurt)
                    {
                        _currentState = State.Hurt;
                    }

                    //Cancel the shoot timer.
                    _shootTimer = 0;

                    //Decrement the timer; if it is now zero, put ourselves in the
                    //post-hit invincibility state.
                    if (--_hurtTimer == 0)
                    {
                        _invincibiltyTimer = _invincibilityDuration;
                    }
                }

                //Decrement the shoot timer if necessary.
                if (_shootTimer > 0)
                {
                    _shootTimer--;
                }
            }
            //We're dying - set the shoot timer to zero;
            else
            {
                _shootTimer = 0;
            }
        }

        /// <summary>
        /// Plays the relevant sound effects for the current state.
        /// </summary>
        private void PlaySoundEffects()
        {
            //If we were previously on the ground but we're now jumping, play the
            //jump sound effect.
            if (_previousState != State.Jumping && _currentState == State.Jumping)
            {
                _jumpSound.Play();
            }

            //If we were previously in the air but we're now on the ground it
            //means that we've landed, so play the land sound effect.
            if ((_previousState == State.Jumping || _previousState == State.Falling)
                && (_currentState == State.Standing || _currentState == State.Running || _currentState == State.Sliding))
            {
                _landSound.Play();
            }

            //If we were previously not sliding but we are now, play the slide
            //sound effect.
            if (_previousState != State.Sliding && _currentState == State.Sliding)
            {
                _slideSound.Play();
            }

            //If we were previously not hurt but we are now, play the hurt
            //sound effect.
            if (_previousState != State.Hurt && _currentState == State.Hurt)
            {
                _hurtSound.Play();
            }

            //If the shoot timer is the same as its set duration (minus one, because
            //the timer has already been decremented at this point) it means that
            //we've only just shot, so play the shoot sound effect.
            if (_shootTimer >= ShootDuration - 1)
            {
                _shootSound.Play();
            }
        }

        /// <summary>
        /// Sets the previous state to the current state. This must be done at a different time
        /// to ensure that the previous state and current state are, at some points in time, different.
        /// </summary>
        private void UpdatePreviousState()
        {
            //If the state is different to what it was last time, restart the
            //state's animation.
            if (_currentState != _previousState)
            {
                _animationSet[new Tuple<State, bool>(_currentState, IsShooting)].ToFrame(0);
            }

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

        /// <summary>
        /// Detects the camera's edges, and decides to move the player back into the screen,
        /// change room or die accordingly.
        /// </summary>
        private void DetectCameraEdges()
        {
            //If we're below the edge of the screen, die.
            if (!_room.Camera.Area.Intersects(Hitbox)
                && _position.Y > _room.Camera.Area.Bottom + 8)
            {
                Die();
            }
            else
            {
                //If we're trying to move off the right edge of the camera...
                if (Hitbox.Right >= _room.Camera.Area.Right)
                {
                    //If the next room is to the right, we're the camera's focus and the camera isn't already
                    //scrolling, scroll right.
                    if ((_room.NextRoomLocation == NextRoomLocation.TopRight || _room.NextRoomLocation == NextRoomLocation.BottomRight)
                        && _room.Camera.Focus == this && !_room.Camera.IsScrolling)
                    {
                        _room.Camera.StartScrolling(ScrollDirection.Right, Level.SCROLL_DURATION);

                        //Cancel any invincibility time so that we don't scroll whilst in the middle
                        //of flashing and thus not appear.
                        _invincibiltyTimer = 0;

                        //Subscribe to the camera's finish scrolling event.
                        _room.Camera.OnScrollFinish += new ScrollFinish(GameCamera_OnScrollFinish);
                    }
                    //Otherwise, just limit our position.
                    else
                    {
                        _position = new Vector2((float)Math.Floor(_position.X - Math.Abs(_speed.X)),
                                                _position.Y);
                    }
                }

                //If we're trying to move off the left edge of the camera, limit our position.
                if (Hitbox.Left <= _room.Camera.Area.Left)
                {
                    _position = new Vector2((float)Math.Ceiling(_position.X + Math.Abs(_speed.X)),
                                            _position.Y);
                }

                //If we're trying to move off the top edge of the camera...
                if (Hitbox.Top <= _room.Camera.Area.Top)
                {
                    //Wait until we're fully off the top of the camera before moving back down.
                    if (Hitbox.Bottom <= (_room.Camera.Area.Top - 16))
                    {
                        _position = new Vector2(_position.X,
                                                (float)Math.Ceiling(_position.Y + Math.Abs(_speed.Y)));
                    }
                }

                //If we're trying to move off the bottom edge of the camera, the next room
                //is below us, we're the focus of the camera and the camera isn't already scrolling,
                //scroll down.
                if (Hitbox.Bottom >= _room.Camera.Area.Bottom && _room.NextRoomLocation == NextRoomLocation.Down
                    && _room.Camera.Focus == this && !_room.Camera.IsScrolling)
                {
                    _room.Camera.StartScrolling(ScrollDirection.Down, Level.SCROLL_DURATION);

                    //Cancel any invincibility time so that we don't scroll whilst in the middle
                    //of flashing and thus not appear.
                    _invincibiltyTimer = 0;

                    //Subscribe to the camera's finish scrolling event.
                    _room.Camera.OnScrollFinish += new ScrollFinish(GameCamera_OnScrollFinish);
                }
            }
        }

        /// <summary>
        /// Checks for any intersecting entity hitboxes, and deals with them accordingly.
        /// </summary>
        private void CollideWithEntities()
        {
            //Don't collide if we're being hurt or dying.
            if (_currentState != State.Hurt && _currentState != State.Dying)
            {
                //Iterate through all current entities.
                foreach (Entity entity in _room.Entities)
                {
                    //If the two hitboxes collide...
                    if (Hitbox.Intersects(entity.Hitbox))
                    {
                        //Enemy collision.
                        if (entity is Enemy)
                        {
                            //If we're not in post-hit invincibility and the two hitboxes
                            //intersect, get hurt.
                            if (_invincibiltyTimer <= 0)
                            {
                                //Cast the entity as an enemy to access its damage property.
                                Enemy enemy = entity as Enemy;
                                GetHurt(enemy.Damage);
                            }
                        }

                        //Item collision.
                        if (entity is Item)
                        {
                            //If the item is a small health capsule...
                            if (entity is SmallHealth)
                            {
                                //Cast the entity as a health capsule to access its health boost property.
                                SmallHealth smallHealth = entity as SmallHealth;
                                HealHealth(smallHealth.HealthBoost);
                            }
                            else
                            {
                                //If the item is a large health capsule...
                                if (entity is LargeHealth)
                                {
                                    //Cast the entity as a health capsule to access its health boost property.
                                    LargeHealth largeHealth = entity as LargeHealth;
                                    HealHealth(largeHealth.HealthBoost);
                                }
                                else
                                {
                                    //If the item is an extra life...
                                    if (entity is ExtraLife)
                                    {
                                        //Increase the number of lives and play a sound effect.
                                        _lives++;
                                        _extraLifeSound.Play();
                                    }
                                    else
                                    {
                                        //If the item is the cake...
                                        if (entity is Cake)
                                        {
                                            //Call the cake collection event if there are subscribers.
                                            if (OnCakeCollect != null)
                                                OnCakeCollect(this, new EventArgs());
                                        }
                                    }
                                }
                            }

                            //Finally, destroy the item.
                            _room.Remove(entity);
                        }

                        //Stop further iterations.
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Checks for any intersecting Enemy bullet hitboxes, and gets hurt accordingly.
        /// </summary>
        private void CollideWithBullets()
        {
            //Be ready to store a reference to a bullet upon collision so that it can be removed
            //once we have finished iterating through the list of bullets.
            Bullet collidingBullet = null;

            //Iterate through the list of bullets.
            foreach (Bullet bullet in _room.Bullets)
            {
                //If the enemy bullet is intersecting our hitbox and we're not being hurt...
                if (bullet.BulletOwner is Enemy && Hitbox.Intersects(bullet.Hitbox)
                    && _currentState != State.Hurt && _invincibiltyTimer <= 0)
                {
                    //Take some damage.
                    GetHurt(bullet.Damage);

                    //Store a reference to the bullet if it is supposed to be destroyed upon
                    //a collision.
                    if (bullet.DestroyOnCollision)
                    {
                        collidingBullet = bullet;
                    }

                    //Stop iteration.
                    break;
                }
            }

            //If we collided with a bullet, remove the bullet. This can't be done during the
            //foreach loop because we cannot change the list during its iteration.
            if (collidingBullet != null)
            {
                _room.Bullets.Remove(collidingBullet);
            }
        }

        /// <summary>
        /// Starts gradually healing the player.
        /// </summary>
        /// <param name="amount">The amount of health to heal.</param>
        private void HealHealth(int amount)
        {
            //Only bother doing something if the health isn't at the maximum.
            if (_health < _maxHealth)
            {
                //Set the new health variable.
                _newHealth = Math.Min(_health + amount, _maxHealth);

                //If there are subscribers, call the start healing event.
                if (OnStartHealing != null)
                    OnStartHealing(this, new EventArgs());
            }
        }

        /// <summary>
        /// Causes the player's health to gradually increase and go "bee-dee-dee-dee..."
        /// after the player picks up a health item. This method does not start healing the
        /// player - use HealHealth(int amount) for that.
        /// </summary>
        private void Healing()
        {
            //If the game is not frozen because of the player's healing, throw an exception.
            if (_level.CurrentState != Level.State.PlayerHealing)
                throw new InvalidOperationException("The level is not frozen due to " +
                    "the player's healing. If gradual healing was intended, use " +
                    "HealHealth(amount) instead.");
            
            //Decrease the healing gap timer; if it is now zero, increase the health.
            if (--_healingGapTimer <= 0)
            {
                //If the player's health is equal to the target health value, stop healing.
                if (_health == _newHealth)
                {
                    //If there are subscribers, call the finish healing event.
                    if (OnFinishHealing != null)
                        OnFinishHealing(this, new EventArgs());
                }
                //Otherwise, keep healing.
                else
                {
                    //Increase the health and play the sound.
                    _health = Math.Min(++_health, _newHealth);
                    _healingSound.Play();

                    //Reset the timer for the next loop.
                    _healingGapTimer = HealingGapDuration;
                }
            }
        }

        /// <summary>
        /// Deals damage to the player. :( If the damage is enough to reduce the player's health
        /// to zero, this method will kill the player. :((((
        /// </summary>
        /// <param name="damage">The amount of damage to take.</param>
        protected override void GetHurt(int damage)
        {
            _health = Math.Max(0, _health - damage);

            //If the enemy dealt enough damage to bring our health to zero, then
            //kill the player.
            if (_health == 0)
            {
                Die();
            }
            //Otherwise, we've only been hurt.
            else
            {
                //Set the hurt timer.
                _hurtTimer = _hurtDuration;

                _speed.X = -2 * (int)_direction;
                _speed.Y = -4;
            }
        }

        /// <summary>
        /// Kills the player. :(
        /// </summary>
        protected override void Die()
        {
            //Only die if we're not dying.
            if (_currentState != State.Dying)
            {
                _currentState = State.Dying;
                _health = 0;

                //If there is at least one subscriber, call the death event.
                if (OnDeath != null)
                {
                    OnDeath(this, new EventArgs());
                }

                //Subscribe to the level's death unfrozen event, so that we know
                //when to explode into a trillion pieces.
                _level.OnPlayerDeathUnfreeze += new PlayerDeathUnfreeze(_level_OnPlayerDeathUnfreeze);
                Debug.WriteLine("deathunfreeze subscription");
            }
        }

        /// <summary>
        /// Provides debugging controls.
        /// </summary>
        private void DebugControls()
        {
            //If we're not in debug mode, throw an exception.
            if (!Game1.DEBUG)
                throw new InvalidOperationException("The debug controls method " +
                    "was called when the game was not in debug mode.");

            //Flying controls.
            if (InputHelper.IsKeyHeld(Keys.W))
                _speed.Y = -10;
            if (InputHelper.IsKeyHeld(Keys.A))
                _speed.X = -10;
            if (InputHelper.IsKeyHeld(Keys.S))
                _speed.Y = 10;
            if (InputHelper.IsKeyHeld(Keys.D))
                _speed.X = 10;

            //Destroy all enemies.
            if (InputHelper.IsKeyPressed(Keys.D1))
                foreach (Entity entity in _room.Entities)
                    if (entity is Enemy)
                    {
                        _room.Add(new ExplosionEffect(_room, entity.Position, null));
                        _room.Remove(entity);
                    }

            //Take damage.
            if (InputHelper.IsKeyHeld(Keys.Subtract))
                GetHurt(1);

            //Gain health.
            if (InputHelper.IsKeyHeld(Keys.Add))
                _health = Math.Min(++_health, _maxHealth);

            //Camera shake.
            if (InputHelper.IsKeyPressed(Keys.H))
                _room.Camera.StartShaking();

            //Create new player if this is the original player.
            if (InputHelper.IsKeyPressed(Keys.P))
            {
                if (_room.PlayerOne.Equals(this))
                    _room.Add(new Player(_level, _room, _position + new Vector2(32 * (int)_direction, 0)));
            }
        }

        /// <summary>
        /// Perform the player's update game loop.
        /// </summary>
        /// <param name="gameTime">The current snapshot of time.</param>
        public override void Update(GameTime gameTime)
        {
            //If we're not recovering health...
            if (_level.CurrentState != Level.State.PlayerHealing)
            {
                //If the camera isn't scrolling and we're not dead, update stuff!
                if (!_room.Camera.IsScrolling && _currentState != State.Dying
                    && _currentState != State.Dead)
                {
                    //Update physics.
                    if (_level.CurrentState != Level.State.BossIntroduction
                        && _level.CurrentState != Level.State.BossDeath
                        && _level.CurrentState != Level.State.BossDeathAftermath)
                        InduceInput();

                    InduceFriction();
                    _speed = EntityHelper.InduceGravity(_speed);
                    DetectCameraEdges();
                    ApplyMovement();

                    //Check for collisions with enemies and bullets.
                    if (_level.CurrentState != Level.State.BossIntroduction
                        && _level.CurrentState != Level.State.BossDeath
                        && _level.CurrentState != Level.State.BossDeathAftermath)
                    {
                        CollideWithEntities();
                        CollideWithBullets();
                    }

                    //Update the State enum.
                    UpdateCurrentState();

                    //Play required sound effects.
                    PlaySoundEffects();

                    UpdatePreviousState();
                }

                //Update the animation frames regardless.
                _animationSet[new Tuple<State, bool>(_currentState, IsShooting)].Update();
            }

            //If the level has paused so that we can recover health, recover health.
            if (_level.CurrentState == Level.State.PlayerHealing)
                Healing();

            if (Game1.DEBUG)
                DebugControls();
        }

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

                //Make an integer and sprite effects form of the current
                //direction for calculations.
                float directionMultiplier;
                SpriteEffects directionEffects;
                if (_direction == Direction.Right)
                {
                    directionMultiplier = 1f;
                    directionEffects = SpriteEffects.None;
                }
                else
                {
                    directionMultiplier = 0.5f;
                    directionEffects = SpriteEffects.FlipHorizontally;
                }

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

        #endregion

        #region Event Handles

        /// <summary>
        /// Method called when one of the player's bullets has left the screen.
        /// </summary>
        /// <param name="sender">The bullet object that was removed.</param>
        /// <param name="e">Event arguments (none).</param>
        void bullet_OnBulletRemove(object sender, EventArgs e)
        {
            //Reduce our number of bullets counter.
            _numberOfBullets--;

            //Unsubscribe from this event.
            BasicBullet bullet = sender as BasicBullet;
            bullet.OnBulletRemove -= bullet_OnBulletRemove;
        }

        /// <summary>
        /// Method called when the camera stops scrolling.
        /// </summary>
        /// <param name="sender">The camera object.</param>
        /// <param name="e">Scrolling event arguments.</param>
        void GameCamera_OnScrollFinish(object sender, ScrollEventArgs e)
        {
            //Update the player's room reference.
            _room = e.Room;

            //Reset the number of bullets shot (in case the player shot and scrolled
            //before the bullets left the camera view).
            _numberOfBullets = 0;

            //Unsubscribe from this event.
            _room.Camera.OnScrollFinish -= GameCamera_OnScrollFinish;
        }

        /// <summary>
        /// Method called when the level has unfrozen after the player has died.
        /// </summary>
        /// <param name="sender">The level.</param>
        /// <param name="e">Event arguments (none).</param>
        void _level_OnPlayerDeathUnfreeze(object sender, EventArgs e)
        {
            //Explode into a trillion pieces and play the sound effect.
            EntityHelper.MegaManExplosion(_room, new Vector2(Hitbox.Center.X, Hitbox.Center.Y));
            _deathSound.Play();

            //Die.
            _currentState = State.Dead;

            //Decrease the number of lives.
            _lives--;

            //Unsubscribe from this event.
            _level.OnPlayerDeathUnfreeze -= _level_OnPlayerDeathUnfreeze;
        }

        #endregion
    }
}