using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Sokoban3D.Scenes;

namespace Sokoban3D.Objects
{
    /// <summary>
    /// Class acts as a manager for all of the 3D objects associated in a certain
    /// scene. It will also create the necessary 3D objects to be drawn. For every object
    /// seen in the screen there also exists one in the memory.
    /// </summary>
    public class ObjectManager
    {
        private Sokoban3D game;
        private List<UnmovableObject> unmovableObjects;
        private List<AutonomousObject> autonomousObjects;
        private List<PlayerMovableObject> playerMovableObjects;
        private List<PlayerObject> playerObjects;
        private List<GoalObject> goalObjects;
        private PlayerMovableObject[,] board;
        private BackGroundObject skyband;

        public enum Direction { Left, Right, Up, Down } 

        /// <summary>
        /// Initialize new ObjectManager and create various lists to hold the
        /// Game components.
        /// </summary>
        /// <param name="game">Sokoban3D object</param>
        public ObjectManager(Sokoban3D game)
        {
            unmovableObjects = new List<UnmovableObject>();
            autonomousObjects = new List<AutonomousObject>();
            playerMovableObjects = new List<PlayerMovableObject>();
            playerObjects = new List<PlayerObject>();
            goalObjects = new List<GoalObject>();
            this.game = game;
            
        }

        /// <summary>
        /// Return the player object. Desing supports multiple players,
        /// but only one is currently in use.
        /// </summary>
        /// <returns>Current player</returns>
        public PlayerObject getPlayer(){
            return playerObjects[0];
        }

        /// <summary>
        /// This function reads in the gameboard and creates all the necessary objects to be used with the game.
        /// </summary>
        /// <param name="board">Gameboard 2-dimendional character array.</param>
        public void initializeBoard(char[,] board)
        {
            UnmovableObject uobject;
            PlayerObject pobject;
            PlayerMovableObject pmobject;
            GoalObject gobject;
            int width = board.GetLength(0);
            int height = board.GetLength(1);

            // Create new skysphere object to hold the background.
            this.skyband = new BackGroundObject(game.gameContent.skyband, new Vector3(0, 0, 0), Matrix.Identity);

            // Create board to help moving the player movable objects.
            // This will only store the player movable objects for easy
            // Access when their positions need to be updated.
            this.board = new PlayerMovableObject[width, height];

            // Initialize the 3D objects in the memory.
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    // Calculate the correct position on the screen.
                    float x = (2.0f * (i + 1)) - (2.0f * (float)width) / 2.0f - 1.0f;
                    float y = (2.0f * (j + 1)) - (2.0f * (float)height) / 2.0f - 1.0f;

                    // Initialize entry of the player movable board to null (should be null allready
                    // but it doesn't cost much for safety.)
                    this.board[i, j] = null;

                    switch (board[i, j])
                    {
                        case 'f':
                            if (game.gameContent.floor == null)
                            {
                                throw new NullReferenceException();
                            }
                            uobject = new UnmovableObject(game.gameContent.floor, 
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)));
                            unmovableObjects.Add(uobject);
                            break;
                        case 'w':
                            uobject = new UnmovableObject(game.gameContent.wall,
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)));
                            unmovableObjects.Add(uobject);
                            break;
                        case 'g':
                            gobject = new GoalObject(game.gameContent.goal,
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)),i,j);
                            gobject.active = false;
                            goalObjects.Add(gobject);
                            break;
                        case 'b':
                            pmobject = new PlayerMovableObject(game.gameContent.ball, new Vector3(x, y, 1.0f), Matrix.Identity, 2.0f);
                            playerMovableObjects.Add(pmobject);

                            uobject = new UnmovableObject(game.gameContent.floor,
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)));

                            unmovableObjects.Add(uobject);
                            this.board[i, j] = pmobject;
                            break;
                        case 'p':
                            pobject = new PlayerObject(game.gameContent.player, new Vector3(x, y, 2.0f), Matrix.CreateRotationX(MathHelper.ToRadians(90)), 2.0f, i, j);
                            playerObjects.Add(pobject);
                            uobject = new UnmovableObject(game.gameContent.floor,
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)));

                            unmovableObjects.Add(uobject);
                            break;
                        case 'd':
                            gobject = new GoalObject(game.gameContent.goal,
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)), i, j);
                            gobject.active = true;
                            goalObjects.Add(gobject);

                            pmobject = new PlayerMovableObject(game.gameContent.ball, new Vector3(x, y, 1.0f), Matrix.Identity, 2.0f);
                            playerMovableObjects.Add(pmobject);

                            uobject = new UnmovableObject(game.gameContent.floor,
                                                          new Vector3(x, y, 0),
                                                          Matrix.CreateRotationX(MathHelper.ToRadians(90)));

                            unmovableObjects.Add(uobject);
                            this.board[i, j] = pmobject;
                            break;
                        case 'o': 
                            gobject = new GoalObject(game.gameContent.goal,
                                                           new Vector3(x, y, 0),
                                                           Matrix.CreateRotationX(MathHelper.ToRadians(90)), i, j);
                            gobject.active = false;
                            goalObjects.Add(gobject);
                            pobject = new PlayerObject(game.gameContent.player, new Vector3(x, y, 2.0f), Matrix.CreateRotationX(MathHelper.ToRadians(90)), 2.0f, i, j);
                            playerObjects.Add(pobject);
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Moves the ball (or boulder or what ever the player movable object is.)
        /// to the right direction given by dir. from the position given by i and j.
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="i"></param>
        /// <param name="j"></param>
        public void moveBall(Direction dir, int i, int j)
        {
            switch (dir)
            {
                case Direction.Left:
                    this.board[i - 1, j] = this.board[i, j];
                    this.board[i, j].move(Vector3.Left);
                    this.board[i, j] = null;
                    break;
                case Direction.Right:
                    this.board[i + 1, j] = this.board[i, j];
                    this.board[i, j].move(Vector3.Right);
                    this.board[i, j] = null;
                    break;
                case Direction.Up:
                    this.board[i, j + 1] = this.board[i, j];
                    this.board[i, j].move(Vector3.Up);
                    this.board[i, j] = null;
                    break;
                case Direction.Down:
                    this.board[i, j - 1] = this.board[i, j];
                    this.board[i, j].move(Vector3.Down);
                    this.board[i, j] = null;
                    break;
            }
        }

        /// <summary>
        /// Check if player has completed the game.
        /// </summary>
        /// <returns></returns>
        public Boolean checkStatus() 
        {
            foreach (GoalObject status in goalObjects)
                if (!status.active)
                    return false;
            return true;
        }

        /// <summary>
        /// Change the status of the playermovable object in 
        /// place x,y to opposite.
        /// </summary>
        /// <param name="x">x-index</param>
        /// <param name="y">y-index</param>
        public void alterStatus(int x, int y)
        {
            foreach (GoalObject gobject in goalObjects)
                if (gobject.xPosition == x && gobject.yPosition == y)
                    gobject.active = !gobject.active;
        }

        /// <summary>
        /// This method updates the autonomous objects in the scene.
        /// </summary>
        /// <param name="gametime">Current gametime.</param>
        public void Update(GameTime gametime)
        {
            this.skyband.Update(gametime);
        }

        /// <summary>
        /// This method goes trough all the objects in gameboard and draws them out.
        /// </summary>
        /// <param name="camera"></param>
        public void drawObjects(Camera camera)
        {
            // Invert the culling mode for the skysphere so that it will be shown when we are inside of it.
            game.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            this.skyband.draw(camera);
            game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

            game.GraphicsDevice.RenderState.DepthBufferEnable = true;

            foreach (UnmovableObject element in unmovableObjects)
                element.draw(camera);

            foreach (AutonomousObject element in autonomousObjects)
                element.draw(camera);

            foreach (PlayerMovableObject element in playerMovableObjects)
                element.draw(camera);

            foreach (PlayerObject element in playerObjects)
                element.draw(camera);
            foreach (GoalObject element in goalObjects)
                element.draw(camera);

        }
    }
}
