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.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.GamerServices;
using StrategicCombat.PlayerProfiles;
using StrategicCombat.CombatUnits;
using StrategicCombat.Teams;
using StrategicCombat.Maps;
using StrategicCombat.PowerUps;

namespace StrategicCombat.GameLogic
{
    /// <summary>
    /// This class represents game logic for a single player game.
    /// </summary>
    public class SinglePlayerGameLogic : GamePlayLogic
    {
        #region Variables

        // Player profiles
        public SignedInGamer singleHumanGamer;
        public PlayerProfile computerPlayerProfile;

        #endregion

        #region Constructors

        /// <summary>
        /// Create new SinglePlayerGameLogic.
        /// </summary>
        /// <param name="content"></param>
        public SinglePlayerGameLogic(ContentManager content, NetworkSession networkSession, PlayerIndex controllingPlayer)
            : base(networkSession, content)
        {
  
            // Get the signed in gamer
            singleHumanGamer = Gamer.SignedInGamers[controllingPlayer];

            // Create a player profile for the human player.
            if (Program.hostTeam == 0)
            {
                singleHumanGamer.Tag = new HumanPlayerProfile(1, singleHumanGamer.Gamertag, true, new LeftTeam(content), content);
                this.leftGamerTag = singleHumanGamer.Gamertag;
            }
            else if (Program.hostTeam == 1)
            {
                singleHumanGamer.Tag = new HumanPlayerProfile(1, singleHumanGamer.Gamertag, true, new RightTeam(content), content);
                this.rightGamerTag = singleHumanGamer.Gamertag;
            }

            // Create a computer player profile
            if (Program.hostTeam == 0)
            {
                this.computerPlayerProfile = new ComputerPlayerProfile(2, "Computer AI", false, new RightTeam(content));
                this.rightGamerTag = this.computerPlayerProfile.playerName;
            }
            else if (Program.hostTeam == 1)
            {
                this.computerPlayerProfile = new ComputerPlayerProfile(2, "Computer AI", false, new LeftTeam(content));
                this.leftGamerTag = this.computerPlayerProfile.playerName;
            }

            // Set computer health damage for different difficulty levels
            if (Program.difficulty == 1)
            {
                // Medium difficulty
                foreach (CombatUnit combatUnit in this.computerPlayerProfile.team.combatUnits)
                {
                    combatUnit.healthDamage *= 1.5f;
                }

            }
            if (Program.difficulty == 2)
            {
                // Hard difficulty
                foreach (CombatUnit combatUnit in this.computerPlayerProfile.team.combatUnits)
                {
                    combatUnit.healthDamage *= 2f;
                }
            }

        }

        #endregion

        #region Methods

        /// <summary>
        /// Handles player input on the board.
        /// </summary>
        /// <param name="playerIndex"></param>
        public override void PlayerInputOnBoard(LocalNetworkGamer gamer, PlayerIndex playerIndex, Map currentMap)
        {
            
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Handle human player movement.
            if (player.hasTurn == true)
                player.PlayerMovementOnBoard(playerIndex, currentMap, this.computerPlayerProfile);
            else if (this.computerPlayerProfile.hasTurn == true)
                this.computerPlayerProfile.PlayerMovementOnBoard(playerIndex, currentMap, player);

        } // End of the PlayerInputOnBoard method.

        /// <summary>
        /// Handles player input in a combat.
        /// </summary>
        /// <param name="playerIndex"></param>
        /// <param name="currentCombatScene"></param>
        public override void PlayerInputInCombat(LocalNetworkGamer gamer, Viewport viewPort, PlayerIndex playerIndex, CombatScene currentCombatScene)
        {
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Handle player movement.
            player.PlayerMovementInCombat(viewPort, playerIndex, this.computerPlayerProfile, currentCombatScene);
            this.computerPlayerProfile.PlayerMovementInCombat(viewPort, playerIndex, player, currentCombatScene);

        } // End of the PlayerInputInCombat method.

        /// <summary>
        /// Update on the board.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="currentMap"></param>
        public override void UpdateOnBoard(GameTime gameTime, Map currentMap)
        {
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Update the human player profile
            player.UpdateOnBoard(gameTime, currentMap);

            // Update the computer player profile.
            this.computerPlayerProfile.UpdateOnBoard(gameTime, currentMap);

        } // End of the UpdateOnBoardMethod

        /// <summary>
        /// Update combat units in a combat.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void UpdateInCombat(GameTime gameTime, CombatScene combatScene)
        {
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Check for a collision between shots and player.
            player.CollisionCheckShotsAndPlayer(this.computerPlayerProfile);
            this.computerPlayerProfile.CollisionCheckShotsAndPlayer(player);

            // Update the human player profile
            player.combatUnitInCombat.UpdateInCombat(gameTime, combatScene);

            // Update the computer player profile.
            this.computerPlayerProfile.combatUnitInCombat.UpdateInCombat(gameTime, combatScene);

            // Handle power ups.
            PowerUpManagement(gameTime, combatScene);

        } // End of the UpdateInCombat method.

        /// <summary>
        /// Draw on the board.
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="currentMap"></param>
        public override void DrawOnBoard(SpriteBatch spriteBatch, SpriteFont font, Map currentMap)
        {

            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Draw the player profile
            player.DrawOnBoard(spriteBatch, font, currentMap);

            // Draw the computer player
            this.computerPlayerProfile.DrawOnBoard(spriteBatch, font, currentMap);

            // Draw left gamer tag.
            spriteBatch.Draw(this.blueCircleTexture, new Vector2(currentMap.xOffset - 2, currentMap.yOffset - 28), new Rectangle(0, 0, 32, 32), Color.White, 0, Vector2.Zero, 0.7f, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, leftGamerTag, new Vector2(currentMap.xOffset + 26, currentMap.yOffset - 30), Color.DarkGray, 0, Vector2.Zero, 1.1f, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, leftGamerTag, new Vector2(currentMap.xOffset + 25, currentMap.yOffset - 31), Color.White, 0, Vector2.Zero, 1.1f, SpriteEffects.None, 0);

            // Draw right gamer tag.
            spriteBatch.Draw(this.redCircleTexture, new Vector2(currentMap.xOffset + 503, currentMap.yOffset - 28), new Rectangle(0, 0, 32, 32), Color.White, 0, Vector2.Zero, 0.7f, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, rightGamerTag, new Vector2(currentMap.xOffset + 499 - font.MeasureString(rightGamerTag).X * 1.1f, currentMap.yOffset - 30), Color.DarkGray, 0, Vector2.Zero, 1.1f, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, rightGamerTag, new Vector2(currentMap.xOffset + 498 - font.MeasureString(rightGamerTag).X * 1.1f, currentMap.yOffset - 31), Color.White, 0, Vector2.Zero, 1.1f, SpriteEffects.None, 0);

        } // End of the DrawOnBoard method.

        /// <summary>
        /// Draw combat units in a combat.
        /// </summary>
        /// <param name="spriteBatch"></param>
        public override void DrawInCombat(SpriteBatch spriteBatch)
        {
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Draw power ups.
            foreach (PowerUp powerUp in this.powerUps)
            {
                powerUp.Draw(spriteBatch);
            }

            // Draw the player profile
            player.DrawInCombat(spriteBatch);

            // Draw the computer player
            this.computerPlayerProfile.DrawInCombat(spriteBatch);

        } // End of the DrawInCombat method.

        /// <summary>
        /// Check if there is a collision between combat units.
        /// </summary>
        /// <returns></returns>
        public override bool CollisionBetweenCombatUnits()
        {
            // A flag to mark if there is a collision.
            bool collision = false;

            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Loop through each combat unit.
            foreach (CombatUnit combatUnit in player.team.combatUnits)
            {
                foreach (CombatUnit otherCombatUnit in this.computerPlayerProfile.team.combatUnits)
                {
                    if (combatUnit.boardCollisionRectangle.Intersects(otherCombatUnit.boardCollisionRectangle))
                    {
                        // There is a collision.
                        collision = true;

                        // Set combatUnits for player profiles.
                        player.combatUnitInCombat = combatUnit;
                        this.computerPlayerProfile.combatUnitInCombat = otherCombatUnit;

                        // Set the startpositions for combat units
                        player.combatUnitInCombat.combatPosition = player.combatUnitInCombat.combatStartPosition;
                        this.computerPlayerProfile.combatUnitInCombat.combatPosition = this.computerPlayerProfile.combatUnitInCombat.combatStartPosition;

                    }
                }
            }

            // Return the collision variable.
            return collision;

        } // End of the collision between combat units method.

        /// <summary>
        /// This method is used to check if a combat has ended.
        /// </summary>
        /// <returns></returns>
        public override bool CheckIfCombatHasEnded()
        {
            bool hasCombatEnded = false;

            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Check if the human combat unit is dead.
            if (player.combatUnitInCombat.health <= 0)
            {
                // Remove the combat unit
                player.team.combatUnits.Remove(player.combatUnitInCombat);

                // Reset the rotation and remove shots for the surviving combat unit.
                this.computerPlayerProfile.combatUnitInCombat.rotation = 0;

                // Remove all shots.
                while (this.computerPlayerProfile.combatUnitInCombat.shots.Count > 0)
                {
                    this.computerPlayerProfile.combatUnitInCombat.shots.RemoveAt(0);
                }

                // Set the mark that the combat has ended.
                hasCombatEnded = true;
            }

            // Check if the computer combat unit is dead.
            if (this.computerPlayerProfile.combatUnitInCombat.health <= 0)
            {
                // Remove the combat unit
                this.computerPlayerProfile.team.combatUnits.Remove(this.computerPlayerProfile.combatUnitInCombat);

                // Reset the rotation and remove shots for the surviving combat unit.
                player.combatUnitInCombat.rotation = 0;
                player.combatUnitInCombat.combatDirection = Vector2.Zero;

                // Remove all shots.
                while (player.combatUnitInCombat.shots.Count > 0)
                {
                    player.combatUnitInCombat.shots.RemoveAt(0);
                }

                // Set the mark.
                hasCombatEnded = true;
            }

            // Return a value indicating if the combat has ended.
            return hasCombatEnded;

        } // End of the CheckIfCombatHasEnded.

        /// <summary>
        /// Check if a game has ended.
        /// </summary>
        public override void CheckIfGameHasEnded()
        {
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Check if the human player profile is dead.
            if (player.team.combatUnits.Count == 0)
            {
                this.winningPlayer = this.computerPlayerProfile.playerName;
                this.hasGameEnded = true;
            }

            // Check if the computer player profile is dead.
            if (this.computerPlayerProfile.team.combatUnits.Count == 0)
            {
                this.winningPlayer = player.playerName;
                this.hasGameEnded = true;
            }

        } // End of the CheckIfGamHasEnded method.

        /// <summary>
        /// This method handles power ups.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="currentCombatScene"></param>
        public override void PowerUpManagement(GameTime gameTime, CombatScene currentCombatScene)
        {
            // Look up the player profile belonging to this local gamer.
            HumanPlayerProfile player = singleHumanGamer.Tag as HumanPlayerProfile;

            // Check for a collision with a power up.
            for (int i = 0; i < this.powerUps.Count; i++)
            {
                // Check for a collision with a power up.
                if (this.powerUps[i].CheckForCollision(player.combatUnitInCombat) == true)
                {
                    // Add the power up to the player.
                    if (this.powerUps[i].isDurationInfinite == false)
                    {
                        this.powerUps[i].durationCounter = this.powerUps[i].durationFrames;
                        player.combatUnitInCombat.powerUps.Add(this.powerUps[i]);

                    }

                    // Play a collision sound.
                    Program.soundBank.PlayCue("powerUpCollection");

                    // Remove the power up.
                    this.powerUps.RemoveAt(i);
                }
                else if (this.powerUps[i].CheckForCollision(this.computerPlayerProfile.combatUnitInCombat) == true)
                {
                    // Make a mark that the player has a power up.
                    if (this.powerUps[i].isDurationInfinite == false)
                    {
                        this.powerUps[i].durationCounter = this.powerUps[i].durationFrames;
                        this.computerPlayerProfile.combatUnitInCombat.powerUps.Add(this.powerUps[i]);
                    }

                    // Play a collision sound.
                    Program.soundBank.PlayCue("powerUpCollection");

                    // Remove the power up.
                    this.powerUps.RemoveAt(i);
                }
            }

            // Check if we should reset a power up for the human player.
            for (int i = 0; i < player.combatUnitInCombat.powerUps.Count; i++)
            {
                if (player.combatUnitInCombat.powerUps[i].durationCounter <= 0)
                {
                    // Reset the power up.
                    player.combatUnitInCombat.powerUps[i].ResetPowerUp(player.combatUnitInCombat);

                    // Remove the power up.
                    player.combatUnitInCombat.powerUps.RemoveAt(i);

                }
                else
                {
                    player.combatUnitInCombat.powerUps[i].durationCounter--;
                }
            }

            // Check if we should reset a power up for the computer profile.
            for (int i = 0; i < this.computerPlayerProfile.combatUnitInCombat.powerUps.Count; i++)
            {
                if (this.computerPlayerProfile.combatUnitInCombat.powerUps[i].durationCounter <= 0)
                {
                    // Reset the power up.
                    this.computerPlayerProfile.combatUnitInCombat.powerUps[i].ResetPowerUp(this.computerPlayerProfile.combatUnitInCombat);

                    // Remove the power up.
                    this.computerPlayerProfile.combatUnitInCombat.powerUps.RemoveAt(i);

                }
                else
                {
                    this.computerPlayerProfile.combatUnitInCombat.powerUps[i].durationCounter--;
                }
            }

            // Check if we should add a power up.
            if (this.powerUpCounter >= 30000)
            {
                // Get a random value between 0 and 1.
                int randomValue = (int)(this.rnd.NextDouble() * 5);

                // Get a random x value between 
                int xValue = (int)(this.rnd.NextDouble() * (currentCombatScene.combatSceneRectangle.Width - 32)) + (currentCombatScene.combatSceneRectangle.Left + 16);
                int yValue = (int)(this.rnd.NextDouble() * (currentCombatScene.combatSceneRectangle.Height - 32)) + (currentCombatScene.combatSceneRectangle.Top + 16);

                // Create a new Vector 2
                Vector2 powerUpPosition = new Vector2(xValue, yValue);

                // Add the power up
                if (randomValue == 0)
                    this.powerUps.Add(new HealthPowerUp(powerUpPosition, 100, this.health100PowerUpTexture));
                else if (randomValue == 1)
                    this.powerUps.Add(new DamagePowerUp(powerUpPosition, 2, this.damagePowerUpTexture));
                else if (randomValue == 2)
                    this.powerUps.Add(new ShotSpeedPowerUp(powerUpPosition, 2, this.shotPowerUpTexture));
                else if (randomValue == 3)
                    this.powerUps.Add(new ShotDelayPowerUp(powerUpPosition, 2, this.shotDelayPowerUpTexture));
                else if (randomValue == 4)
                    this.powerUps.Add(new HealthPowerUp(powerUpPosition, 200,  this.health200PowerUpTexture));

                // Reset the powerUpCounter to 0
                this.powerUpCounter = 0;

            }
            else
            {
                this.powerUpCounter += gameTime.ElapsedGameTime.Milliseconds;
            }

        } // End of the PowerUpManagement method.

        #endregion

    } // End of class

} // End of namespace
