using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
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.Media;
using System.Diagnostics;
using MegaManRipoff.UI;

namespace MegaManRipoff.MainGameClasses
{
    #region Delegate Declarations

    /// <summary>
    /// The handler for the event called when the level has become unfrozen
    /// after waiting for a while when the player dies.
    /// </summary>
    /// <param name="sender">The sender object.</param>
    /// <param name="e">The event arugments, if any.</param>
    public delegate void PlayerDeathUnfreeze(object sender, EventArgs e);

    /// <summary>
    /// Holds event data for when the level should end.
    /// </summary>
    public class LevelQuitEventArgs : EventArgs
    {
        public enum QuitReason
        {
            /// <summary>
            /// The level has quit because the user chose to quit from the pause screen.
            /// </summary>
            MenuQuit,
            /// <summary>
            /// The level has quit because the level has been beaten.
            /// </summary>
            Victory,
            /// <summary>
            /// The level has quit because the player is out of lives.
            /// </summary>
            OutOfLives,
            /// <summary>
            /// The level has quit because the player collected the cake.
            /// </summary>
            CakeCollected,
        }

        /// <summary>
        /// The reason why the level has quit.
        /// </summary>
        public QuitReason CurrentQuitReason { get; private set; }

        public LevelQuitEventArgs(QuitReason quitReason)
        {
            CurrentQuitReason = quitReason;
        }
    }

    /// <summary>
    /// The handler for the event called when the level should end.
    /// </summary>
    /// <param name="sender">The sender object.</param>
    /// <param name="e">Quit event arguments.</param>
    public delegate void Quit(object sender, LevelQuitEventArgs e);

    #endregion

    /// <summary>
    /// Stores all things needed to play a level.
    /// </summary>
    class Level
    {
        #region Member Variables

        /// <summary>
        /// The current game instance.
        /// </summary>
        MainGame _mainGame;

        /// <summary>
        /// The player.
        /// </summary>
        Player _playerOne;

        /// <summary>
        /// The level's boss. A reference to the boss is kept so that the
        /// level can quit when it is destroyed.
        /// </summary>
        IBoss _boss;

        /// <summary>
        /// The strength of gravity.
        /// </summary>
        public const float GRAVITY = 0.6f;

        /// <summary>
        /// The list of rooms in the level.
        /// </summary>
        List<Room> _rooms;

        /// <summary>
        /// The index of the most recent room that contains a checkpoint.
        /// </summary>
        int _latestCheckpointIndex;

        /// <summary>
        /// The index of the currently played room in the level.
        /// </summary>
        int _currentRoom;

        /// <summary>
        /// List of currently active rooms (multiple rooms are used so that
        /// good-looking room transitions can occur)
        /// </summary>
        List<Room> _activeRooms;

        /// <summary>
        /// A list of all backgrounds that the rooms use.
        /// </summary>
        List<Background> _backgrounds;

        /// <summary>
        /// The path of this level, relative to the content folder.
        /// </summary>
        string _levelPath;

        /// <summary>
        /// Asset name of the level's tileset.
        /// </summary>
        string _tileAssetName;

        /// <summary>
        /// Stores the types of music that play in the level.
        /// </summary>
        public enum MusicType
        {
            /// <summary>
            /// Silence. Or a remix of John Cage's 4'33, if you prefer.
            /// </summary>
            None,
            /// <summary>
            /// The level's main background music.
            /// </summary>
            Level,
            /// <summary>
            /// The boss fight's background music.
            /// </summary>
            Boss,
            /// <summary>
            /// The victory jingle.
            /// </summary>
            Victory
        }

        /// <summary>
        /// The collection of each type of song used in the level.
        /// </summary>
        Dictionary<MusicType, Song> _music;

        /// <summary>
        /// The currently played music in the level.
        /// </summary>
        Song _currentMusic;

        /// <summary>
        /// The level's background music asset name. The music is loaded from the level file.
        /// </summary>
        string _backgroundMusicAssetName;

        /// <summary>
        /// The level's camera.
        /// </summary>
        GameCamera _gameCamera;

        /// <summary>
        /// The level's heads up display.
        /// </summary>
        HeadsUpDisplay _headsUpDisplay;

        /// <summary>
        /// The level's pause screen.
        /// </summary>
        PauseScreen _pauseScreen;

        /// <summary>
        /// The pause screen's menu items, used to subscribe and unsubscribe to/from
        /// their selection events.
        /// </summary>
        MenuItem _resumeItem, _quitItem;

        /// <summary>
        /// Determines how long the level's room transitions should last,
        /// in ticks.
        /// </summary>
        public const int SCROLL_DURATION = 60;

        #region State Variables

        /// <summary>
        /// Holds all states of play of the level.
        /// </summary>
        public enum State
        {
            /// <summary>
            /// The level is running normally.
            /// </summary>
            Normal,
            /// <summary>
            /// The level is frozen because the user paused the game.
            /// </summary>
            Paused,
            /// <summary>
            /// The level is frozen because the player has just died.
            /// </summary>
            PlayerDeath,
            /// <summary>
            /// The level is waiting for a while after the player has died
            /// so that it can restart.
            /// </summary>
            PlayerDeathAftermath,
            /// <summary>
            /// The level is frozen because the player picked up an item
            /// that is restoring his health.
            /// </summary>
            PlayerHealing,
            /// <summary>
            /// The level is paused because a boss is being introduced.
            /// </summary>
            BossIntroduction,
            /// <summary>
            /// The level is waiting for a while after the boss has died
            /// so that it can play the victory music.
            /// </summary>
            BossDeath,
            /// <summary>
            /// The level is waiting for a while after the victory music
            /// has played so that it can finish.
            /// </summary>
            BossDeathAftermath
        }

        /// <summary>
        /// Holds the current state of the level.
        /// </summary>
        State _currentState;

        /// <summary>
        /// An all-purpose tick timer. Its use is dependent on the current state.
        /// </summary>
        int _timer;

        /// <summary>
        /// The length of time, in ticks, to pause after the player has just died
        /// so that the user can bask in his own defeat.
        /// </summary>
        const int PlayerDeathDuration = 40;

        /// <summary>
        /// The length of time, in ticks, to wait after the player has exploded
        /// before restarting the level.
        /// </summary>
        const int PlayerDeathAftermathDuration = 200;

        /// <summary>
        /// The length of time, in ticks, to wait after the victory music has played
        /// before finishing the level.
        /// </summary>
        const int BossDeathAftermathDuration = 300;

        /// <summary>
        /// The current boss introduction instance.
        /// </summary>
        BossIntroduction _bossIntroduction;

        /// <summary>
        /// The length of time, in ticks, it takes to make the level music fade out.
        /// </summary>
        const int MusicFadeDuration = 100;

        #endregion

        #endregion

        #region Event Declarations

        /// <summary>
        /// The event called when the level has become unfrozen after waiting
        /// for a while when the player dies.
        /// </summary>
        public event PlayerDeathUnfreeze OnPlayerDeathUnfreeze;

        /// <summary>
        /// The event called when the level has ended.
        /// </summary>
        public event Quit OnQuit;

        #endregion

        #region Properties

        /// <summary>
        /// Accessor for player 1 in the room.
        /// </summary>
        public Player PlayerOne
        {
            get { return _playerOne; }
        }

        /// <summary>
        /// Accessor for the list of current rooms.
        /// </summary>
        public List<Room> ActiveRooms
        {
            get { return _activeRooms; }
        }

        /// <summary>
        /// Gets the current state of the level.
        /// </summary>
        public State CurrentState
        {
            get { return _currentState; }
        }

        #endregion

        /// <summary>
        /// Creates a new level.
        /// </summary>
        /// <param name="mainGame">The current game instance.</param>
        /// <param name="levelPath">The path of this level, relative to the content folder.</param>
        /// <param name="tileAssetName">The name of the texture asset for the tileset.</param>
        /// <param name="backgroundMusicAssetName">The name of the music asset for the background
        /// music.</param>
        /// <param name="rooms">The list of rooms in the level.</param>
        /// <param name="backgrounds">The list of backgrounds.</param>
        public Level(MainGame mainGame, string levelPath, string tileAssetName, string backgroundMusicAssetName,
            List<Room> rooms, List<Background> backgrounds)
        {
            _mainGame = mainGame;
            _levelPath = levelPath;
            _tileAssetName = tileAssetName;
            _backgroundMusicAssetName = backgroundMusicAssetName;
            _rooms = rooms;
            _backgrounds = backgrounds;

            _latestCheckpointIndex = 0;

            //Assign the backgrounds to this level.
            foreach (Background background in backgrounds)
                background.AssignLevel(this);
        }

        #region Methods

        /// <summary>
        /// Initialise all things needed to play the level.
        /// </summary>
        /// <param name="lives">The number of lives the player will start with.</param>
        public void Initialise()
        {
            //Set up the room with the most recent checkpoint.
            _currentRoom = _latestCheckpointIndex;
            _activeRooms = new List<Room>();
            _activeRooms.Add(_rooms[_currentRoom]);

            //Instantiate player, camera and heads up display.
            Vector2 activeRoomLocation = new Vector2(_activeRooms[0].Area.Left, _activeRooms[0].Area.Top);
            _playerOne = new Player(this, _activeRooms[0], activeRoomLocation + _activeRooms[0].Checkpoint);

            _gameCamera = new GameCamera(_mainGame, this, _playerOne.FocusPoint);
            _gameCamera.Focus = _playerOne;

            _headsUpDisplay = new HeadsUpDisplay(this);
            _headsUpDisplay.AddNormalHealthBar(_playerOne, new Vector2(8, 8), Color.White);
            _headsUpDisplay.AddLivesCounter(new Vector2(8, 260));

            _activeRooms[0].Initialise(_playerOne, _gameCamera, _headsUpDisplay);

            //Subscribe to the player's events.
            _playerOne.OnDeath += new Death(_playerOne_OnPlayerDeath);
            _playerOne.OnStartHealing += new StartHealing(_playerOne_OnStartHealing);
            _playerOne.OnFinishHealing += new FinishHealing(_playerOne_OnFinishHealing);
            _playerOne.OnCakeCollect += new CakeCollect(_playerOne_OnCakeCollect);

            //Subscribe to the camera's events.
            _gameCamera.OnScrollStart += new ScrollStart(_gameCamera_OnScrollStart);
            _gameCamera.OnScrollFinish += new ScrollFinish(_gameCamera_OnScrollFinish);

            //Subscribe to the HUD's events.
            _headsUpDisplay.OnNewBossHealthBar += new NewBossHealthBar(_headsUpDisplay_OnNewBossHealthBar);

            _boss = null;

            //Display the debug text if necessary.
            if (Game1.DEBUG)
                _headsUpDisplay.AddText("debug", new Vector2(24, 8), Color.LawnGreen, Color.Black);

            //Reset the music if this isn't our first pass through the Initialise method.
            if (_music != null)
                _currentMusic = _music[MusicType.Level];
            MediaPlayer.IsRepeating = true;

            _currentState = State.Normal;
        }

        /// <summary>
        /// Loads all assets required by the level.
        /// </summary>
        /// <param name="content">The game's content manager.</param>
        public void LoadContent(ContentManager content)
        {
            //Load level-dependent assets.
            Tile.LoadContent(content, _levelPath + _tileAssetName);

            //Load in and assign music.
            _music = new Dictionary<MusicType, Song>();
            _music[MusicType.Level] = content.Load<Song>(_levelPath + _backgroundMusicAssetName);
            _music[MusicType.Boss] = content.Load<Song>("Music\\Boss");
            _music[MusicType.Victory] = content.Load<Song>("Music\\Victory");
            _currentMusic = _music[MusicType.Level];

            foreach (Background background in _backgrounds)
                background.LoadContent(content, _levelPath);
        }

        /// <summary>
        /// Checks if start has been inputted, and creates the pause menu accordingly.
        /// </summary>
        private void CreatePauseScreen()
        {
            //Only allow creation of the pause screen if the game is not frozen or waiting.
            if (_currentState == State.Normal && InputHelper.IsStartPressed())
            {
                //Create the pause screen and subscribe to the pause menu's close event.
                _pauseScreen = new PauseScreen();
                _pauseScreen.Menu.OnClose += new Close(_pauseScreen_OnClose);

                //Create the items and subscribe to any events.
                _resumeItem = new MenuItem(_pauseScreen.Menu, "Resume", new Vector2(220, 109));
                _resumeItem.OnSelected += new Selected(_resumeItem_OnSelected);
                _pauseScreen.Menu.AddItem(_resumeItem);

                _quitItem = new MenuItem(_pauseScreen.Menu, "Quit", new Vector2(220, 139), 70);
                _quitItem.OnSelected += new Selected(_quitItem_OnSelected);
                _pauseScreen.Menu.AddItem(_quitItem);

                //Pause the music.
                MediaPlayer.Pause();

                //Set the state. 
                _currentState = State.Paused;
            }
        }

        /// <summary>
        /// Plays the current background music.
        /// </summary>
        private void PlayBackgroundMusic()
        {
            //Only attempt to play if we're supposed to play music.
            if (_currentMusic != null && Game1.MUSIC)
            {
                //Play the background music if we're not already.
                if (MediaPlayer.State != MediaState.Playing && _currentState != State.PlayerDeath
                    && _currentState != State.PlayerDeathAftermath && _currentState != State.BossDeath
                    && _currentState != State.BossDeathAftermath)
                {
                    MediaPlayer.Play(_currentMusic);
                }
            }
        }

        #region State-related Methods

        /// <summary>
        /// Handles the player death state - reduces the timer and tells the player
        /// to explode into a trillion pieces.
        /// </summary>
        private void PlayerDeath()
        {
            if (--_timer <= 0)
            {
                //The player is told to explode into a trillion pieces via this
                //event call.
                OnPlayerDeathUnfreeze(this, new EventArgs());

                //Change to the aftermath state and set its timer.
                _currentState = State.PlayerDeathAftermath;
                _timer = PlayerDeathAftermathDuration;
            }
        }

        /// <summary>
        /// Handles the player death aftermath state - reduces the timer and restarts
        /// the level, or goes to the game over screen if the player is out of lives.
        /// </summary>
        private void PlayerDeathAftermath()
        {
            if (--_timer <= 0)
            {
                //If the player has lives left, restart the level.
                if (Player.Lives > 0)
                    Initialise();
                //Otherwise, go to the game over screen if there is a subscriber
                //to the quit event.
                else
                    if (OnQuit != null)
                        OnQuit(this, new LevelQuitEventArgs(LevelQuitEventArgs.QuitReason.OutOfLives));
            }
        }

        /// <summary>
        /// Handles the boss introduction state - causes the level music to gradually
        /// fade out and be replaced by the boss music.
        /// </summary>
        private void BossIntroductionMethod()
        {
            if (_currentMusic == _music[MusicType.Level])
            {
                //Cause the level music to fade out.
                MediaPlayer.Volume -= 1 / (float)MusicFadeDuration;
                Debug.WriteLine(MediaPlayer.Volume);

                //If the volume is now zero, or the music is never playing in the first place...
                if (MediaPlayer.Volume <= 0)
                {
                    //Change the music to the boss music if we're supposed to be playing music.
                    MediaPlayer.Stop();

                    _currentMusic = _music[MusicType.Boss];
                    MediaPlayer.Volume = 1;

                    //Only actually play it if we're supposed to.
                    if (Game1.MUSIC)
                        MediaPlayer.Play(_currentMusic);

                    //Tell the boss introduction to increase the health.
                    _bossIntroduction.StartHealing();
                }
            }
        }

        /// <summary>
        /// Handles the boss death state - reduces the timer and plays the victory
        /// music once it expires.
        /// </summary>
        private void BossDeath()
        {
            if (--_timer <= 0)
            {
                //Play the victory music.
                MediaPlayer.Stop();
                MediaPlayer.IsRepeating = false;
                _currentMusic = _music[MusicType.Victory];

                if (Game1.MUSIC)
                    MediaPlayer.Play(_currentMusic);

                //Change state and reset the timer.
                _currentState = State.BossDeathAftermath;
                _timer = BossDeathAftermathDuration;
            }
        }

        /// <summary>
        /// Handles the player death aftermath state - reduces the timer and restarts
        /// the level, or goes to the game over screen if the player is out of lives.
        /// </summary>
        private void BossDeathAftermath()
        {
            if (--_timer <= 0)
            {
                //Go to the next level if there is a subscriber to the quit event.
                if (OnQuit != null)
                    OnQuit(this, new LevelQuitEventArgs(LevelQuitEventArgs.QuitReason.Victory));
            }
        }

        #endregion

        /// <summary>
        /// Updates the current room by triggering the relevant update methods.
        /// </summary>
        /// <param name="gameTime">The current snapshot of time.</param>
        public void Update(GameTime gameTime)
        {
            //If the game isn't paused...
            if (_currentState != State.Paused)
            {
                //Play the background music.
                PlayBackgroundMusic();

                //Update the active room(s) if the camera is not scrolling and if
                //the game is not frozen.
                if (!_gameCamera.IsScrolling && _currentState != State.PlayerDeath
                    && _currentState != State.PlayerHealing && _currentState != State.BossIntroduction)
                {
                    foreach (Room room in _activeRooms)
                        room.Update(gameTime);
                }

                //Update the camera.
                _gameCamera.Update(gameTime);

                //Update the player if he isn't dead.
                if (_playerOne.CurrentState != Player.State.Dead)
                    _playerOne.Update(gameTime);

                //Handle state timers.
                switch (_currentState)
                {
                    case State.PlayerDeath:
                        PlayerDeath();
                        break;
                    case State.PlayerDeathAftermath:
                        PlayerDeathAftermath();
                        break;
                    case State.BossIntroduction:
                        BossIntroductionMethod();
                        _bossIntroduction.Update();
                        break;
                    case State.BossDeath:
                        BossDeath();
                        break;
                    case State.BossDeathAftermath:
                        BossDeathAftermath();
                        break;
                }

                //Allow pausing if a boss isn't being introduced.
                if (_currentState != State.BossIntroduction)
                    CreatePauseScreen();
            }
            //If the game is paused...
            else
            {
                //Update the pause screen.
                _pauseScreen.Update(gameTime);
            }
        }

        /// <summary>
        /// Add the level and its contents to the drawing sprite batch.
        /// </summary>
        /// <param name="spriteBatch">The sprite batch to draw to.</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            //Background sprite batch.
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap,
                null, null, null, _gameCamera.Transformation);

            //Draw the backgrounds.
            foreach (Background background in _backgrounds)
                background.Draw(spriteBatch);

            spriteBatch.End();

            //In-game object sprite batch.
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap,
                null, null, null, _gameCamera.Transformation);

            //Draw the active rooms.
            foreach (Room room in _activeRooms)
                room.Draw(spriteBatch);

            //Only draw the player if he isn't dead.
            if (_playerOne.CurrentState != Player.State.Dead)
                _playerOne.Draw(spriteBatch);

            spriteBatch.End();

            //Heads up display and pause screen sprite batch.
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointWrap,
                null, null, null, _gameCamera.ScaleTransformation);

            _headsUpDisplay.Draw(spriteBatch);

            //Only draw the pause screen if it's open.
            if (_pauseScreen != null)
                _pauseScreen.Draw(spriteBatch);

            spriteBatch.End();
        }

        #endregion

        #region Event Handles

        #region Pause Screen

        /// <summary>
        /// Method called when the pause screen is closed.
        /// </summary>
        /// <param name="sender">The pause screen that closed.</param>
        /// <param name="e">Event arguments (none).</param>
        void _pauseScreen_OnClose(object sender, EventArgs e)
        {
            //Reset the state.
            _currentState = State.Normal;

            //Unsubscribe from the pause screen's events and set the reference to null.
            _pauseScreen.Menu.OnClose -= _pauseScreen_OnClose;
            _resumeItem.OnSelected -= _resumeItem_OnSelected;
            _quitItem.OnSelected -= _quitItem_OnSelected;
            _pauseScreen = null;

            //Unpause the music.
            MediaPlayer.Resume();
        }

        /// <summary>
        /// Method called when the resume item is selected in the pause menu.
        /// </summary>
        /// <param name="sender">The menu item that was selected.</param>
        /// <param name="e">Event arguments (none).</param>
        void _resumeItem_OnSelected(object sender, EventArgs e)
        {
            //Call the pause screen closed method.
            _pauseScreen_OnClose(sender, e);
        }

        /// <summary>
        /// Method called when the quit item is selected in the pause menu.
        /// </summary>
        /// <param name="sender">The menu item that was selected.</param>
        /// <param name="e">Event arguments (none).</param>
        void _quitItem_OnSelected(object sender, EventArgs e)
        {
            //If there are subscribers, call the level's quit event - the reason for quitting is
            //because the user chose to quit from the pause screen.
            if (OnQuit != null)
                OnQuit(this, new LevelQuitEventArgs(LevelQuitEventArgs.QuitReason.MenuQuit));
        }

        #endregion

        #region Camera


        /// <summary>
        /// Method called when the camera starts a room transition.
        /// </summary>
        /// <param name="sender">The game camera object that is scrolling.</param>
        /// <param name="e">The room that the camera started in.</param>
        void _gameCamera_OnScrollStart(object sender, ScrollEventArgs e)
        {
            //Add the new room to the list of active rooms.
            _activeRooms.Add(_rooms[_currentRoom + 1]);
            _rooms[_currentRoom + 1].Initialise(_playerOne, _gameCamera, _headsUpDisplay);

            //Increase the current room index.
            _currentRoom++;
        }

        /// <summary>
        /// Method called when the camera finishes a room transition.
        /// </summary>
        /// <param name="sender">The game camera object that is scrolling.</param>
        /// <param name="e">The room that the camera finished in.</param>
        void _gameCamera_OnScrollFinish(object sender, ScrollEventArgs e)
        {
            //Hold a temporary reference to the newly active room.
            Room newActiveRoom = _activeRooms[1];

            //Make this new active room the only room in the active rooms list.
            _activeRooms = new List<Room>();
            _activeRooms.Add(newActiveRoom);

            //If this new room contains a checkpoint, update the latest checkpoint
            //index.
            if (_activeRooms[0].Checkpoint != Room.NO_CHECKPOINT)
                _latestCheckpointIndex = _currentRoom;
        }

        #endregion

        #region Player

        /// <summary>
        /// Method called when the player starts healing himself.
        /// </summary>
        /// <param name="sender">The player instance that started healing.</param>
        /// <param name="e">Event arguments (none).</param>
        void _playerOne_OnStartHealing(object sender, EventArgs e)
        {
            //Set the frozen state.
            _currentState = State.PlayerHealing;
        }

        /// <summary>
        /// Method called when the player finishes healing himself.
        /// </summary>
        /// <param name="sender">The player instance that finished healing.</param>
        /// <param name="e">Event arguments (none).</param>
        void _playerOne_OnFinishHealing(object sender, EventArgs e)
        {
            //Reset the state.
            _currentState = State.Normal;
        }

        /// <summary>
        /// Method called when the player dies.
        /// </summary>
        /// <param name="sender">The player object that died.</param>
        /// <param name="e">Event arguments (none).</param>
        void _playerOne_OnPlayerDeath(object sender, EventArgs e)
        {
            //Set our state and timer.
            _currentState = State.PlayerDeath;
            _timer = PlayerDeathDuration;

            //Unsubscribe from the camera's scrolling events.
            _gameCamera.OnScrollStart -= _gameCamera_OnScrollStart;
            _gameCamera.OnScrollFinish -= _gameCamera_OnScrollFinish;

            //Unsubscribe from the player's death event (cause he's dead).
            _playerOne.OnDeath -= _playerOne_OnPlayerDeath;

            //Unsubscribe to boss related events.
            _headsUpDisplay.OnNewBossHealthBar -= _headsUpDisplay_OnNewBossHealthBar;
            if (_boss != null)
                _boss.OnBossDestroy -= _boss_OnBossDestroy;

            //Stop the music.
            MediaPlayer.Stop();
        }

        /// <summary>
        /// Method called when the player collects the cake.
        /// </summary>
        /// <param name="sender">The player object that collected the cake.</param>
        /// <param name="e">Event arguments (none).</param>
        void _playerOne_OnCakeCollect(object sender, EventArgs e)
        {
            //Go to the next level if there is a subscriber to the quit event.
            if (OnQuit != null)
                OnQuit(this, new LevelQuitEventArgs(LevelQuitEventArgs.QuitReason.CakeCollected));
        }

        #endregion

        #region Boss

        /// <summary>
        /// Method called when a boss health bar is created.
        /// </summary>
        /// <param name="sender">The heads up display that called the event.</param>
        /// <param name="e">Event arguments.</param>
        void _headsUpDisplay_OnNewBossHealthBar(object sender, NewBossHealthBarEventArgs e)
        {
            //Set the state.
            _currentState = State.BossIntroduction;

            //Hold the reference to the boss introduction instance and subscribe to the boss
            //introduction's finish event.
            _bossIntroduction = e.BossIntroduction;
            _bossIntroduction.OnBossIntroductionFinish += new BossIntroductionFinish(_bossIntroduction_OnBossIntroductionFinish);

            //Store the reference to the boss and subscribe to its event for later.
            _boss = e.Boss;
            _boss.OnBossDestroy += new BossDestroy(_boss_OnBossDestroy);
        }

        /// <summary>
        /// Method called when the boss introduction finishes.
        /// </summary>
        /// <param name="sender">The boss introduction that finished.</param>
        /// <param name="e">Event arguments.</param>
        void _bossIntroduction_OnBossIntroductionFinish(object sender, EventArgs e)
        {
            //Set the state.
            _currentState = State.Normal;

            //Unsubscribe to the boss introduction's finish event.
            _bossIntroduction.OnBossIntroductionFinish -= _bossIntroduction_OnBossIntroductionFinish;
        }

        /// <summary>
        /// Method called when the boss is destroyed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _boss_OnBossDestroy(object sender, BossDestroyEventArgs e)
        {
            //Set the state and timer.
            _currentState = State.BossDeath;
            _timer = e.WaitingTime;

            //Stop the music.
            MediaPlayer.Stop();

            //Unsubscribe from this event and lose the reference.
            _boss.OnBossDestroy -= _boss_OnBossDestroy;
            _boss = null;
        }

        #endregion

        #endregion
    }
}
