using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using System.Text;
using Microsoft.Xna.Framework;

namespace Sokoban3D.Scenes
{
    /// <summary>
    /// Scene manager is responsible of managing of the invidual scene objects in the game. 
    /// It has functions to load them and for adding them to the list of scenes it manages.
    /// </summary>
    public class SceneManager : DrawableGameComponent
    {
        // List of all scenes in order so we can go through all of them
        private List<Scene> scenes;

        // Main game object.
        private Sokoban3D game;

        // Scene variables for the currently active screen and its rollback option
        private Scene current, restart;
        public enum Status { Done, Ongoing }
        private KeyboardState oldKeyboardState;

        /// <summary>
        /// Initializes new scene manger. There is usually only one of them
        /// in game.
        /// </summary>
        /// <param name="game"></param>
        public SceneManager(Sokoban3D game)
            : base(game)
        {
            this.scenes = new List<Scene>();
            this.game = game;
            this.visible = false;
        }

        /// <summary>
        /// Get/set variable to inform if the scenes are visible and should be drawn 
        /// </summary>
        public Boolean visible
        {
            get { return Visible; }
            set { Visible = value; }
        }

        /// <summary>
        /// Getter to ask  the scene manager if all the levels have been cleared
        /// </summary>
        public Status status
        {
            get
            {
                if (scenes.Count > 0)
                    return Status.Ongoing;
                else
                {
                    return Status.Done;
                }
            }
        }


        /// <summary>
        /// function is called when the scene list is to be reset
        /// </summary>
        public void scenesClear()
        {
            this.scenes.Clear();
        }

        /// <summary>
        /// returns the currently active scenes name for saving purposes
        /// </summary>
        public String sceneName {
            get { return this.current.name; }
        }
        
        /// <summary>
        /// Translates the board so that file version and screen version look like they are in same position.
        /// </summary>
        /// <param name="board">Game board to be translated.</param>
        /// <returns>char[,], which is translated gameboard</returns>
        private char[,] translateBoard(char[,] board)
        {
            int width = board.GetLength(0);
            int height = board.GetLength(1);

            char[,] outboard = new char[height,width];

            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    outboard[j, width-(i+1)] = board[i, j];
                }
            }

            return outboard;
        }

        /// <summary>
        /// Loads the scene information from the savegame / scene start file
        /// All graphics content and models are loaded from the game.Content
        /// object.
        /// </summary>
        /// <returns></returns>
        public Scene loadScene()
        {
            // Return simple scene

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

            char[,] board = new char[,] { {'e','e','w','w','w','w','e','e','e'},
                                          {'w','w','w','f','f','w','w','w','w'},
                                          {'w','f','f','f','f','f','b','f','w'},
                                          {'w','f','w','f','f','w','b','f','w'},
                                          {'w','f','g','f','g','w','p','f','w'},
                                          {'w','w','w','w','w','w','w','w','w'} };
            return new Scene(this.game, this.translateBoard(board), "scene1");
        }

        /// <summary>
        /// same function as above but more commonly used. will load the levels from map files
        /// instead of using one default map.
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public Scene loadScene(String filename) 
        {
            String line;
            int lines = 0;
            int characters = 0;
            try 
            {
                using (StreamReader reader = new StreamReader(filename))
                {
                    if ((line = reader.ReadLine()) != null)
                        foreach (Char c in line)
                            characters += 1;
                    lines += 1;
                    while ((line = reader.ReadLine()) != null) {
                        lines += 1;
                    }
                }

                char[,] board = new char[lines, characters];
                
                using (StreamReader reader = new StreamReader(filename))
                {
                    lines = 0;
                    while ((line = reader.ReadLine()) != null)
                    {
                        characters = 0;
                        foreach (Char c in line) 
                        {
                            board[lines, characters] = c;
                            characters += 1;
                        }
                        lines += 1;
                    }
                }
                String[] path = filename.Split(new char[] {'\\'});
                return new Scene(this.game, board, path[path.Length-1].Remove(path[(path.Length-1)].Length-4));
            }
            catch (IOException) 
            {
                return null;
            }
        }

        /// <summary>
        /// Saves the current scene to savegame file
        /// </summary>
        public Boolean saveScene()
        {
            String savefile = this.current.name;

            char[,] board = this.current.currentBoard;
            int x = board.GetLength(0);
            int y = board.GetLength(1);
            try
            {
                using (StreamWriter writer = new StreamWriter("Save\\" + savefile + ".sav"))
                {
                    for (int i = 0; i < x; i++)
                    {
                        if (i != 0)
                            writer.Write(writer.NewLine);
                        for (int j = 0; j < y; j++)
                            writer.Write(board[i, j]);
                    }
                }
                return true;
            }
            catch (IOException)
            {
                return false;
            }
        }

        /// <summary>
        /// Adds new scene for scene manager to control.
        /// </summary>
        /// <param name="scene"></param>
        public void addScene(Scene scene)
        {
            this.scenes.Add(scene);
            this.current = scenes[0];
            char[,] board = new char[this.current.currentBoard.GetLength(0),this.current.currentBoard.GetLength(1)];
            Array.Copy(this.current.currentBoard, board,this.current.currentBoard.Length);
            this.restart = new Scene(this.game, board, this.current.name);
        }

        /// <summary>
        /// Update is called to handle input and other events that affect the scene
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            if (Visible)
            {
                if ((this.game.curScreen == null) || (this.game.curScreen.visible == false))
                {
                    this.current.Update(gameTime);
                    base.Update(gameTime);
                    if (this.current.status == Scene.Status.Done)
                    {
                        if (scenes.Count > 1)
                        {
                            scenes.Remove(this.current);
                            this.current = scenes[0];

                            char[,] board = new char[this.current.currentBoard.GetLength(0), this.current.currentBoard.GetLength(1)];
                            Array.Copy(this.current.currentBoard, board, this.current.currentBoard.Length);
                            this.restart = new Scene(this.game, board, this.current.name);

                            this.game.cScreen = Sokoban3D.Screens.Level;
                        }
                        else if (scenes.Count == 1)
                        {
                            scenes.RemoveAt(0);
                        }
                    }
                    KeyboardState keyboardState = Keyboard.GetState();

                    if (oldKeyboardState.IsKeyDown(Keys.Escape) && (keyboardState.IsKeyUp(Keys.Escape)))
                    {
                        this.game.cScreen = Sokoban3D.Screens.Menu;
                        this.visible = false;
                    }
                    oldKeyboardState = keyboardState;
                }
            }
        }

        /// <summary>
        /// Draw function is called when scene is visible and scenemanager should draw its content to the screen.
        /// Then it will call the draw functionality of the current screen.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            if (Visible)
            {
                this.current.Draw();
                base.Draw(gameTime);
            }
        }

        /// <summary>
        /// Function to load the scene from a savegame(.sav) file. This event will flush all the maps in the scenes list.
        /// </summary>
        /// <param name="filename"></param>
        public void sceneLoad(String filename)
        {
            this.current = loadScene(filename);
            char[,] board = new char[this.current.currentBoard.GetLength(0), this.current.currentBoard.GetLength(1)];
            Array.Copy(this.current.currentBoard, board, this.current.currentBoard.Length);
            this.restart = new Scene(this.game, board, this.current.name);
            this.scenes.Add(this.current);
        }

        /// <summary>
        /// Function is called, when player desires to restart the current level due to getting stuck or just because they want to.
        /// </summary>
        public void sceneRestart()
        {
            char[,] board = new char[this.restart.currentBoard.GetLength(0), this.restart.currentBoard.GetLength(1)];
            Array.Copy(this.restart.currentBoard, board, this.restart.currentBoard.Length);
            this.current = new Scene(this.game, board, this.current.name);
            this.scenes.Insert(0, this.current);
            this.scenes.RemoveAt(1);
            this.game.cScreen = Sokoban3D.Screens.Level;
        }
    }
}
