using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using System.Diagnostics;
using Audio3D;

namespace FredriksdalAssault2
{
    /// <summary>
    /// This class represent a network game.
    /// </summary>
    public class NetworkGameScreen : GameScreen
    {

        #region Instance variables

        // A reference variable to the network session
        public NetworkSession networkSession;

        // A content manager.
        public ContentManager content;

        // A float value to make the transition smooth when pausing.
        public float pauseAlpha;

        // A variable for gameTime used for player input
        public GameTime storedGameTime;

        // Camera, game logic, map and a dynamic resource manager.
        private Camera gameCamera;
        private GameModeLogic gameModeLogic;
        private IMap currentMap;
        private DynamicResourcesManager dynamicResourceManager;

        // A spawn screen when a player is dead.
        SpawnScreen mySpawnScreen;
        bool showSpawnScreen = false;

        // Loadtime to have everyone start the game at the same time (10 seconds).
        int totalLoadTime = 10000;

        // Audio
        public AudioManager audioManager;

        #endregion

        #region Properties

        /// <summary>
        /// The logic for deciding whether the game is paused depends on whether
        /// this is a networked or single player game. If we are in a network session,
        /// we should go on updating the game even when the user tabs away from us or
        /// brings up the pause menu, because even though the local player is not
        /// responding to input, other remote players may not be paused. In single
        /// player modes, however, we want everything to pause if the game loses focus.
        /// </summary>
        public new bool IsActive
        {
            get
            {
                if (networkSession == null)
                {
                    // Pause behavior for single player games.
                    return base.IsActive;
                }
                else
                {
                    // Pause behavior for networked games.
                    return !IsExiting;
                }
            }

        } // End of the IsActive method

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new network game screen.
        /// </summary>
        /// <param name="networkSession"></param>
        public NetworkGameScreen(NetworkSession networkSession)
        {
            // Set the network session.
            this.networkSession = networkSession;

            // Set transition times for this screen.
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Load graphic content for the game.
        /// </summary>
        public override void LoadContent()
        {
            // Create a stopwatch.
            Stopwatch clock = new Stopwatch();

            // Start the clock.
            clock.Start();

            // Create a content manager if it is null.
            if (content == null)
                this.content = new ContentManager(ScreenManager.Game.Services, "Content");

            // Create a new audio manager
            this.audioManager = new AudioManager(content, 1000f, 0.9f);

            // Create new game logic based on the choosen game mode.
            switch (Program.gameMode)
            {
                case 0: 
                    this.gameModeLogic = new AssaultModeLogic(this.networkSession, this.content, ScreenManager.GraphicsDevice, ScreenManager.SpriteBatch);
                    break;
                case 1: 
                    this.gameModeLogic = new DeathMatchModeLogic(this.networkSession, this.content, ScreenManager.GraphicsDevice, ScreenManager.SpriteBatch);
                    break;
            }

            // Create a new map based on the choosen map.
            switch (Program.currentMap)
            {
                case 0: 
                    this.currentMap = new Valley(content, ScreenManager.GraphicsDevice);
                    break;
                case 1: 
                    this.currentMap = new Hills(content, ScreenManager.GraphicsDevice);
                    break;
                case 2:
                    this.currentMap = new Fortrest(content, ScreenManager.GraphicsDevice);
                    break;
            }
            
            // Create a new camera and a dynamic resource manager.
            this.gameCamera = this.gameModeLogic.SetUpNetworkGameCamera(ScreenManager.GraphicsDevice);
            this.dynamicResourceManager = new DynamicResourcesManager(content, ScreenManager.GraphicsDevice, networkSession);

            // Create a spawn screen.
            this.mySpawnScreen = new SpawnScreen(networkSession, this.gameCamera);

            // Stop the stopwatch
            clock.Stop();

            // Get the time difference
            int timeDifference = this.totalLoadTime - (int)clock.ElapsedMilliseconds;

            // Sleep for a while to allow for other players to catch up.
            if (timeDifference > 0)
                Thread.Sleep(timeDifference);

            // Start the background sound (GameModeLogic)
            this.audioManager.Play3DSound("summer", true, this.gameModeLogic);

            // 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();

        } // End of the LoadContent method.

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            // Dispose the audio manager to stop all sounds.
            this.audioManager.Dispose(true);

            // Unload the content
            content.Unload();

        } // End of the UnloadContent method.

        #endregion

        #region Update

        /// <summary>
        /// Update the state of the game.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="otherScreenHasFocus"></param>
        /// <param name="coveredByOtherScreen"></param>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            // Call the update method in the base class.
            base.Update(gameTime, otherScreenHasFocus, false);

            // Update the game time variable
            this.storedGameTime = gameTime;

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

            // Update if the game is active
            if (IsActive == true)
            {
                // Update the the map, the dynamic resource manager, the game logic and the audio manager.
                this.currentMap.Update(gameTime);
                this.dynamicResourceManager.Update(gameTime, this.currentMap);
                this.gameModeLogic.UpdateGameMode(gameTime, this.gameCamera, this.dynamicResourceManager, this.currentMap, this.audioManager);
                this.audioManager.Update(gameTime);

                // Update the spawn screen
                if (this.gameCamera.isDead == true && this.gameModeLogic.hasGameEnded == false)
                {
                    // Add a spawn screen if we not already are showing a spawn screen.
                    if (this.showSpawnScreen == false)
                    {
                        // Set the killed by name variable in a try/catch/finally block, because there could be a null point exception.
                        try
                        {
                            this.mySpawnScreen.killedByName = this.networkSession.FindGamerById(this.gameCamera.killedByGamerId).Gamertag;
                        }
                        catch (Exception ex)
                        {
                            String exMessage = ex.Message;
                            this.mySpawnScreen.killedByName = "Unknown";
                        }

                        // Add the spawn screen, for the player that controls the game.
                        ScreenManager.AddScreen(this.mySpawnScreen, null);

                        // Set show spawn screen to true
                        this.showSpawnScreen = true;
                    }

                    // Update the spawn screen.
                    this.mySpawnScreen.Update(gameTime);
                }

                // If we are showing the spawn screen check if we should remove it
                if (this.showSpawnScreen == true)
                {
                    // if the player is alive or if the game has ended.
                    if (this.gameCamera.isDead == false)
                    {
                        // Get back to the game.
                        this.gameModeLogic.GetBackToGame(this.mySpawnScreen);

                        // Remove the spawn screen
                        ScreenManager.RemoveScreen(this.mySpawnScreen);

                        this.showSpawnScreen = false;
                    }
                    else if (this.gameModeLogic.hasGameEnded == true)
                    {
                        // Remove the spawn screen
                        ScreenManager.RemoveScreen(this.mySpawnScreen);

                        this.showSpawnScreen = false;
                    }
                }

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

            } // End of IsActive

        } // End of the update method

        #endregion

        #region Playerinput

        /// <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(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(input, gamer.SignedInGamer.PlayerIndex))
                        break;
                }
            }

        } // End of the HandleInput method.

        /// <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(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.
            this.gameCamera.PlayerInput(storedGameTime, this.currentMap, playerIndex);
            this.dynamicResourceManager.FireBullets(storedGameTime, this.gameCamera.cameraPosition,
                    this.gameCamera.cameraDirection, this.gameCamera.cameraLookUpDown,
                    this.gameCamera.isDead, this.gameModeLogic.hasGameEnded, playerIndex);

            // Check if the game is paused.
            if (input.IsPauseGame(playerIndex) || gamePadDisconnected)
            {
                ScreenManager.AddScreen(new PauseMenuScreen(networkSession), playerIndex);
                return false;
            }

            // Add the end game screen if the game has ended.
            if (this.gameModeLogic.hasGameEnded == true && this.showSpawnScreen == false)
            {

                // Add the end game screen
                this.gameModeLogic.AddEndGameScreen(ScreenManager);
                return false;
            }

            return true;
        }

        #endregion

        #region Draw

         /// <summary>
        /// Draw the game screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // Clear the graphics on the screen and set the Z-buffer.
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 1);

            // Draw terrain and the dynamic resource manager.
            this.currentMap.Draw(gameTime, this.gameCamera.view, this.gameCamera.projection, this.gameCamera.viewProj, this.gameCamera.cameraPosition);
            if(this.gameCamera.isDead == false)
                this.gameCamera.DrawPlayerWeapon();
            this.gameModeLogic.DrawGameMode(gameTime, this.gameCamera, this.audioManager, ScreenManager.GraphicsDevice.Viewport, this.currentMap);
            this.dynamicResourceManager.Draw(gameTime, this.gameCamera.view, this.gameCamera.projection);
            
            // 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);
            }

        } // End of the draw method.

        #endregion

    } // End of class

} // End of namespace
