#region Using
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace GhostArena3D
{
   class Camera
   {
      #region Member Variables

      private Vector3 camPos;
      private Vector3 camLookAt;
      private Vector3 camUp;
      private Vector3 camViewDir;
      private Vector3 camRight;
      
      private Matrix projectionMatrix;
      
      #endregion

      #region Initialize

      public Camera()
      {
         camPos = Vector3.Zero;
         camLookAt = -Vector3.UnitZ;
         camUp = Vector3.UnitY;
         RecalculateViewVec();
         RecalculateRightVec();
         
         projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45.0f), 1.33f, 1.0f, 10000.0f);
      }
      
      
      public Camera(Vector3 position, Vector3 lookingAt, Vector3 up)
      {
         camPos = position;
         camLookAt = lookingAt;
         camUp = up;
         RecalculateViewVec();
         RecalculateRightVec();
         
         projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45.0f), 1.33f, 1.0f, 10000.0f);
      }
      
      
      public Camera(Vector3 position, Vector3 lookingAt, Vector3 up,
                    float fov, float aspectRatio,
                    float near, float far)
      {
         camPos = position;
         camLookAt = lookingAt;
         camUp = up;
         RecalculateViewVec();
         RecalculateRightVec();
         
         projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
            fov, aspectRatio, near, far);
      }
      
      #endregion
      
      #region Properties
      
      public Vector3 Position
      {
         get { return camPos; }
         set { camPos = value; }
      }
      
      
      public Vector3 LookAt
      {
         get { return camLookAt; }
         set { camLookAt = value; }
      }
      
      
      public Vector3 Up
      {
         get { return camUp; }
         set { camUp = value; }
      }
      
      
      public Matrix LookAtMatrix
      {
         get
         {
            return Matrix.CreateLookAt(camPos, camLookAt, camUp);
         }
      }
      
      
      public Matrix ProjectionMatrix
      {
         get
         {
            return projectionMatrix;
         }
         set
         {
            projectionMatrix = value;
         }
      }
      
      #endregion
      
      #region Camera Movement
      
      public void MoveCamera(Vector3 direction, float speed)
      {
	      camPos += direction * speed;
	      camLookAt += direction * speed;
      }
      
      
      public void MoveForwardXZ(float speed)
      {
	      MoveCamera(camViewDir, speed);
      }
      
      
      public void MoveBackwardXZ(float speed)
      {
	      MoveCamera(camViewDir * -1, speed);
      }
      
      
      public void MoveLeftXZ(float speed)
      {
         MoveCamera(camRight * -1, speed);
      }
      
      
      public void MoveRightXZ(float speed)
      {
         MoveCamera(camRight, speed);
      }
      
      
      public void RotateCamera(float angle, Vector3 axis)
      {
	      Quaternion qRotation, qView, qNewView, tempQRot;
	      
	      qRotation = Quaternion.CreateFromAxisAngle(axis, angle);
	      
	      qView = new Quaternion();
	      
         qView.X = camLookAt.X - camPos.X;
         qView.Y = camLookAt.Y - camPos.Y;
         qView.Z = camLookAt.Z - camPos.Z;
         qView.W = 0.0f;

         tempQRot = qRotation;
         tempQRot.Conjugate();

         qNewView = Quaternion.Multiply(Quaternion.Multiply(qRotation, qView), tempQRot);

         camLookAt.X = camPos.X + qNewView.X;
         camLookAt.Y = camPos.Y + qNewView.Y;
         camLookAt.Z = camPos.Z + qNewView.Z;
         
         RecalculateViewVec();
         RecalculateRightVec();
      }
      
      
      public void RotateCamera(Vector2 inputPosition)
      {
         Vector3 axis;
        
         axis = Vector3.Cross(camViewDir, camUp);
         axis.Normalize();

         RotateCamera(inputPosition.Y, axis);
         RotateCamera(-inputPosition.X, Vector3.UnitY);
      }
      
      #endregion
      
      #region Helper Functions
      
      private void RecalculateViewVec()
      {
         camViewDir = new Vector3(camLookAt.X - camPos.X,
                                  0.0f,
                                  camLookAt.Z - camPos.Z);

         camViewDir.Normalize();
      }
      
      
      private void RecalculateRightVec()
      {
         Vector3 viewDir = new Vector3(camLookAt.X - camPos.X,
                                       camLookAt.Y - camPos.Y,
                                       camLookAt.Z - camPos.Z);

         camRight = Vector3.Cross(camUp, viewDir * -1);
      }
      
      #endregion
   }
}
