using System;
using System.Collections.Generic;
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.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Ika.Graphics
{
    /// <summary>
    /// A generic 3D camera
    /// </summary>
    public abstract class Camera
    {
        /// <summary>
        /// Vectors defining the view
        /// </summary>
        protected Vector3 m_eye, m_target, m_up;

        /// <summary>
        /// The view matrix
        /// </summary>
        protected Matrix m_ViewMatrix;

        /// <summary>
        /// The projection matrix
        /// </summary>
        protected Matrix m_ProjectionMatrix;

        /// <summary>
        /// A controler for the camera movement
        /// </summary>
        protected CameraControler m_controler;

        /// <summary>
        /// The projection parameters
        /// </summary>
        protected float m_fieldOfView, m_aspectRatio, m_nearDistance, m_farDistance;

        /// <summary>
        /// Obtain a controller for the camera
        /// </summary>
        public CameraControler Controler
        {
            get 
            {
                if (m_controler == null)
                    m_controler = CreateControler();
                return m_controler; 
            }
            set { m_controler = value; }
        }

        /// <summary>
        /// Gets the camera point of view
        /// </summary>
        public Vector3 Eye
        {
            get { return m_eye; }
            protected set { m_eye = value; CreateViewMatrix(); }
        }

        /// <summary>
        /// Gets the camera target
        /// </summary>
        public Vector3 Target
        {
            get { return m_target; }
            protected set { m_target = value; CreateViewMatrix(); }
        }

        /// <summary>
        /// Gets the camera up vector
        /// </summary>
        public Vector3 Up
        {
            get { return m_up; }
            protected set { m_up = value; CreateViewMatrix(); }
        }

        /// <summary>
        /// Gets the view matrix associated to the camera
        /// </summary>
        public Matrix ViewMatrix
        {
            get { return m_ViewMatrix; }
        }

        /// <summary>
        /// Gets the projection matrix associated to the camera
        /// </summary>
        public Matrix ProjectionMatrix
        {
            get { return m_ProjectionMatrix; }
            protected set { m_ProjectionMatrix = value; }
        }

        /// <summary>
        /// Gets the view frustrum
        /// </summary>
        BoundingFrustum m_frustrum;
        public BoundingFrustum Frustrum
        {
            get { return m_frustrum; }
            protected set { m_frustrum = value; }
        }

        /// <summary>
        /// Create the view matrix from eye, target and up vectors
        /// </summary>
        private void CreateViewMatrix()
        {
            m_ViewMatrix = Matrix.CreateLookAt(m_eye, m_target, m_up);
            m_frustrum = new BoundingFrustum(ViewMatrix * ProjectionMatrix);
        }

        /// <summary>
        /// Set the camera point of interest
        /// </summary>
        /// <param name="point"></param>
        public abstract void SetPointOfInterest(Vector3 point);

        /// <summary>
        /// Change the camera projection matrix
        /// </summary>
        /// <param name="fieldOfView">The field of view</param>
        /// <param name="aspectRatio">The aspect ratio</param>
        /// <param name="nearDistance">The near plane distance</param>
        /// <param name="farDistance">The far plane distance</param>
        public void SetProjectionMatrix(float fieldOfView, float aspectRatio, float nearDistance, float farDistance)
        {
            m_fieldOfView = fieldOfView;
            m_aspectRatio = aspectRatio;
            m_nearDistance = nearDistance;
            m_farDistance = farDistance;

            ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(m_fieldOfView, m_aspectRatio, m_nearDistance, m_farDistance);
        }

        /// <summary>
        /// Change the camera projection matrix
        /// </summary> 
        /// <param name="aspectRatio">The aspect ratio</param>
        public void SetProjectionMatrix(float aspectRatio)
        {
            m_aspectRatio = aspectRatio;

            ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(m_fieldOfView, m_aspectRatio, m_nearDistance, m_farDistance);
        }

        /// <summary>
        /// Transform a vector from screen space to world space
        /// </summary>
        /// <param name="vector">The screen space vector</param>
        /// <returns>The world space vector</returns>
        public Vector3 ScreenToWorld(Vector3 vector)
        {
            Matrix ViewProjectionInv = Matrix.Invert(ViewMatrix * ProjectionMatrix);
            return Vector3.Transform(vector, ViewProjectionInv);
        }
        
        /// <summary>
        /// Provide a class-specific camera controler
        /// </summary>
        /// <returns>A camera controler</returns>
        protected abstract CameraControler CreateControler();

        protected Camera()
        {
            m_eye = Vector3.Zero;
            m_target = Vector3.UnitX;
            m_up = Vector3.UnitZ;

            CreateViewMatrix();
            SetProjectionMatrix(MathHelper.Pi / 4.0f, 4.0f / 3.0f, 1.0f, 10000);

            m_controler = null;
        }
    }
}
