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


namespace Sokoban3D.Scenes
{
    /// <summary>
    /// Base class for the game levels. They will be created and managed by
    /// the SceneManager-class.
    /// </summary>
    public class Scene
    {
        public enum Status { Ongoing, Done } // could also contain deadlock but its not implemented
        private enum Direction { Left, Right, Up, Down }
        private Sokoban3D game;
        /// <summary>
        /// Object manager stores and manages all the 3D objects currently in the game.
        /// </summary>
        private ObjectManager objectManager;
        /// <summary>
        /// Camera stores the current camera used for drawing the screens.
        /// </summary>
        private Camera camera;
        private char[,] board;
        private string sceneName;
        private int keytimer = 0;
        

        public string name
        {
            get { return sceneName; }
            private set { sceneName = value; }
        }

        public char[,] currentBoard
        {
            get { return board; }
        }

        public Status status
        {
            get 
            {
                if (this.objectManager.checkStatus())
                    return Status.Done;
                else 
                {
                    return Status.Ongoing;
                }
            }
        }

        /// <summary>
        /// Initializes the new gameboard. It will take the game object, 
        /// playing board as char array and name of the scene as parameters.
        /// </summary>
        /// <param name="game">Game object.</param>
        /// <param name="board">Gameboard as 2-dimensional char array.</param>
        /// <param name="name">Name of the current scene.</param>
        public Scene(Sokoban3D game, char[,] board, string name)
        {
            this.game = game;
            this.name = name;
            this.board = board;
            camera = new Camera(game, new Vector3(0, -30, 30), new Vector3(0,0,0), Vector3.Up);
            this.objectManager = new ObjectManager(game);
            this.objectManager.initializeBoard(board);
        }

        /// <summary>
        /// Moves the player and if necessary bolders in direction given by dir enumerator.
        /// If move is not possible this method doesn't move the objects.
        /// </summary>
        /// <param name="dir">Enumerator telling the direction to move.</param>
        private void movePlayer(Direction dir)
        {
            // Players x and y position.
            int playerx = this.objectManager.getPlayer().xPosition;
            int playery = this.objectManager.getPlayer().yPosition;
            // Next squares position. Initialize as players position for now.
            int nextx = playerx;
            int nexty = playery;
            // After next squares position. Initialize as players position for now.
            int afternextx = playerx;
            int afternexty = playery;
            // Direction for which to move the 3D objects.
            Vector3 movedirection = Vector3.Zero;
            // Direction where objectmanager will move the Ball(or boulder if you are inclined that way.)
            ObjectManager.Direction balldirection = ObjectManager.Direction.Left;

            // Initialize the variables according to the value of dir enumerator.
            switch (dir)
            {
                case Direction.Left:
                    nextx -= 1;
                    afternextx -= 2;
                    movedirection = Vector3.Left;
                    balldirection = ObjectManager.Direction.Left;
                    break;
                case Direction.Right:
                    nextx += 1;
                    afternextx += 2;
                    movedirection = Vector3.Right;
                    balldirection = ObjectManager.Direction.Right;
                    break;
                case Direction.Up:
                    nexty += 1;
                    afternexty += 2;
                    movedirection = Vector3.Up;
                    balldirection = ObjectManager.Direction.Up;
                    break;
                case Direction.Down:
                    nexty -= 1;
                    afternexty -= 2;
                    movedirection = Vector3.Down;
                    balldirection = ObjectManager.Direction.Down;
                    break;
            }

            // e = no object
            // w = wall
            // p = player
            // f = empty floor
            // g = goal square
            // d = done / ball on goal square
            // o = player over the goal square

            // Calculate the new positions of the objects if there is either ball (b or d)
            // or empty floor (f or g) in the next square. Clean also current square in 
            // correct state after player moves.
            if (this.board[nextx, nexty] == 'b' || this.board[nextx, nexty] == 'd')
            {
                if (this.board[afternextx, afternexty] == 'f' || this.board[afternextx, afternexty] == 'g')
                {
                    // if we're moving boulder away from a done goal square, unmark it
                    if (this.board[nextx, nexty] == 'd')
                        this.objectManager.alterStatus(nextx, nexty);

                    this.board[afternextx, afternexty] = this.board[afternextx, afternexty] == 'f' ? 'b' : 'd';

                    // if we moved a boulder to a goal square, mark it as done
                    if (this.board[afternextx, afternexty] == 'd')
                        this.objectManager.alterStatus(afternextx, afternexty);

                    this.board[nextx, nexty] = this.board[nextx, nexty] == 'b' ? 'p' : 'o';
                    this.board[playerx, playery] = this.board[playerx, playery] == 'p' ? 'f' : 'g';
                    this.objectManager.moveBall(balldirection, nextx, nexty);
                    this.objectManager.getPlayer().move(movedirection);
                    this.objectManager.getPlayer().xPosition = nextx;
                    this.objectManager.getPlayer().yPosition = nexty;
                }
            }
            else if (this.board[nextx, nexty] == 'f' || this.board[nextx, nexty] == 'g')
            {
                this.board[nextx, nexty] = this.board[nextx, nexty] == 'f' ? 'p' : 'o';
                this.board[playerx, playery] = this.board[playerx, playery] == 'p' ? 'f' : 'g';
                this.objectManager.getPlayer().move(movedirection);
                this.objectManager.getPlayer().xPosition = nextx;
                this.objectManager.getPlayer().yPosition = nexty;
            }
        }

        /// <summary>
        /// Main update function for the Scene object
        /// </summary>
        /// <param name="time">Current gametime.</param>
        public void Update(GameTime time)
        {
            // Make sure that we don't read the keypresses too often. 
            keytimer -= time.ElapsedGameTime.Milliseconds;
            KeyboardState state = Keyboard.GetState();
            // Player movement shouldn't take keypresses too often.
            if (keytimer < 0)
            {                
                if (state.GetPressedKeys().Length > 0)
                    keytimer = 200;
                if (state.IsKeyDown(Keys.Left))
                    this.movePlayer(Direction.Left);
                if (state.IsKeyDown(Keys.Right))
                    this.movePlayer(Direction.Right); 
                if (state.IsKeyDown(Keys.Up))
                    this.movePlayer(Direction.Up);
                if (state.IsKeyDown(Keys.Down))
                    this.movePlayer(Direction.Down);
            }
            // Camera will rotate freely on keypress
            if (state.IsKeyDown(Keys.A))
                this.camera.rotate(0.02f);
            if (state.IsKeyDown(Keys.D))
                this.camera.rotate(-0.02f);
            if (state.IsKeyDown(Keys.S))
                this.camera.reset();
            if (state.IsKeyDown(Keys.W))
                this.camera.zoomIn();
            if (state.IsKeyDown(Keys.X))
                this.camera.zoomOut();

            this.objectManager.Update(time);
        }

        /// <summary>
        /// Calls the ObjectManager to draw the current scenes objects with
        /// camera associated to the scene.
        /// </summary>
        public void Draw()
        {
            objectManager.drawObjects(camera);
        }
    }
}
