using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Net;
using Audio3D;

namespace FredriksdalAssault2
{
    /// <summary>
    /// This class represents the common logic for all game modes.
    /// </summary>
    public abstract class GameModeLogic : IAudioEmitter
    {

        #region Instance variables

        // A reference to the current networksession.
        public NetworkSession networkSession;

        // Content manager and graphicsdevice.
        ContentManager content;
        GraphicsDevice graphicsDevice;

        // Textures
        public Texture2D crosshairTexture;
        public Texture2D redPoint;
        public Texture2D redBackground;
        public Texture2D zoomCrosshair;

        // Total game time in milliseconds.
        public int totalGameTime = 300000;

        // Blink delay and blink countdown to make the screen blink red. 
        public int blinkDelay = 1000;
        public int blinkCountdown = 0;

        // A flag to mark if the game has ended
        public bool hasGameEnded = false;

        // Blood effects
        public BloodParticles bloodEffects;
        public int bloodEffectTime = 5000;
        public int bloodEffectCounter = -1;
        public Vector3 bloodEffectStartPosition;

        // Random generator
        public Random rndGenerator = new Random();

        // Audio
        public Vector3 position;
        public Vector3 forward;
        public Vector3 up;
        public Vector3 velocity;

        // SpriteBatch variables (2D)
        public SpriteBatch spriteBatch;
        public SpriteFont gameFont;
        public Color textColor;
        public float textScale;

        // Variables for network communication
        public PacketWriter packetWriter = new PacketWriter();
        public PacketReader packetReader = new PacketReader();

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the 3D position of the entity.
        /// </summary>
        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        /// <summary>
        /// Gets or sets which way the entity is facing.
        /// </summary>
        public Vector3 Forward
        {
            get { return forward; }
            set { forward = value; }
        }

        /// <summary>
        /// Gets or sets the orientation of this entity.
        /// </summary>
        public Vector3 Up
        {
            get { return up; }
            set { up = value; }
        }

        /// <summary>
        /// Gets or sets how fast this entity is moving.
        /// </summary>
        public Vector3 Velocity
        {
            get { return velocity; }
            protected set { velocity = value; }
        }

        #endregion

        #region Constructors
        
        /// <summary>
        /// Create a new game mode logic object.
        /// </summary>
        /// <param name="networkSession"></param>
        /// <param name="content"></param>
        /// <param name="graphicsDevice"></param>
        public GameModeLogic(NetworkSession networkSession, ContentManager content, GraphicsDevice graphicsDevice, 
            SpriteBatch spriteBatch)
        {
            // Instantiate variables
            this.networkSession = networkSession;
            this.totalGameTime = Program.totalGameTimeMilliseconds;
            this.content = content;
            this.graphicsDevice = graphicsDevice;
            this.spriteBatch = spriteBatch;

            // Load content
            this.crosshairTexture = this.content.Load<Texture2D>("Textures/crosshair");
            this.redPoint = this.content.Load<Texture2D>("Textures/red-arrow");
            this.redBackground = this.content.Load<Texture2D>("Textures/redbackground");
            this.zoomCrosshair = this.content.Load<Texture2D>("Textures/zoomCrosshair");

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

            // Create a new blood effect object
            this.bloodEffects = new BloodParticles(this.content, this.graphicsDevice);

        }

        #endregion

        #region Methods

        /// <summary>
        /// This method returns a game camera for a network game.
        /// </summary>
        /// <param name="graphicsDevice"></param>
        /// <returns>A game camera for a network game.</returns>
        public Camera SetUpNetworkGameCamera(GraphicsDevice graphicsDevice)
        {
            // Variables
            Vector3 startPosition = new Vector3(0, 0, 0);
            bool isAttacking = false;

            // Get the attacking value and startposition for a network gamer.
            if (networkSession != null)
            {
                // Loop through each local gamer to set the start position.
                foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
                {
                    NetworkPlayerProfile localPlayer = gamer.Tag as NetworkPlayerProfile;

                    // Get the team and the start position.
                    isAttacking = localPlayer.isAttacking;
                    startPosition = GetStartPosition(localPlayer);
                }
            }

            // Create a new game camera reference.
            Camera gameCamera;

            // Create a game camera.
            if (isAttacking == true)
            {
                gameCamera = new Camera(this.content, startPosition, new Vector3(0, 0, 1), new Vector3(0, 1, 0), this.graphicsDevice);
                gameCamera.playerModel.rotationMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(180));
            }
            else
            {
                gameCamera = new Camera(this.content, startPosition, new Vector3(0, 0, -1), new Vector3(0, 1, 0), this.graphicsDevice);
            }

            // Set the attacking value for the camera.
            gameCamera.isAttacking = isAttacking;

            return gameCamera;

        } // End of the SetUpNetworkGameCamera method.

        /// <summary>
        /// A helper method to update the audio listener and audio emitter concerning the background sound.
        /// </summary>
        /// <param name="gameCamera"></param>
        /// <param name="audioManager"></param>
        private void UpdateAudioForBackgroundSound(Camera gameCamera, AudioManager audioManager)
        {
            // Update the audio listener
            audioManager.listener.Position = gameCamera.cameraPosition;
            audioManager.listener.Forward = gameCamera.cameraDirection;
            audioManager.listener.Up = Vector3.Up;
            audioManager.listener.Velocity = gameCamera.cameraDirection * gameCamera.cameraSpeed;

            // Set position data and play the choke sound.
            this.Position = gameCamera.cameraPosition;
            this.Forward = gameCamera.cameraDirection;
            this.Up = Vector3.Up;
            this.Velocity = gameCamera.cameraDirection * gameCamera.cameraSpeed;

        } // End of the UpdateAudioEmitterForBackgroundSound method.

        /// <summary>
        /// Play a choke sound for a network player
        /// </summary>
        /// <param name="networkPlayer"></param>
        /// <param name="audioManager"></param>
        private void PlayChokeSound(NetworkPlayerProfile networkPlayer, AudioManager audioManager)
        {
            // Set position data and play the choke sound.
            this.Position = networkPlayer.soldierPosition;
            this.Forward = networkPlayer.direction;
            this.Up = Vector3.Up;
            this.Velocity = Vector3.Zero;
            audioManager.Play3DSound("choke", false, this);

        } // End of the PlayChokeSound method

        /// <summary>
        /// Play a choke sound for the local player.
        /// </summary>
        /// <param name="gameCamera"></param>
        /// <param name="audioManager"></param>
        private void PlayChokeSound(Camera gameCamera, AudioManager audioManager)
        {
            // Set position data and play the choke sound.
            this.Position = gameCamera.cameraPosition;
            this.Forward = gameCamera.cameraDirection;
            this.Up = Vector3.Up;
            this.Velocity = gameCamera.cameraDirection * gameCamera.cameraSpeed;
            audioManager.Play3DSound("choke", false, this);

        } // End of the PlayChokeSound method

        // Abstract methods that has to be implemented by classes that inherits from this class.
        public abstract Vector3 GetStartPosition(PlayerProfile playerProfile);
        public abstract void GetBackToGame(SpawnScreen spawnScreen);
        public abstract void AddEndGameScreen(ScreenManager screenManager);

        #endregion

        #region Update and draw

        /// <summary>
        /// Update this game mode.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="gameCamera"></param>
        /// <param name="dynamicResourceManager"></param>
        /// <param name="currentMap"></param>
        /// <param name="audioManager"></param>
        public void UpdateGameMode(GameTime gameTime, Camera gameCamera, DynamicResourcesManager dynamicResourceManager, IMap currentMap, AudioManager audioManager)
        {
            // Only update the network session if there is a network session.
            if (networkSession != null)
            {
                // Update network session
                UpdateNetworkSession(gameTime, gameCamera, dynamicResourceManager, currentMap, audioManager);

                // Decrease total game time if the game not has ended.
                if (this.totalGameTime > -1)
                {
                    // Decrease total gametime
                    this.totalGameTime -= gameTime.ElapsedGameTime.Milliseconds;
                }

                // Update audio listener and audio emitter.
                UpdateAudioForBackgroundSound(gameCamera, audioManager);

                // Check if the game has ended.
                CheckIfGameHasEnded(gameTime, currentMap);
            }
      
        } // End of the UpdateGameMode method

        /// <summary>
        /// Draw the game mode.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="gameCamera"></param>
        /// <param name="audioManager"></param>
        /// <param name="spriteBatch"></param>
        /// <param name="viewPort"></param>
        /// <param name="spriteFont"></param>
        /// <param name="currentMap"></param>
        public void DrawGameMode(GameTime gameTime, Camera gameCamera, AudioManager audioManager, Viewport viewPort, IMap currentMap)
        {
            
            // Draw the network session (3D) if there is a network session and if the game not
            // has ended.
            if (this.networkSession != null && this.hasGameEnded == false)
            {
                // Call to draw network session.
                DrawNetworkSession(gameTime, gameCamera, audioManager);
            }

            // Draw vegetation (Drawn late because of alpha blending)
            currentMap.DrawGrass(gameCamera.view, gameCamera.projection, gameTime);
            currentMap.DrawTrees(gameCamera.view, gameCamera.projection, gameCamera.cameraPosition);

            // Draw the enemy shot effect.
            if (this.networkSession != null && this.hasGameEnded == false)
            {
                // Call to draw network session.
                DrawEnemyShots(gameTime, gameCamera);
            }

            // Draw blood effects (Drawn late because of alpha blending)
            if (bloodEffectCounter >= 0)
            {
                this.bloodEffects.DrawBloodEffect(gameTime, gameCamera.view, gameCamera.projection);

                // Subract game time from the bloodEffect counter
                bloodEffectCounter -= gameTime.ElapsedGameTime.Milliseconds;
            }

            // Draw common information for all game modes.
            int safeAreaLeft = viewPort.TitleSafeArea.Left;
            int safeAreaRight = viewPort.TitleSafeArea.Right;
            int safeAreaTop = viewPort.TitleSafeArea.Top;
            int safeAreaBottom = viewPort.TitleSafeArea.Bottom;

            // Do not draw this if the player is dead or the game has ended.
            if (gameCamera.isDead == false && !this.hasGameEnded)
            {
                // Begin drawing
                spriteBatch.Begin();

                // Text scale and color
                this.textScale = 0.8f;
                this.textColor = Color.White;

                // Set text color to black if the player is on the attacking team.
                if (gameCamera.isAttacking)
                    this.textColor = Color.Black;

                // Total game time string
                int minutes = this.totalGameTime / 60000;
                String gameTimeLeft = minutes + ":" + String.Format("{0:00}", (this.totalGameTime - (minutes * 60000)) / 1000);

                // Draw the crosshair
                spriteBatch.Draw(crosshairTexture, new Vector2((viewPort.Width / 2) - (crosshairTexture.Width / 2),
                    (viewPort.Height / 2) - (crosshairTexture.Height / 2)), Color.White);

                // Draw the zoom crosshair
                if (gameCamera.isZooming == true)
                {
                    spriteBatch.Draw(zoomCrosshair, new Vector2((viewPort.Width / 2) - (zoomCrosshair.Width / 2),
                    (viewPort.Height / 2) - (zoomCrosshair.Height / 2)), Color.White);
                }

                // Draw total game time.
                spriteBatch.DrawString(this.gameFont, gameTimeLeft, new Vector2(safeAreaRight
                    - (this.gameFont.MeasureString(gameTimeLeft).X * textScale) - 10, safeAreaTop + 30), textColor, 0, Vector2.Zero, textScale, SpriteEffects.None, 0);

                // Draw the minimap
                spriteBatch.Draw(currentMap.minimap, new Rectangle(safeAreaLeft + 10, safeAreaBottom - 139, 129, 129), null, Color.White, 0, new Vector2(0, 0),
                    SpriteEffects.None, 0);

                int xValue = (int)(gameCamera.cameraPosition.X / 100);
                int yValue = (int)(gameCamera.cameraPosition.Z / 100);

                // Pulsate the size red point and barrels.
                double time = gameTime.TotalGameTime.TotalSeconds;
                float pulsate = (float)Math.Sin(time * 6) + 1;
                float scale = 0.2f + pulsate * 0.40f;
                float scaleBarrel = 0.1f + pulsate * 0.10f;

                // Rotate the arrow.
                Vector3 destinationRightVector = Vector3.Cross(gameCamera.cameraDirection, Vector3.Up);
                float forwardDot = Vector3.Dot(new Vector3(0, 0, -1), gameCamera.cameraDirection);
                float rightDot = Vector3.Dot(new Vector3(0, 0, -1), destinationRightVector);

                // Make sure we stay in range no matter what, so Acos doesn't fail later 
                forwardDot = MathHelper.Clamp(forwardDot, -1.0f, 1.0f);

                float angleBetween = (float)Math.Acos((float)forwardDot);

                if (rightDot < 0.0f)
                    angleBetween *= -1.0f;

                // Draw the red arrow.
                spriteBatch.Draw(redPoint, new Vector2(safeAreaLeft + 10 + xValue + 64, safeAreaBottom - 139 + yValue + 64), null, Color.White, -angleBetween, new Vector2(8, 8),
                    scale, SpriteEffects.None, 0);

                // If the player is wounded, make the screen blink red
                if (gameCamera.isWounded == true && gameCamera.isDead == false)
                {
                    if (this.blinkCountdown <= 0)
                    {
                        spriteBatch.Draw(this.redBackground, new Rectangle(0, 0, viewPort.Width, viewPort.Height),
                            new Color(255, 255, 255, 0.4f));

                        // Reset the blink countdown.
                        this.blinkCountdown = this.blinkDelay;
                    }
                    else
                    {
                        this.blinkCountdown -= gameTime.ElapsedGameTime.Milliseconds;
                    }
                }

                // Draw specific information
                DrawSpecific2D(viewPort, gameCamera, currentMap, scaleBarrel);

                // Draw gamer tags
                if (networkSession != null)
                    DrawGamerTags(gameCamera, this.spriteBatch, this.gameFont, currentMap);

                // End drawing
                spriteBatch.End();
            }
            
        } // End of the DrawGameMode method

        // Abstract methods that need to be implemented by classes that inherits from this class.
        public abstract void CheckIfGameHasEnded(GameTime gameTime, IMap currentMap);
        public abstract void DrawSpecific2D(Viewport viewPort, Camera gameCamera, IMap currentMap, float scaleBarrel);

        #endregion

        #region Networking

        /// <summary>
        /// Updates the state of the network session.
        /// </summary>
        private void UpdateNetworkSession(GameTime gameTime, Camera gameCamera, DynamicResourcesManager dynamicResourceManager, IMap currentMap, AudioManager audioManager)
        {
            // Send data to server from client players.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                // Send data about the player model to the server.
                SendPositionDataFromClient(gamer, gameCamera, dynamicResourceManager);

                // Make collision checks between local gamers bullets and network soldiers, sends
                // information if a player is dead.
                UpdateLocalGamer(gamer, gameCamera, dynamicResourceManager, currentMap, audioManager);
            }

            // If we are the server, do updates and send packets to clients.
            if (networkSession.IsHost)
            {
                // Loop over all the players in the session, not just the local ones!
                foreach (NetworkGamer gamer in networkSession.AllGamers)
                {
                    // Look up what player is associated with this player.
                    NetworkPlayerProfile networkPlayer = gamer.Tag as NetworkPlayerProfile;

                    UpdateServerDeaths(gamer, networkPlayer, gameTime, gameCamera);
                    UpdateServerPositions(networkPlayer);
                    UpdateServerBarrels(networkPlayer, currentMap, audioManager);

                }
            }

            // Pump the underlying session object.
            networkSession.Update();

            // Make sure the session has not ended.
            if (networkSession == null)
                return;

            // Read any incoming network packets.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                if (gamer.IsHost)
                {
                    ServerReadInputFromClients(gamer, gameCamera, audioManager);
                }
                else
                {
                    ClientReadGameStateFromServer(gamer, gameCamera, currentMap, audioManager);
                }
            }

        } // End of the UpdateNetworkSession method

        /// <summary>
        /// Send data about a local player to the server.
        /// </summary>
        /// <param name="gamer"></param>
        /// <param name="gameCamera"></param>
        /// <param name="dynamicResourceManager"></param>
        private void SendPositionDataFromClient(LocalNetworkGamer gamer, Camera gameCamera, DynamicResourcesManager dynamicResourceManager)
        {
            // Look up what playerData object that is associated with this local player.
            NetworkPlayerProfile localPlayer = gamer.Tag as NetworkPlayerProfile;

            // Set the datatype variable, 0: player data.
            byte dataType = 0;

            // Save data in localPlayer
            localPlayer.soldierPosition = gameCamera.playerModel.position;
            localPlayer.rotation = gameCamera.playerModel.rotationQuaternion;
            localPlayer.clipNumber = gameCamera.playerModel.clipNumber;
            localPlayer.numberOfKills = gameCamera.numberOfKills;
            localPlayer.isShooting = dynamicResourceManager.isShooting;

            // Send data to the server if we not are the server.
            if (!networkSession.IsHost)
            {
                // Write world, clip name and number of kills to the packet.
                packetWriter.Write(dataType);
                packetWriter.Write(localPlayer.soldierPosition);
                packetWriter.Write(localPlayer.rotation);
                packetWriter.Write(localPlayer.clipNumber);
                packetWriter.Write(localPlayer.numberOfKills);
                packetWriter.Write(localPlayer.isShooting);

                // Send our input data to the server.
                gamer.SendData(packetWriter, SendDataOptions.InOrder, networkSession.Host);
            }

        } // End of the sendPositionDataFromClient method

        /// <summary>
        /// Check for bullet collision with other players, and send a message to the server if there is
        /// a collision.
        /// </summary>
        private void UpdateLocalGamer(LocalNetworkGamer localGamer, Camera gameCamera, DynamicResourcesManager dynamicResourceManager, IMap currentMap, AudioManager audioManager)
        {

            // Loop over all the players in the session, not just the local ones!
            foreach (NetworkGamer networkGamer in networkSession.AllGamers)
            {
                // Do not do this collision check for the local gamer
                if (localGamer.Id != networkGamer.Id)
                {
                    // Look up what player is associated with this player.
                    NetworkPlayerProfile networkPlayer = networkGamer.Tag as NetworkPlayerProfile;

                    // Do a collision check, returns true or false.
                    bool collision = dynamicResourceManager.BulletCollisionOnClient(localGamer, networkPlayer, currentMap);

                    // Send a network package if there was a collision.
                    if (collision == true)
                    {
                        // Declare a data type variable
                        byte dataType;

                        // Create a new random number
                        double randomValue = rndGenerator.NextDouble();

                        // The player is dead 50 % of the time and wounded 50 % of the time.
                        if (randomValue < 0.5)
                        {
                            // Set the data type variable, 1: death data.
                            dataType = 1;

                            // Set data for the network gamer that is dead.
                            networkPlayer.isDead = true;
                            networkPlayer.killedByGamerId = localGamer.Id;

                            // Start the blood effect
                            this.bloodEffectCounter = this.bloodEffectTime;
                            this.bloodEffectStartPosition = networkPlayer.soldier.boundingSpheres[1].Center;
                            this.bloodEffects.AddParticles(80, this.bloodEffectStartPosition);

                            // Play a choke sound
                            PlayChokeSound(networkPlayer, audioManager);
     
                            // Add one kill to the camera record
                            CalculateNumberOfKills(networkPlayer, gameCamera);

                        }
                        else
                        {
                            // Set the data type variable.
                            dataType = 2;

                            // Mark the player as wounded.
                            networkPlayer.isWounded = true;
                        }

                        // Send data to the server if we not are the server.
                        if (!networkSession.IsHost)
                        {
                            if (dataType == 1)
                            {
                                // Write data to the server
                                packetWriter.Write(dataType);
                                packetWriter.Write(networkGamer.Id);
                                packetWriter.Write(networkPlayer.isDead);
                                packetWriter.Write(networkPlayer.killedByGamerId);
                            }

                            if (dataType == 2)
                            {
                                // Write data to the server
                                packetWriter.Write(dataType);
                                packetWriter.Write(networkGamer.Id);
                                packetWriter.Write(networkPlayer.isWounded);
                            }

                            // Send our input data to the server, this packet must get to the destination.
                            localGamer.SendData(packetWriter, SendDataOptions.Reliable, networkSession.Host);
                        }

                        // Send data to all network players if we are the server.
                        if (networkSession.IsHost)
                        {
                            if (dataType == 1)
                                SendDeathDataFromServer(networkPlayer);
                            if (dataType == 2)
                                SendWoundedDataFromServer(networkPlayer);
                        }
                    }
                }
            }

        } // End of the UpdateLocalGamer method

        /// <summary>
        /// Check if a player should be alive and send a
        /// </summary>
        /// <param name="gamer"></param>
        /// <param name="networkPlayer"></param>
        private void UpdateServerDeaths(NetworkGamer gamer, NetworkPlayerProfile networkPlayer, GameTime gameTime, Camera gameCamera)
        {
            // If a player is dead, check to see if he should be alive
            if (networkPlayer.isDead == true)
            {
                // Check if the player should be alive.
                networkPlayer.UpdateSpawnTime(gameTime);

                // If networkPlayer is alive then send data to all network players
                // in the session.
                if (networkPlayer.isDead == false)
                {
                    SendDeathDataFromServer(networkPlayer);

                    // If the gamer is the host then set values if he is alive because
                    // there will not be any packets sent to the server.
                    if (gamer.IsHost)
                    {
                        // Set death data for the host.
                        gameCamera.isDead = networkPlayer.isDead;
                        gameCamera.isWounded = networkPlayer.isWounded;
                    }
                }
            }

        } // End of the UpdateServerDeaths method

        /// <summary>
        /// Send position data to all network gamers in the session.
        /// </summary>
        private void UpdateServerPositions(NetworkPlayerProfile player)
        {
            // Set the datatype variable, 0: position data.
            byte dataType = 0;

            // Write the player model state into the output network packet.
            packetWriter.Write(dataType);
            packetWriter.Write(player.playerId);
            packetWriter.Write(player.soldierPosition);
            packetWriter.Write(player.rotation);
            packetWriter.Write(player.clipNumber);
            packetWriter.Write(player.numberOfKills);
            packetWriter.Write(player.isShooting);
            packetWriter.Write(player.collectedBarrels);
  
            // Send the combined data for all players to everyone in the session.
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;

            server.SendData(packetWriter, SendDataOptions.InOrder);

        } // End of the UpdateServerPositions method.

        /// <summary>
        /// Send a package with data if a player is dead or alive.
        /// </summary>
        /// <param name="player"></param>
        private void SendDeathDataFromServer(NetworkPlayerProfile player)
        {
            // Set the datatype variable, 1: death data.
            byte dataType = 1;

            // Create the package
            packetWriter.Write(dataType);
            packetWriter.Write(player.playerId);
            packetWriter.Write(player.isDead);
            packetWriter.Write(player.killedByGamerId);
            packetWriter.Write(player.isWounded);

            // Send the data for the player to everyone in the network session.
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;

            // Send the package reliable.
            server.SendData(packetWriter, SendDataOptions.Reliable);

        } // End of the SendDeathDataFromServer method

        /// <summary>
        /// Send a package with data if a player is dead or alive.
        /// </summary>
        /// <param name="player"></param>
        private void SendWoundedDataFromServer(NetworkPlayerProfile player)
        {
            // Set the datatype variable, 2: wounded data.
            byte dataType = 2;

            // Create the package
            packetWriter.Write(dataType);
            packetWriter.Write(player.playerId);
            packetWriter.Write(player.isWounded);

            // Send the data for the player to everyone in the network session.
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;

            // Send the package reliable.
            server.SendData(packetWriter, SendDataOptions.Reliable);

        } // End of the SendWoundedDataFromServer method


        /// <summary>
        /// This method only runs on the server. It reads a packet that
        /// have been sent over the network by a client machine, storing
        /// them for later use by the UpdateServer methods.
        /// </summary>
        private void ServerReadInputFromClients(LocalNetworkGamer gamer, Camera gameCamera, AudioManager audioManager)
        {
            // Keep reading as long as incoming packets are available.
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;

                // Read a single packet from the network.
                gamer.ReceiveData(packetReader, out sender);

                if (!sender.IsLocal)
                {

                    // Check what type av package that has been received.
                    byte typeOfPackage = packetReader.ReadByte();

                    // Position data about a local gamer.
                    if (typeOfPackage == 0)
                    {
                        // Look up the player associated with whoever sent this packet.
                        NetworkPlayerProfile remotePlayer = sender.Tag as NetworkPlayerProfile;

                        // Read the latest inputs controlling this player.
                        remotePlayer.soldierPosition = packetReader.ReadVector3();
                        remotePlayer.rotation = packetReader.ReadQuaternion();
                        remotePlayer.clipNumber = packetReader.ReadByte();
                        remotePlayer.numberOfKills = packetReader.ReadInt16();
                        remotePlayer.isShooting = packetReader.ReadBoolean();
                    }

                    // Data about killing (dead).
                    if (typeOfPackage == 1)
                    {
                        // Get data from the package.
                        byte gamerId = packetReader.ReadByte();
                        bool isDead = packetReader.ReadBoolean();
                        byte killedByGamerId = packetReader.ReadByte();

                        // Look up which gamer this state refers to.
                        NetworkGamer remoteGamer = networkSession.FindGamerById(gamerId);

                        // Make sure that the remote gamer still are playing.
                        if (remoteGamer != null)
                        {
                            // Update our local state with data from the network packet.
                            NetworkPlayerProfile playerData = remoteGamer.Tag as NetworkPlayerProfile;

                            // Do not update and send data if the player already is marked as dead.
                            if (!playerData.isDead)
                            {
                                // Set data for the network player.
                                playerData.isDead = isDead;
                                playerData.killedByGamerId = killedByGamerId;

                                if (remoteGamer.IsHost)
                                {
                                    // Play a choke sound.
                                    if (gameCamera.isDead == false)
                                        PlayChokeSound(gameCamera, audioManager);

                                    // Set variables for the host camera
                                    gameCamera.isDead = playerData.isDead;
                                    gameCamera.killedByGamerId = playerData.killedByGamerId;
                                }

                                // Send a packet about the death to every network player in the session.
                                SendDeathDataFromServer(playerData);
                            }
                        }
                    }

                    // Data about killing (wounded).
                    if (typeOfPackage == 2)
                    {
                        // Get data from the package.
                        byte gamerId = packetReader.ReadByte();
                        bool isWounded = packetReader.ReadBoolean();

                        // Look up which gamer this state refers to.
                        NetworkGamer remoteGamer = networkSession.FindGamerById(gamerId);

                        // Make sure that the remote gamer still are playing.
                        if (remoteGamer != null)
                        {
                            // Update our local state with data from the network packet.
                            NetworkPlayerProfile playerData = remoteGamer.Tag as NetworkPlayerProfile;

                            // Do not update and send data if the player already is wounded
                            if (!playerData.isWounded)
                            {
                                // Set data for the network player.
                                playerData.isWounded = isWounded;

                                if (remoteGamer.IsHost)
                                {
                                    // Set variables for the host camera
                                    gameCamera.isWounded = playerData.isWounded;
                                }

                                // Send a packet about wounded player to every network player in the session.
                                SendWoundedDataFromServer(playerData);
                            }
                        }
                    }
                }
            }
        } // End of the ServerReadInputFromClients method

        /// <summary>
        /// This method only runs on client machines. It read a packet that
        /// is sent from the server.
        /// </summary>
        public void ClientReadGameStateFromServer(LocalNetworkGamer gamer, Camera gameCamera, IMap currentMap, AudioManager audioManager)
        {

            // Keep reading as long as incoming packets are available.
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;

                // Read a single packet from the network.
                gamer.ReceiveData(packetReader, out sender);

                // Check what type av package that has been received.
                byte typeOfPackage = packetReader.ReadByte();

                // Position data.
                if (typeOfPackage == 0)
                {
                    // Read the state of one player from the network packet.
                    byte gamerId = packetReader.ReadByte();
                    Vector3 soldierPosition = packetReader.ReadVector3();
                    Quaternion soldierRotation = packetReader.ReadQuaternion();
                    byte clipNumber = packetReader.ReadByte();
                    Int16 numberOfKills = packetReader.ReadInt16();
                    bool isShooting = packetReader.ReadBoolean();
                    byte collectedBarrels = packetReader.ReadByte();

                    // Look up which gamer this state refers to.
                    NetworkGamer remoteGamer = networkSession.FindGamerById(gamerId);

                    // This might come back null if the gamer left the session after
                    // the host sent the packet but before we received it. If that
                    // happens, we just ignore the data for this gamer.
                    if (remoteGamer != null)
                    {
                        // Update our local state with data from the network packet.
                        NetworkPlayerProfile playerData = remoteGamer.Tag as NetworkPlayerProfile;

                        // Do not update position data for our own gamer
                        if (remoteGamer.Id != gamer.Id)
                        {
                            playerData.soldierPosition = soldierPosition;
                            playerData.rotation = soldierRotation;
                            playerData.clipNumber = clipNumber;
                            playerData.numberOfKills = numberOfKills;
                            playerData.isShooting = isShooting;
                        }

                        // Set the number of collected barrels for a player.
                        playerData.collectedBarrels = collectedBarrels;

                        // Set the number of collected barrels for the camera.
                        if (remoteGamer.Id == gamer.Id)
                        {
                            // Set the number of collected barrels for the player.
                            gameCamera.collectedBarrels = playerData.collectedBarrels;
                        }
                    }

                } // End of position data.

                // Death data.
                if (typeOfPackage == 1)
                {
                    // Read the state of one player from the network packet.
                    byte gamerId = packetReader.ReadByte();
                    bool isDeadOrAlive = packetReader.ReadBoolean();
                    byte killedBy = packetReader.ReadByte();
                    bool isWounded = packetReader.ReadBoolean();

                    // Look up which gamer this state refers to.
                    NetworkGamer remoteGamer = networkSession.FindGamerById(gamerId);

                    // This might come back null if the gamer left the session after
                    // the host sent the packet but before we received it. If that
                    // happens, we just ignore the data for this gamer.
                    if (remoteGamer != null)
                    {
                        // Update our local state with data from the network packet.
                        NetworkPlayerProfile playerData = remoteGamer.Tag as NetworkPlayerProfile;

                        playerData.isDead = isDeadOrAlive;
                        playerData.killedByGamerId = killedBy;
                        playerData.isWounded = isWounded;

                        if (remoteGamer.Id == gamer.Id)
                        {
                            // Play a choke sound.
                            if (gameCamera.isDead == false)
                                PlayChokeSound(gameCamera, audioManager);

                            // Set the dead variables for the camera
                            gameCamera.isDead = playerData.isDead;
                            gameCamera.killedByGamerId = playerData.killedByGamerId;
                            gameCamera.isWounded = playerData.isWounded;
                        }
                    }

                } // End of death data.

                // Wounded data.
                if (typeOfPackage == 2)
                {
                    // Read the state of one player from the network packet.
                    byte gamerId = packetReader.ReadByte();
                    bool isWounded = packetReader.ReadBoolean();

                    // Look up which gamer this state refers to.
                    NetworkGamer remoteGamer = networkSession.FindGamerById(gamerId);

                    // This might come back null if the gamer left the session after
                    // the host sent the packet but before we received it. If that
                    // happens, we just ignore the data for this gamer.
                    if (remoteGamer != null)
                    {
                        // Update our local state with data from the network packet.
                        NetworkPlayerProfile playerData = remoteGamer.Tag as NetworkPlayerProfile;

                        playerData.isWounded = isWounded;

                        if (remoteGamer.Id == gamer.Id)
                        {
                            // Set the wounded variables for the camera
                            gameCamera.isWounded = playerData.isWounded;
                        }
                    }
                } // End of wounded data.

                // Barrel data.
                if (typeOfPackage == 3)
                {
                    byte barrelToRemove = packetReader.ReadByte();

                    // Remove the barrel.
                    currentMap.CheckForItemToRemove(barrelToRemove, audioManager);

                } // End of barrel data
            }

        } // End of the ClientReadGameStateFromServer method

        /// <summary>
        /// Draws the state of an active network session.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="gameCamera"></param>
        /// <param name="audioManager"></param>
        private void DrawNetworkSession(GameTime gameTime, Camera gameCamera, AudioManager audioManager)
        {
            // Loop through each local gamer.
            foreach (LocalNetworkGamer localGamer in networkSession.LocalGamers)
            {
                // For each person in the session...
                foreach (NetworkGamer gamer in networkSession.AllGamers)
                {
                    // Look up the player object belonging to this network gamer.
                    NetworkPlayerProfile player = gamer.Tag as NetworkPlayerProfile;

                    // Update each player model, must update each player model so that the server
                    // can do collision checks.
                    player.UpdatePlayerProfile(gameTime, audioManager);

                    // Do not draw other players when you are dead
                    if (!gameCamera.isDead)
                    {
                        // Just draw player models and shot effect for other gamers.
                        if (localGamer.Id != gamer.Id)
                        {
                            // Draw each player model
                            player.DrawPlayerProfile(gameCamera.view, gameCamera.projection);

                        }
                    }
                }
            }

        } // End of the DrawNetworkSession method.

        /// <summary>
        /// Draw a enemy shot effect.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="gameCamera"></param>
        /// <param name="audioManager"></param>
        private void DrawEnemyShots(GameTime gameTime, Camera gameCamera)
        {
            // Loop through each local gamer.
            foreach (LocalNetworkGamer localGamer in networkSession.LocalGamers)
            {
                // For each person in the session...
                foreach (NetworkGamer gamer in networkSession.AllGamers)
                {
                    // Look up the player object belonging to this network gamer.
                    NetworkPlayerProfile player = gamer.Tag as NetworkPlayerProfile;

                    // Do not draw other players when you are dead
                    if (!gameCamera.isDead)
                    {
                        // Just draw player models and shot effect for other gamers.
                        if (localGamer.Id != gamer.Id)
                        {
                            // Draw the shot effect.
                            player.DrawShotEffect(gameTime, gameCamera.view, gameCamera.projection);
                        }
                    }
                }
            }

        } // End of the DrawEnemyShots method.

        /// <summary>
        /// Draw the gamer tag for each player.
        /// </summary>
        private void DrawGamerTags(Camera gameCamera, SpriteBatch spriteBatch, SpriteFont spriteFont, IMap currentMap)
        {
            // Loop through each local gamer.
            foreach (LocalNetworkGamer localGamer in networkSession.LocalGamers)
            {
                // For each person in the session...
                foreach (NetworkGamer gamer in networkSession.AllGamers)
                {
                    // Look up the player object belonging to this network gamer.
                    NetworkPlayerProfile player = gamer.Tag as NetworkPlayerProfile;

                    // Do not draw other players when you are dead
                    if (!gameCamera.isDead)
                    {
                        // Do not draw our own gamer tag, and do not draw a gamer tag when we are looking on the ground.
                        if (localGamer.Id != player.playerId)
                        {
                            player.DrawGamerTag(this.graphicsDevice.Viewport, gameCamera.view, gameCamera.projection,
                                spriteBatch, spriteFont, gameCamera.cameraPosition, gameCamera.cameraDirection + gameCamera.cameraLookUpDown, currentMap);
                        }
                    }
                }
            }

        } // End of the DrawGamerTags method

        // Abstract methods that must be implemented by class that inherits from this class
        public abstract void CalculateNumberOfKills(PlayerProfile playerProfile, Camera gameCamera);
        public abstract void UpdateServerBarrels(NetworkPlayerProfile networkPlayer, IMap currentMap, AudioManager audioManager);

        #endregion

    } // End of the class

} // End of the namespace
