using System;
using System.Collections.Generic;
using System.Linq;
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.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Sokoban3D.Scenes;
using Sokoban3D.Screens;
using System.IO;

namespace Sokoban3D
{
    /// <summary>
    /// Main class for the game. Responsible of initializing 
    /// all the other classes and other game assets.
    /// </summary>
    public class Sokoban3D : Microsoft.Xna.Framework.Game
    {
        // variables for graphical information
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        // Variables to hold the scene and content objects
        protected SceneManager sceneManager;
        public GameContent gameContent;
        public enum Screens {New, Start, Options, Help, Save, End, Load, Level, Menu, Game }

        // Variables to hold different kind of popup screens.
        private StartScreen startScreen;
        private OptionsScreen optionsScreen;
        private HelpScreen helpScreen;
        private EndScreen endScreen;
        private LevelStartScreen levelStart;
        private LoadScreen loadScreen;
        private MenuScreen menuScreen;
        private Screen currentScreen;

        // Variables to hold sound values
        public Boolean sound;
        private Song bgm;

        // getter to access the currentScreen and its variables
        public Screen curScreen
        {
            get { return this.currentScreen; }
        }

        // public setter to handle switching of the current screen inside screens or scenemanager
        public Screens cScreen
        {
            set
            {
                if (value == Screens.Start)
                {
                    this.currentScreen = startScreen;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.New)
                {
                    this.sceneManager.visible = false;
                    this.initializeScenes(0);

                    this.levelStart = new LevelStartScreen(this, this.sceneManager, this.gameContent.skybackground); 
                    this.currentScreen = levelStart;

                    this.currentScreen.visible = true;
                }
                else if (value == Screens.Options)
                {
                    this.currentScreen = optionsScreen;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.Save)
                {
                    saveGame();
                }
                else if (value == Screens.Load)
                {
                    this.currentScreen = loadScreen;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.Help)
                {
                    this.currentScreen = helpScreen;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.Level)
                {
                    this.sceneManager.visible = false;
                    this.levelStart = new LevelStartScreen(this, this.sceneManager, this.gameContent.skybackground);
                    this.currentScreen = this.levelStart;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.End)
                {
                    this.currentScreen = endScreen;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.Menu)
                {
                    this.currentScreen = menuScreen;
                    this.currentScreen.visible = true;
                }
                else if (value == Screens.Game)
                {
                    this.currentScreen = null;
                    this.sceneManager.visible = true;
                }
                else throw new InvalidOperationException();
            }
        }

        public Sokoban3D()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            gameContent = new GameContent(this);
            sceneManager = new SceneManager(this);

            this.sound = true;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            this.Components.Add(sceneManager);
            this.Components.Add(currentScreen);
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {

            // Create a new SpriteBatch, which can be used to draw textures.
            if (Services.GetService(typeof(SpriteBatch)) == null)
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                Services.AddService(typeof(SpriteBatch), spriteBatch);
            }
            gameContent.loadContent();

            // Handle sound
            this.bgm = gameContent.bgm;
            MediaPlayer.IsRepeating = true;
            MediaPlayer.Volume = 0.1f;

            // Create default scenes list for levels
            this.initializeScenes(0);

            endScreen = new EndScreen(this, this.sceneManager, gameContent.skybackground);
            helpScreen = new HelpScreen(this, this.sceneManager, gameContent.helpbg);
            levelStart = new LevelStartScreen(this, this.sceneManager, gameContent.skybackground);
            loadScreen = new LoadScreen(this, this.sceneManager, gameContent.skybackground);
            menuScreen = new MenuScreen(this, this.sceneManager, gameContent.skybackground);
            optionsScreen = new OptionsScreen(this, this.sceneManager, gameContent.skybackground);
            startScreen = new StartScreen(this, this.sceneManager, gameContent.skybackground);

            this.currentScreen = startScreen;
            this.sceneManager.visible = false;
            this.currentScreen.visible = true;
        }

        /// <summary>
        /// initializeScenes will be called each time the scenemanagers game list should be reset
        /// to default maps. its called during loadcontent and during the call of a new game.
        /// </summary>
        private void initializeScenes(int index){
            if (index == 0)
                this.sceneManager.scenesClear();

            for (int i = index; i < 5; i++)
            {
                Scene scene = sceneManager.loadScene(@"Content\Maps\level" + (1+i) + ".map");
                sceneManager.addScene(scene);
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // There isn't any content in game that needs to be explicitly unloaded.
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // handles the switching of scenes if the current scene has been completed.
            if (this.sceneManager.visible == true)
            {
                if (this.sceneManager.status == SceneManager.Status.Done)
                {
                    this.currentScreen = endScreen;
                    this.currentScreen.visible = true;

                    for (int i = 1; i < 5; i++)
                    {
                        Scene scene = sceneManager.loadScene(@"Content\Maps\level" + i + ".map");
                        sceneManager.addScene(scene);
                    }

                    this.sceneManager.visible = false;
                }
            }

            // restrict screen keylogging if it doesnt exist or its not visible
            if (this.currentScreen != null && this.currentScreen.visible)
                this.currentScreen.update(gameTime);

            // plays the background music
            if (sound)
            {
                if (MediaPlayer.State == MediaState.Stopped)
                {
                    MediaPlayer.Play(bgm);
                }
            }
            else
            {
                if (MediaPlayer.State == MediaState.Playing)
                    MediaPlayer.Stop();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // draw the screen if it exists and is visible
            if (this.currentScreen != null && this.currentScreen.visible)
            {
                spriteBatch.Begin();
                this.currentScreen.draw(gameTime);
                spriteBatch.End();
            }
            base.Draw(gameTime);
        }

        /// <summary>
        /// saveGame function is called each time save game is initiated inside the savescreen
        /// </summary>
        public void saveGame()
        {
            this.sceneManager.saveScene();

            // recreate the file listing inside load screen by creating a new instance
            this.loadScreen = new LoadScreen(this, this.sceneManager, this.gameContent.skybackground);
        }

        /// <summary>
        /// loadGame function is called each time a game is loaded.
        /// </summary>
        /// <param name="filename">the desired loadstates filename</param>
        public void loadGame(String filename)
        {
            this.sceneManager.visible = false;
            this.sceneManager.scenesClear();
            int index = (int)Char.GetNumericValue(filename[filename.Length - 5]);
            if (Directory.Exists("Save"))
            {
                this.sceneManager.sceneLoad(@"Save\" + filename);
                this.initializeScenes(index);
                this.cScreen = Screens.Level;
            }
            else
            {
                Directory.CreateDirectory("Save");
                this.sceneManager.sceneLoad(@"Save\" + filename);
                this.initializeScenes(index);
                this.cScreen = Screens.Level;
            }
        }
        /// <summary>
        /// updateOptions function is called each time something happens to the settings in options
        /// and they need to be updated to the screen.
        /// </summary>
        public void updateOptions()
        {
            // store the active menu location (only for multiple selections really, but its here if we add more options later)
            int index = this.currentScreen.menu.SelectedIndex;

            this.currentScreen = null;

            this.optionsScreen = new OptionsScreen(this, this.sceneManager, this.gameContent.skybackground);

            this.currentScreen = this.optionsScreen;
            this.currentScreen.menu.SelectedIndex = index;
            this.currentScreen.visible = true;
        }
    }
}
