using System;
using System.Collections.Generic;
using System.Linq;
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.Input;
using Microsoft.Xna.Framework.Media;
using SkinnedModelLibrary;

namespace FredriksdalAssault2
{
    /// <summary>
    /// This is a class that represents the user controlled player.
    /// </summary>
    public class Camera
    {

        #region Variables

        // Variables for camera view and projection.
        public Matrix view;
        public Matrix projection;

        // Variables for Water
        public Matrix viewProj;

        // Variables for the view in the camera
        public Vector3 cameraPosition;
        public Vector3 cameraDirection;
        public Vector3 cameraLookUpDown;
        public Vector3 cameraUp;

        // Variables for camera speed.
        public float cameraSpeed;

        // Mouse support
        public MouseState prevMouseState;

        // A variable used to correct the camera position to the previous position.
        public Vector3 previousCameraPosition;

        // Player data
        public bool isAttacking;
        public Int16 numberOfKills = 0;
        public bool isDead = false;
        public byte killedByGamerId = 0;
        public int collectedBarrels = 0;
        public bool isWounded = false;

        // Reference variable to the content manager and the graphics device
        ContentManager myContentManager;
        GraphicsDevice myGraphicsDevice;

        // A player model and a weapon model.
        public Soldier playerModel;
        public Weapon playerWeapon;

        // Variable for camera height over the ground
        Vector3 heightOverGround;

        // Jump, run and zoom variables
        bool isJumping = false;
        bool jumpMax = false;
        bool inBuilding = false;
        float jumpHeight = 0;
        float jumpVelocity = 4f;
        public bool isZooming = false;
        float fieldOfView = MathHelper.ToRadians(60f);

        // Turn amounts
        float turnLeftRightPC;
        float turnUpDownPC;
        float turnLeftRightXBOX;
        float turnUpDownXBOX;

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new camera.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="position"></param>
        /// <param name="target"></param>
        /// <param name="up"></param>
        /// <param name="graphicsDevice"></param>
        public Camera(ContentManager content, Vector3 position, Vector3 direction, Vector3 up, GraphicsDevice graphicsDevice)
        {
            // Set the content manager and the graphics device
            this.myContentManager = content;
            this.myGraphicsDevice = graphicsDevice;

            // Create a new view matrix and make the view variable reference to it.
            this.cameraPosition = position;
            this.cameraDirection = direction;
            this.cameraLookUpDown = new Vector3(0, 0, 0);
            this.cameraUp = up;
            this.cameraSpeed = 4.0f;
            this.heightOverGround = new Vector3(0, 80, 0);

            // View times projection for Water
            viewProj = Matrix.Identity;

            // Set previous camera position
            this.previousCameraPosition = this.cameraPosition;

            // Call a method to create the view and projection matrix
            CreateLookAt();

            // Set zoom values
            ResetZoomValues();

            // Create a new player model
            playerModel = new Soldier(new Vector3(0, 8000, 0));
            playerWeapon = new Weapon(content);

            // Call intialize
            Initialize();
        }
        #endregion

        #region Methods
        
        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public void Initialize()
        {

#if !XBOX360
            // Set the mouse position and do initial get state
            Mouse.SetPosition(this.myGraphicsDevice.Viewport.Width / 2, this.myGraphicsDevice.Viewport.Height / 2);
            this.prevMouseState = Mouse.GetState();
#endif
            
        }

        /// <summary>
        /// Handle player input to update camera position, camera direction and player model.
        /// </summary>
        /// <param name="gameTime"></param>
        public void PlayerInput(GameTime gameTime, IMap currentMap, PlayerIndex playerIndex)
        {
            // Create variables for input states.
            KeyboardState keyboardState = Keyboard.GetState();
            GamePadState gamePadState = GamePad.GetState(playerIndex, GamePadDeadZone.Circular);

            // A variable to handle if keys or buttons have been pressed
            bool isPlayerWalking = false;

            // Check if the player is running
            if (keyboardState.IsKeyDown(Keys.LeftShift) || gamePadState.IsButtonDown(Buttons.LeftShoulder))
            {
                this.cameraSpeed = 8.0f;
            }
            else
            {
                this.cameraSpeed = 4.0f;
            }

            // Move forward and backward
            if (keyboardState.IsKeyDown(Keys.W) || gamePadState.ThumbSticks.Left.Y > 0.25f)
            {
                // Update camera position.
                this.cameraPosition += this.cameraDirection * this.cameraSpeed;

                // Set the player walking variable
                isPlayerWalking = true;

            }
            if (keyboardState.IsKeyDown(Keys.S) || gamePadState.ThumbSticks.Left.Y < -0.25f)
            {
                // Update camera position.
                this.cameraPosition -= this.cameraDirection * this.cameraSpeed;

                // Set the player walking variable
                isPlayerWalking = true;
            }

            // Move side to side
            if (keyboardState.IsKeyDown(Keys.A) || gamePadState.ThumbSticks.Left.X < -0.25f)
            {
                Vector3 cameraDirection = Vector3.Cross(this.cameraUp, this.cameraDirection);
                this.cameraPosition += cameraDirection * this.cameraSpeed;

                // Set the player walking variable
                isPlayerWalking = true;

            }
            if (keyboardState.IsKeyDown(Keys.D) || gamePadState.ThumbSticks.Left.X > 0.25f)
            {
                Vector3 cameraDirection = Vector3.Cross(this.cameraUp, this.cameraDirection);
                this.cameraPosition -= cameraDirection * this.cameraSpeed;

                // Set the player walking variable
                isPlayerWalking = true;
            }

            // Set the animation clip for the player model
            if (isPlayerWalking)
            {
                // Set the walking animation for the player.
                this.playerModel.StartClip(1);
            }
            else
            {
                // Set a standing pose for the player model.
                this.playerModel.StartClip(2);
            }

            // Check if we should make the left right turn faster on xbox
            if (this.isZooming == false)
            {
                if (gamePadState.IsButtonDown(Buttons.RightShoulder))
                    this.turnLeftRightXBOX = MathHelper.ToRadians(4f);
                else
                    this.turnLeftRightXBOX = MathHelper.ToRadians(0.9f);
            }

            // Check if the player are zooming
            if (Mouse.GetState().RightButton == ButtonState.Pressed || gamePadState.IsButtonDown(Buttons.LeftTrigger))
            {
                if (this.isZooming == false)
                {
                    // Set zoom values
                    this.fieldOfView = MathHelper.ToRadians(10f);
                    this.turnLeftRightPC = MathHelper.ToRadians(0.1f);
                    this.turnUpDownPC = MathHelper.ToRadians(0.5f);
                    this.turnLeftRightXBOX = MathHelper.ToRadians(0.1f);
                    this.turnUpDownXBOX = MathHelper.ToRadians(0.1f);

                    this.isZooming = true;
                }
            }
            else
            {
                if (this.isZooming == true)
                {

                    this.isZooming = false;
                    ResetZoomValues();         
                }
            }

            // Variable for rotation amount and up down amount, should be set by mouse or gamepad
            float rotationAmount;
            float upDownAmount;

#if (!XBOX360)
            // Look to the left and to the right (yaw)
            if (Program.invertedAxisX[(byte)playerIndex] == false)
                rotationAmount = (-this.turnLeftRightPC) * (Mouse.GetState().X - this.prevMouseState.X);
            else
                rotationAmount = (this.turnLeftRightPC) * (Mouse.GetState().X - this.prevMouseState.X);

            // Look up and down (pitch)
            if (Program.invertedAxisY[(byte)playerIndex] == false)
                upDownAmount = (this.turnUpDownPC) * (Mouse.GetState().Y - this.prevMouseState.Y);
            else
                upDownAmount = (-this.turnUpDownPC) * (Mouse.GetState().Y - this.prevMouseState.Y);
#endif

#if XBOX360
            // Gamepad input (look to the left and the right.
            if (Program.invertedAxisX[(int)playerIndex] == false)
                rotationAmount = (-this.turnLeftRightXBOX) * (gamePadState.ThumbSticks.Right.X);
            else
                rotationAmount = (this.turnLeftRightXBOX) * (gamePadState.ThumbSticks.Right.X);

            // Gamepad input (look up and down).
            if (Program.invertedAxisY[(int)playerIndex] == false)
                upDownAmount = (-this.turnUpDownXBOX) * (gamePadState.ThumbSticks.Right.Y);
            else
                upDownAmount = (this.turnUpDownXBOX) * (gamePadState.ThumbSticks.Right.Y);
#endif

            // Set camera direction and up down position.
            this.cameraDirection = Vector3.Transform(this.cameraDirection, Matrix.CreateFromAxisAngle(this.cameraUp, rotationAmount));

            // If the Y value for look up and down is less then 40 or greater than -40
            if (this.cameraLookUpDown.Y <= MathHelper.ToRadians(40) && this.cameraLookUpDown.Y >= MathHelper.ToRadians(-40))
                this.cameraLookUpDown -= new Vector3(0, upDownAmount, 0);
            else if (this.cameraLookUpDown.Y > MathHelper.ToRadians(40))
                this.cameraLookUpDown = new Vector3(0, MathHelper.ToRadians(40), 0);
            else if (this.cameraLookUpDown.Y < MathHelper.ToRadians(-40))
                this.cameraLookUpDown = new Vector3(0, MathHelper.ToRadians(-40), 0);

            // Adjust the camera position so that it is on the heigthmap
            Vector3 newCameraPosition = this.cameraPosition;
            if (currentMap.heightMapInfo.IsOnHeightmap(newCameraPosition))
            {
                Vector3 normal;
                currentMap.heightMapInfo.GetHeightAndNormal(newCameraPosition, out newCameraPosition.Y, out normal);

                // Get a bounding box for a collision check with houses.
                CustomBoundingBox collisionCheck = currentMap.CollisionWithBuildings(new BoundingBox(new Vector3(this.cameraPosition.X - 10, this.cameraPosition.Y - this.heightOverGround.Y - this.jumpHeight, this.cameraPosition.Z - 10)
                    , new Vector3(this.cameraPosition.X + 10, this.cameraPosition.Y, this.cameraPosition.Z + 10)));

                if (collisionCheck.name != "none")
                {
                    if (collisionCheck.name == "wall")
                    {
                        // Set a bounce rate
                        float bounceRate = 1f;

                        // The camera to bounce back from the wall. This means that the player can move along walls
                        if (this.previousCameraPosition.X < this.cameraPosition.X)
                        {
                            if (this.previousCameraPosition.Z < this.cameraPosition.Z)
                                this.cameraPosition = this.previousCameraPosition + new Vector3(-bounceRate, 0, -bounceRate);
                            else
                                this.cameraPosition = this.previousCameraPosition + new Vector3(-bounceRate, 0, bounceRate);
                        }
                        else
                        {
                            if (this.previousCameraPosition.Z < this.cameraPosition.Z)
                                this.cameraPosition = this.previousCameraPosition + new Vector3(bounceRate, 0, -bounceRate);
                            else
                                this.cameraPosition = this.previousCameraPosition + new Vector3(bounceRate, 0, bounceRate);
                        }
 
                    }
                    else if (collisionCheck.name == "floor" || collisionCheck.name == "roof")
                    {
                        this.cameraPosition = new Vector3(newCameraPosition.X, collisionCheck.boundingBox.Max.Y, newCameraPosition.Z) + this.heightOverGround;

                        // Set the previous camera position
                        this.previousCameraPosition = this.cameraPosition;
                    }

                    // No jumping in buildings :-)
                    this.inBuilding = true;
                }
                else
                {
                    // Set the position for the camera.
                    this.cameraPosition = newCameraPosition + this.heightOverGround;

                    // Set the previous camera position
                    this.previousCameraPosition = this.cameraPosition;

                    // Not in a building
                    this.inBuilding = false;
                }
            }
            else
            {
                this.cameraPosition = this.previousCameraPosition;
            }

#if (!XBOX360)

            // Reset the mouse to the middle of the screen
            Mouse.SetPosition(this.myGraphicsDevice.Viewport.Width / 2, this.myGraphicsDevice.Viewport.Height / 2);

            // Reset previous mouse state
            this.prevMouseState = Mouse.GetState();

#endif

            // Check for a jump.
            if (this.isJumping == false && this.inBuilding == false && (keyboardState.IsKeyDown(Keys.Space) || gamePadState.IsButtonDown(Buttons.A)))
            {
                this.isJumping = true;
            }

            // Make the jump
            if (this.isJumping == true)
            {
                // Go up or down.
                if (this.jumpMax == true)
                {
                    this.jumpHeight -= this.jumpVelocity;
                    this.jumpVelocity *= 1.11f;
                }
                else if (this.jumpMax == false)
                {
                    this.jumpHeight += this.jumpVelocity;
                    this.jumpVelocity *= 0.90f;
                }

                // Set the flag for a maximum jump.
                if (this.jumpHeight >= 30)
                {
                    this.jumpMax = true;
                }
                else if (this.jumpHeight <= 0)
                {
                    this.jumpMax = false;
                    this.isJumping = false;
                    this.jumpHeight = 0;
                    this.jumpVelocity = 4f;
                }
            }

            // Set the camera position to adjust for the jump
            this.cameraPosition += new Vector3(0, this.jumpHeight, 0);

            // Adjustments to the player model (is done last so that we donīt need to adjust it to the heightmap
            this.playerModel.UpdateSoldierPositionRotation(this.cameraPosition - this.heightOverGround, rotationAmount, new Vector3(0, 0, 0));

            // Rebuild the camera
            CreateLookAt();

        } // End of the PlayerInput method

        /// <summary>
        /// This method will draw a player weapon
        /// </summary>
        public void DrawPlayerWeapon()
        {
            this.playerWeapon.DrawWeapon(this.view, this.projection);

        } // End of the draw method.

        /// <summary>
        /// A method to create a new view matrix for the cameras view.
        /// </summary>
        public void CreateLookAt()
        {
            // Create a new view matrix.
            this.view = Matrix.CreateLookAt(this.cameraPosition, this.cameraPosition + this.cameraLookUpDown + this.cameraDirection, 
                this.cameraUp);

            // Create a new perspective projection matrix and make the projection variable reference to it
            this.projection = Matrix.CreatePerspectiveFieldOfView(this.fieldOfView, (float)this.myGraphicsDevice.Viewport.Width /
                (float)this.myGraphicsDevice.Viewport.Height, 1, 10000); // 10000

            // For water
            viewProj = view * projection;

        }

        /// <summary>
        /// A helper method to reset zoom values.
        /// </summary>
        private void ResetZoomValues()
        {
            // Reset values zoom values.
            this.fieldOfView = MathHelper.ToRadians(60f);
            this.turnLeftRightPC = MathHelper.ToRadians(0.3f);
            this.turnUpDownPC = MathHelper.ToRadians(1f);
            this.turnLeftRightXBOX = MathHelper.ToRadians(0.9f);
            this.turnUpDownXBOX = MathHelper.ToRadians(1f);

        } // End of the ResetZoomValues method
       
        #endregion

    } // End of class

} // End of namespace
