using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Input;
using Primitives3D;

namespace FredriksdalAssault2
{
    public class DynamicResourcesManager
    {
        #region Variables

        // A list of bullets.
        List<Bullet> bullets;

        // Shot delay and shot countdown to slow down the shooting. 
        int shotDelay = 100;
        int shotCountdown = 0;

        // Is this player shooting
        public bool isShooting;

        // References to the content manager and the graphics device.
        GraphicsDevice myGraphicsDevice;
        NetworkSession networkSession;

        // Shot effects
        ShotParticles shotEffects;
        int shotEffectTime = 1000;
        int shotEffectCounter = -1;

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new dynamic resource manager.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="graphicsDevice"></param>
        /// <param name="networkSession"></param>
        public DynamicResourcesManager(ContentManager content, GraphicsDevice graphicsDevice, NetworkSession networkSession)
        {
            // Set reference variables.
            this.myGraphicsDevice = graphicsDevice;
            this.networkSession = networkSession;

            // Create new lists
            bullets = new List<Bullet>(300);

            // Load the shot effect generator
            this.shotEffects = new ShotParticles(content, graphicsDevice);

        }

        #endregion

        #region Methods

        /// <summary>
        /// Update common resources in the game.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="position"></param>
        /// <param name="direction"></param>
        /// <param name="lookUpDown"></param>
        public void Update(GameTime gameTime, IMap currentMap)
        {

            // Update each bullet
            foreach (Bullet bullet in bullets)
            {
                bullet.UpdateBullet(gameTime);
            }

            // Check for collision with static objects.
            CollisionWithStaticObjects(currentMap);

        } // End of the update method

        /// <summary>
        /// Draw dynamic resources in the game.
        /// </summary>
        /// <param name="view"></param>
        /// <param name="projection"></param>
        public void Draw(GameTime gameTime, Matrix view, Matrix projection)
        {

            // Draw shot effects
            if (shotEffectCounter >= 0)
            {
                this.shotEffects.DrawShotEffect(gameTime, view, projection);

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

        /// <summary>
        /// A method to add bullets to the game.
        /// </summary>
        /// <param name="gameTime"></param>
        public void FireBullets(GameTime gameTime, Vector3 position, Vector3 direction, Vector3 lookUpDown, 
            bool isDead, bool hasGameEnded, PlayerIndex playerIndex)
        {

            // Check if we should add a shot.
            if (Mouse.GetState().LeftButton == ButtonState.Pressed || GamePad.GetState(playerIndex).IsButtonDown(Buttons.RightTrigger))
            {
                // Check if we can fire a shot, not if the shotCountdown is greater than 0, not if the player is dead 
                // and not if the game has ended.
                if (shotCountdown <= 0 && !isDead && !hasGameEnded)
                {
                    // Add a shot to the list of shots. 3f
                    bullets.Add(new Bullet(position, direction + lookUpDown, 200f));

                    // Add a shot effect.
                    this.shotEffectCounter = this.shotEffectTime;
                    this.shotEffects.AddParticles(1, position, (direction + lookUpDown) * 200f);

                    // Reset the shot countdown
                    shotCountdown = shotDelay;
                    this.isShooting = true;

                }
                else
                {
                    // Subract game time from the shotCountdown counter
                    shotCountdown -= gameTime.ElapsedGameTime.Milliseconds;
                }
            }
            else
            {
                // Subract game time from the shotCountdown counter
                shotCountdown -= gameTime.ElapsedGameTime.Milliseconds;
                this.isShooting = false;
            }

        } // End of the FireBullets method.

        /// <summary>
        /// Remove bullets that collides with static objects.
        /// </summary>
        /// <param name="heightMapInfo"></param>
        /// <param name="modelManager"></param>
        public void CollisionWithStaticObjects(IMap currentMap)
        {
            // Loop through each bullet.
            for (int i = 0; i < bullets.Count; i++)
            { 

                // Remove the bullet if it is to high or to low
                if (bullets[i].bulletPosition.Y > 5000 || bullets[i].bulletPosition.Y < -5000)
                {
                    bullets.RemoveAt(i);
                }
                // Remove the bullet if it is outside the map (Z)
                else if (bullets[i].bulletPosition.Z > 8000 || bullets[i].bulletPosition.Z < -8000)
                {
                    bullets.RemoveAt(i);
                }
                // Remove the bullet if it is outside the map (X)
                else if (bullets[i].bulletPosition.X > 8000 || bullets[i].bulletPosition.X < -8000)
                {
                    bullets.RemoveAt(i);
                }
            }
        }

        /// <summary>
        /// Check if a player is killed by a local bullet.
        /// </summary>
        /// <param name="player"></param>
        public bool BulletCollisionOnClient(LocalNetworkGamer localGamer, PlayerProfile player, IMap currentMap)
        {
            // A variable to mark if there is a collision or not.
            bool collisionWithBullet = false;

            // For each gamer, check for collision with bullets if the player is alive.
            if (player.isDead == false)
            {
                for (int i = 0; i < bullets.Count; i++)
                {
                    // Check if there is a collision between player and bullets.
                    Vector3 position = bullets[i].bulletPosition;
                    Vector3 direction = bullets[i].bulletDirection;
                    direction.Normalize();

                    // Check for a collision with a house.
                    float? houseCollisionDistance = float.MaxValue;
                    houseCollisionDistance = currentMap.CollisionWithBuildings(new Ray(position, direction));

                    // 2 bounding spheres per player.
                    foreach (BoundingSphere sphere in player.soldier.boundingSpheres)
                    {

                        if (sphere.Intersects(new Ray(position, direction)) < houseCollisionDistance)
                        {
                            // indicate that there has been a collision with a bullet.
                            collisionWithBullet = true;

                            // Remove the killing bullet.
                            bullets.RemoveAt(i);

                            // Break from the loop because there was a hit.
                            break;

                        }
                    }

                    // Remove the bullet if it has collided with a house.
                    if(collisionWithBullet == false && houseCollisionDistance < float.MaxValue)
                        bullets.RemoveAt(i);

                }
            }

            // Return the boolean
            return collisionWithBullet;

        } // End of the BulletCollisionOnClient method

        #endregion

    } // End of class

} // End of namespace
