using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
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 Microsoft.Xna.Framework.Input;
using MegaManRipoff.UI;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// Determines where the succeeding room should be placed.
    /// </summary>
    public enum NextRoomLocation
    {
        /// <summary>
        /// There is no room succeeding this one.
        /// </summary>
        None,
        /// <summary>
        /// The next room is below this room.
        /// </summary>
        Down,
        /// <summary>
        /// The next room is to the right of this room; the player
        /// will enter the top of the room.
        /// </summary>
        TopRight,
        /// <summary>
        /// The next room is to the right of this room; the player
        /// will enter the bottom of the room.
        /// </summary>
        BottomRight
    }

    /// <summary>
    /// Rooms make up individual parts of rooms. A room stores
    /// the entities, tiles and effects it contains, and how to
    /// transition to the next room (if any).
    /// </summary>
    class Room
    {
        #region Member Variables

        /// <summary>
        /// A reference to the player currently in the room.
        /// </summary>
        Player _playerOne;

        /// <summary>
        /// The position that the player will start at after death, if this room is a checkpoint.
        /// </summary>
        Vector2 _checkpoint;

        /// <summary>
        /// A vector that is used to represent that a room does not contain a checkpoint.
        /// </summary>
        public static readonly Vector2 NO_CHECKPOINT = new Vector2(-1000, -1000);

        /// <summary>
        /// A reference to the camera currently in the room.
        /// </summary>
        GameCamera _gameCamera;

        /// <summary>
        /// A reference to the level's heads up display. This is used so that the boss introduction
        /// class can create the boss' health bar.
        /// </summary>
        HeadsUpDisplay _headsUpDisplay;

        /// <summary>
        /// The absolute area that the room occupies.
        /// </summary>
        Rectangle _area;

        /// <summary>
        /// The collection of required arguments of the entities that will be created in the room
        /// upon its initialisation. 
        /// </summary>
        List<Tuple<Type, Vector2, object[]>> _originalEntities;

        /// <summary>
        /// List of entities currently in the room.
        /// </summary>
        List<Entity> _entities;

        /// <summary>
        /// List of bullets currently in the room.
        /// </summary>
        List<Bullet> _bullets;

        /// <summary>
        /// List of special effects currently in the room.
        /// </summary>
        List<SpecialEffect> _specialEffects;

        /// <summary>
        /// List of tiles currently in the room.
        /// </summary>
        List<Tile> _tiles;

        /// <summary>
        /// Determines what to do with each object in the catch up list.
        /// </summary>
        private enum CatchUpAction
        {
            /// <summary>
            /// The object is to be added to the room.
            /// </summary>
            Add,
            /// <summary>
            /// The object is to be removed from the room.
            /// </summary>
            Remove
        }

        /// <summary>
        /// A list of objects to add/remove references to later. This list exists to
        /// avoid modifying the collections during a foreach loop.
        /// </summary>
        List<Tuple<object, CatchUpAction>> _catchUpList;

        /// <summary>
        /// Determines where the succeeding room should be placed.
        /// </summary>
        NextRoomLocation _nextRoomLocation;

        /// <summary>
        /// The background being used in the room.
        /// </summary>
        Background _background;
        
        #endregion

        #region Properties

        /// <summary>
        /// Gets the player currently in the room.
        /// </summary>
        public Player PlayerOne
        {
            get { return _playerOne; }
        }

        /// <summary>
        /// Gets the position that the player will start at after death, if this room is a
        /// checkpoint.
        /// </summary>
        public Vector2 Checkpoint
        {
            get { return _checkpoint; }
        }

        /// <summary>
        /// Gets the room's camera.
        /// </summary>
        public GameCamera Camera
        {
            get { return _gameCamera; }
        }

        /// <summary>
        /// Gets the level's heads up display. This is used so that the boss introduction
        /// class can create the boss' health bar.
        /// </summary>
        public HeadsUpDisplay HeadsUpDisplay
        {
            get { return _headsUpDisplay; }
        }

        /// <summary>
        /// Gets the area the room occupies.
        /// </summary>
        public Rectangle Area
        {
            get { return _area; }
        }

        /// <summary>
        /// Gets the list of entities in the room.
        /// </summary>
        public List<Entity> Entities
        {
            get { return _entities; }
        }

        /// <summary>
        /// Gets the list of bullets in the room.
        /// </summary>
        public List<Bullet> Bullets
        {
            get { return _bullets; }
        }

        /// <summary>
        /// Gets the list of special effects in the room.
        /// </summary>
        public List<SpecialEffect> SpecialEffects
        {
            get { return _specialEffects; }
        }

        /// <summary>
        /// Gets the list of tiles in the room.
        /// </summary>
        public List<Tile> Tiles
        {
            get { return _tiles; }
        }

        /// <summary>
        /// Gets where the succeeding room is placed, if any.
        /// </summary>
        public NextRoomLocation NextRoomLocation
        {
            get { return _nextRoomLocation; }
        }

        #endregion

        /// <summary>
        /// Creates a new room.
        /// </summary>
        /// <param name="area">The absolute area that the room occupies.</param>
        /// <param name="background">The background instance to use in the room.</param>
        /// <param name="tiles">The list of tiles in the room.</param>
        /// <param name="originalEntities">The collection of arguments required to instantiate this
        /// room's entities.</param>
        /// <param name="nextRoomLocation">The location of the next room relative to this
        /// one, if any.</param>
        /// <param name="checkpoint">The position that the player will start at after death. If this room
        /// is not a checkpoint, use Vector2.Zero.</param>
        public Room(Rectangle area, Background background, List<Tile> tiles,
            List<Tuple<Type, Vector2, object[]>> originalEntities, NextRoomLocation nextRoomLocation,
            Vector2 checkpoint)
        {
            _area = area;
            _background = background;
            _background.AssignRoom(this);
            _tiles = tiles;
            _originalEntities = originalEntities;
            _nextRoomLocation = nextRoomLocation;
            _checkpoint = checkpoint;

            //Instantiate the lists.
            _entities = new List<Entity>();
            _bullets = new List<Bullet>();
            _specialEffects = new List<SpecialEffect>();
            _catchUpList = new List<Tuple<object, CatchUpAction>>();
        }

        #region Methods

        #region Catch Up Methods

        /// <summary>
        /// Adds the given entity to the room.
        /// </summary>
        /// <param name="entity">The entity to add.</param>
        public void Add(Entity entity)
        {
            Add((object)entity);
        }

        /// <summary>
        /// Adds the given bullet to the room.
        /// </summary>
        /// <param name="bullet">The bullet to add.</param>
        public void Add(Bullet bullet)
        {
            Add((object)bullet);
        }

        /// <summary>
        /// Adds the given special effect from the room.
        /// </summary>
        /// <param name="specialEffect">The special effect to add.</param>
        public void Add(SpecialEffect specialEffect)
        {
            Add((object)specialEffect);
        }

        /// <summary>
        /// Adds the given object to the catch up list to be added. This is private
        /// because the other public overloads are safer (because they deal with the
        /// types that are actually to be contained in the list).
        /// </summary>
        /// <param name="obj">The object to add.</param>
        private void Add(object obj)
        {
            _catchUpList.Add(new Tuple<object, CatchUpAction>(obj, CatchUpAction.Add));
        }

        /// <summary>
        /// Removes the given entity from the room.
        /// </summary>
        /// <param name="entity">The entity to remove.</param>
        public void Remove(Entity entity)
        {
            Remove((object)entity);
        }

        /// <summary>
        /// Removes the given bullet from the room.
        /// </summary>
        /// <param name="bullet">The bullet to remove.</param>
        public void Remove(Bullet bullet)
        {
            Remove((object)bullet);
        }

        /// <summary>
        /// Removes the given special effect from the room.
        /// </summary>
        /// <param name="specialEffect">The special effect to remove.</param>
        public void Remove(SpecialEffect specialEffect)
        {
            Remove((object)specialEffect);
        }

        /// <summary>
        /// Adds the given object to the catch up list to be removed. This is private
        /// because the other public overloads are safer (because they deal with the
        /// types that are actually contained in the list).
        /// </summary>
        /// <param name="obj">The object to remove.</param>
        private void Remove(object obj)
        {
            _catchUpList.Add(new Tuple<object, CatchUpAction>(obj, CatchUpAction.Remove));
        }

        /// <summary>
        /// Catches up to the program by adding/removing all objects that have been
        /// previously designated to do so. This is done after the main bulk of the
        /// update loop to avoid altering a collection during its iteration.
        /// </summary>
        private void CatchUp()
        {
            //Only bother doing something if the list is not empty.
            if (_catchUpList.Count != 0)
            {
                //Iterate through the list and add/remove the entities to/from their
                //corresponding lists.
                foreach (Tuple<object, CatchUpAction> tuple in _catchUpList)
                {
                    //Entity handling.
                    if (tuple.Item1 is Entity)
                    {
                        if (tuple.Item2 == CatchUpAction.Add)
                            _entities.Add((Entity)tuple.Item1);
                        else
                            _entities.Remove((Entity)tuple.Item1);
                    }
                    else
                    {
                        //Bullet handling.
                        if (tuple.Item1 is Bullet)
                        {
                            if (tuple.Item2 == CatchUpAction.Add)
                                _bullets.Add((Bullet)tuple.Item1);
                            else
                                _bullets.Remove((Bullet)tuple.Item1);
                        }
                        else
                        {
                            //Special effects handling.
                            if (tuple.Item1 is SpecialEffect)
                            {
                                if (tuple.Item2 == CatchUpAction.Add)
                                    _specialEffects.Add((SpecialEffect)tuple.Item1);
                                else
                                    _specialEffects.Remove((SpecialEffect)tuple.Item1);
                            }
                            else
                                //If the object isn't in any of those lists, throw an exception.
                                throw new InvalidOperationException("Unknown object \"" + tuple.Item1 + "\" in catch up list.");
                        }
                    }
                }

                //Now empty the catch up list.
                _catchUpList.Clear();
            }
        }

        #endregion

        /// <summary>
        /// Populates the room with entities creating according to the definitions in the original
        /// entity list.
        /// </summary>
        private void Populate()
        {
            _entities.Clear();

            //Iterate through the original entity list.
            foreach (Tuple<Type, Vector2, object[]> tuple in _originalEntities)
            {
                if (tuple.Item1 == typeof(Metool))
                {
                    _entities.Add(new Metool(this, tuple.Item2, (bool)tuple.Item3[0]));
                }
                else
                    if (tuple.Item1 == typeof(PipiGenerator))
                    {
                        Rectangle rectangle = (Rectangle)tuple.Item3[0];
                        _entities.Add(new PipiGenerator(this, rectangle));
                    }
                    else
                        if (tuple.Item1 == typeof(SmallHealth))
                        {
                            _entities.Add(new SmallHealth(this, tuple.Item2, false, Vector2.Zero));
                        }
                        else
                            if (tuple.Item1 == typeof(LargeHealth))
                            {
                                _entities.Add(new LargeHealth(this, tuple.Item2, false, Vector2.Zero));
                            }
                            else
                                if (tuple.Item1 == typeof(ExtraLife))
                                {
                                    _entities.Add(new ExtraLife(this, tuple.Item2, false, Vector2.Zero));
                                }
                                else
                                    if (tuple.Item1 == typeof(BigEvilKillingThing))
                                    {
                                        _entities.Add(new BigEvilKillingThing(this, tuple.Item2));
                                    }
                                    else
                                        if (tuple.Item1 == typeof(YellowDevil))
                                        {
                                            _entities.Add(new YellowDevil(this, tuple.Item2));
                                        }
                                        else
                                            if (tuple.Item1 == typeof(PitLurker))
                                            {
                                                _entities.Add(new PitLurker(this, tuple.Item2));
                                            }
                                            else
                                                if (tuple.Item1 == typeof(Tripropellan))
                                                {
                                                    _entities.Add(new Tripropellan(this, tuple.Item2));
                                                }
                                                else
                                                    if (tuple.Item1 == typeof(SniperJoe))
                                                    {
                                                        _entities.Add(new SniperJoe(this, tuple.Item2));
                                                    }
                                                    else
                                                        if (tuple.Item1 == typeof(QuickManLaserGenerator))
                                                        {
                                                            int delay = (int)tuple.Item3[0];
                                                            Entity.Direction direction = (Entity.Direction)tuple.Item3[1];
                                                            _entities.Add(new QuickManLaserGenerator(this, tuple.Item2, delay, direction));
                                                        }
                                                        else
                                                            if (tuple.Item1 == typeof(DarkArea))
                                                            {
                                                                Rectangle rectangle = (Rectangle)tuple.Item3[0];
                                                                _entities.Add(new DarkArea(this, rectangle));
                                                            }
                                                            else
                                                                if (tuple.Item1 == typeof(Cake))
                                                                {
                                                                    _entities.Add(new Cake(this, tuple.Item2));
                                                                }
                                                                else
                                                                    throw new Exception("Unknown entity type \"" + tuple.Item1
                                                                            + "\" in " + this + " original entity list.");
                Debug.WriteLine("found " + tuple.Item1 + tuple.Item2);
            }
        }

        /// <summary>
        /// Initialises everything needed to play the room.
        /// </summary>
        /// <param name="playerOne">The instance of the player.</param>
        /// <param name="gameCamera">The instance of the game camera.</param>
        public void Initialise(Player playerOne, GameCamera gameCamera, HeadsUpDisplay headsUpDisplay)
        {
            //Set the references to the player, camera and heads up display.
            _playerOne = playerOne;
            _gameCamera = gameCamera;
            _headsUpDisplay = headsUpDisplay;

            //Populate the entity list.
            Populate();

            //Clear the other lists.
            _bullets.Clear();
            _specialEffects.Clear();
        }

        /// <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)
        {
            //Update the background.
            _background.Update(gameTime);

            //Update the entities.
            foreach (Entity entity in _entities)
                entity.Update(gameTime);

            //Update the bullets.
            foreach (Bullet bullet in _bullets)
                bullet.Update(gameTime);

            //Update the special effects.
            foreach (SpecialEffect specialEffect in _specialEffects)
                specialEffect.Update(gameTime);

            //Catch up by adding/removing references to all objects that are supposed to
            //have been added/removed.
            CatchUp();
        }

        /// <summary>
        /// Add the room and its contents to the drawing sprite batch.
        /// </summary>
        /// <param name="spriteBatch">The sprite batch to draw to.</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            //Draw the tiles.
            foreach (Tile tile in _tiles)
                tile.Draw(spriteBatch);

            //Draw the bullets.
            foreach (Bullet bullet in _bullets)
                bullet.Draw(spriteBatch);

            //Draw the entities.
            foreach (Entity entity in _entities)
                entity.Draw(spriteBatch);

            //Draw the special effects.
            foreach (SpecialEffect specialEffect in _specialEffects)
                specialEffect.Draw(spriteBatch);
        }

        #endregion
    }
}
