using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using StrategicCombat.GameLogic;
using StrategicCombat.Maps;

namespace StrategicCombat.GameScreens
{
    /// <summary>
    /// This class represent the gameplay for this game.
    /// </summary>
    public class GameplayScreen : GameScreen
    {
        #region Variables

        // Networksession variable.
        NetworkSession networkSession;

        // Gameplay logic
        GamePlayLogic gameLogic;

        // Background texture
        Texture2D backgroundTexture;

        // Map
        Map currentMap;

        // Combat scenes
        CombatScene[] combatScenes;
        int currentCombatScene = 0;
        CombatScene combatScene;

        // Is in combat
        bool isInCombat;

        // Gamemode
        byte gameMode;

        // Content manager and fonts
        ContentManager content;
        SpriteFont gameFont;

        // Transparency for fading screens when pausing.
        float pauseAlpha;

        #endregion

        #region Properties

        /// <summary>
        /// The logic for deciding whether the game is paused.
        /// </summary>
        new bool IsActive
        {
            get
            {
                if (this.gameMode == 2)
                {
                    // Pause behavior for networked games.
                    return !IsExiting;
                    
                }
                else
                {
                    // Pause behavior for single player games.
                    return base.IsActive;
                }
            }
        } // End of the IsActive property.

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new gameplay screen.
        /// </summary>
        /// <param name="networkSession"></param>
        /// <param name="gameMode">0: Single player, 1: local network, 2: live network</param>
        public GameplayScreen(NetworkSession networkSession, byte gameMode)
        {
            // Set the network session
            this.networkSession = networkSession;

            // Set the game mode
            this.gameMode = gameMode;
            this.isInCombat = false;

            // Set up the combat scene array
            this.combatScenes = new CombatScene[6];

            // Set the transition time for fading of screens.
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (this.content == null)
                this.content = new ContentManager(ScreenManager.Game.Services, "Content");

            // Create the map.
            this.currentMap = new Map(content, 75, 7, 6);

            // Load combat scenes.
            this.combatScenes[0] = new CombatSceneOne(content);
            this.combatScenes[1] = new CombatSceneTwo(content);
            this.combatScenes[2] = new CombatSceneThree(content);
            this.combatScenes[3] = new CombatSceneFour(content);
            this.combatScenes[4] = new CombatSceneFive(content);
            this.combatScenes[5] = new CombatSceneSix(content);

            // Set the default combat scene
            this.combatScene = this.combatScenes[this.currentCombatScene];

            // Set the gameplay logic
            switch (this.gameMode)
            {
                case 0:
                    this.gameLogic = new SinglePlayerGameLogic(content, this.networkSession, ControllingPlayer.Value);
                    break;
                case 1:
                    this.gameLogic = new LocalMultiplayerGameLogic(content, this.networkSession);
                    break;
                case 2:
                    this.gameLogic = new NetworkGameLogic(content, this.networkSession);
                    break;
            }

            // Load fonts
            this.gameFont = content.Load<SpriteFont>("Fonts/gamefont");

            // Load the background texture.
            this.backgroundTexture = content.Load<Texture2D>("MenuAssets/menuBackground");

            // Stop the menu music if it is playing
            if (Program.menuMusic != null && Program.menuMusic.IsPlaying)
                Program.menuMusic.Pause();

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();
        }

        /// <summary>
        /// Unload graphics content used by the screen.
        /// </summary>
        public override void UnloadContent()
        {
            content.Unload();
        }


        #endregion

        #region Methods

        /// <summary>
        /// Updates the state of the game.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, false);

            // Gradually fade in or out depending on whether we are covered by the pause screen.
            if (coveredByOtherScreen)
                pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
            else
                pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);

            if (IsActive)
            {
                // Check if there should be a combat
                if (this.isInCombat == false)
                    this.isInCombat = this.gameLogic.CollisionBetweenCombatUnits();

                // Update the game logic on the board.
                if (this.isInCombat == false)
                    this.gameLogic.UpdateOnBoard(gameTime, this.currentMap);

                // Update the game logic in a combat
                if (this.isInCombat == true)
                {
                    // Update the combat.
                    this.gameLogic.UpdateInCombat(gameTime, this.combatScene);

                    // Check if the combat has ended.
                    if (this.gameLogic.CheckIfCombatHasEnded() == true)
                    {
                        // Change the current combatScene
                        this.currentCombatScene = (this.currentCombatScene + 1) % this.combatScenes.Length;
                        this.combatScene = this.combatScenes[this.currentCombatScene];

                        // Make the game go back to the board.
                        this.isInCombat = false;
                    }
                }

                // Check if the game has ended, if it not has ended already.
                if(this.gameLogic.hasGameEnded == false)
                    this.gameLogic.CheckIfGameHasEnded();

            } // End of isActive.

            // If we are in a network game, check if we should return to the lobby.
            if ((networkSession != null) && !IsExiting)
            {
                if (networkSession.SessionState == NetworkSessionState.Lobby)
                {
                    if(this.gameMode == 1)
                        LoadingScreen.Load(ScreenManager, true, null, new BackgroundScreen(), new LocalNetworkLobby(networkSession));
                    else if (this.gameMode == 2)
                        LoadingScreen.Load(ScreenManager, true, null, new BackgroundScreen(), new NetworkLobby(networkSession));
                }
            }
        }

        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            if (ControllingPlayer.HasValue)
            {
                // In single player games, handle input for the controlling player.
                HandlePlayerInput(null, input, ControllingPlayer.Value);
            }
            else if (networkSession != null)
            {
                // In network game modes, handle input for all the
                // local players who are participating in the session.
                foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
                {
                    if (!HandlePlayerInput(gamer, input, gamer.SignedInGamer.PlayerIndex))
                        break;
                }
            }
        }

        /// <summary>
        /// Handles input for the specified player. In local game modes, this is called
        /// just once for the controlling player. In network modes, it can be called
        /// more than once if there are multiple profiles playing on the local machine.
        /// Returns true if we should continue to handle input for subsequent players,
        /// or false if this player has paused the game.
        /// </summary>
        public bool HandlePlayerInput(LocalNetworkGamer gamer, InputState input, PlayerIndex playerIndex)
        {
            // Look up inputs for the specified player profile.
            KeyboardState keyboardState = input.CurrentKeyboardStates[(int)playerIndex];
            GamePadState gamePadState = input.CurrentGamePadStates[(int)playerIndex];

            // The game pauses either if the user presses the pause button, or if
            // they unplug the active gamepad. This requires us to keep track of
            // whether a gamepad was ever plugged in, because we don't want to pause
            // on PC if they are playing with a keyboard and have no gamepad at all!
            bool gamePadDisconnected = !gamePadState.IsConnected && input.GamePadWasConnected[(int)playerIndex];

            // Handle player input on board.
            if(this.isInCombat == false)
                this.gameLogic.PlayerInputOnBoard(gamer, playerIndex, this.currentMap);
            
            // Handle player input in a combat scene.
            if (this.isInCombat == true)
                this.gameLogic.PlayerInputInCombat(gamer, ScreenManager.GraphicsDevice.Viewport, playerIndex, this.combatScene);

            if (input.IsPauseGame(playerIndex) || gamePadDisconnected)
            {
                ScreenManager.AddScreen(new PauseMenuScreen(networkSession), playerIndex);
                return false;
            }

            // Add the EndGameScreen if the game has ended.
            if (this.gameLogic.hasGameEnded == true)
            { 
                // Add the end game screen
                ScreenManager.AddScreen(new EndGameScreen(networkSession, this.gameLogic.winningPlayer), null);

                return false;
            }

            return true;
        }

        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // This game has a blue background. Why? Because!
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target, Color.Beige, 0, 0);

            // Create a new spritebatch.
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            // Begin 2D drawing
            spriteBatch.Begin();

            // Draw the background texture
            spriteBatch.Draw(this.backgroundTexture, new Rectangle(0, 0, 1280, 720), Color.White * TransitionAlpha);

            // Draw the board when there is no combat
            if (this.isInCombat == false)
            {
                // Draw the map.
                this.currentMap.Draw(spriteBatch);

                // Draw gameplay logic
                this.gameLogic.DrawOnBoard(spriteBatch, this.gameFont, this.currentMap);
            }

            // Draw the combat scene.
            if (this.isInCombat == true)
            {
                this.combatScene.Draw(spriteBatch);

                // Draw the gameplay logic.
                this.gameLogic.DrawInCombat(spriteBatch);
            }

            // End 2D drawing.
            spriteBatch.End();

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0 || pauseAlpha > 0)
            {
                float alpha = MathHelper.Lerp(1f - TransitionAlpha, 1f, pauseAlpha / 2);

                ScreenManager.FadeBackBufferToBlack(alpha);
            }
        }

        #endregion

    } // End of class

} // End of namespace
