using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace Sokoban3D.Scenes
{
    /// <summary>
    /// Describes the movable camera class for the game.
    /// </summary>
    public class Camera
    {
        // How close object can be to be seen.
        const float nearPlane = 1.0f;
        // How far object can be to be seen.
        const float farPlane = 1000.0f;
        // How far camera can zoom out.
        const float maxZoom = 76.0f;
        // How close camera can zoom in.
        const float minZoom = 2.0f;

        // Variables for holdin current and original 
        // positions for the camera. And also the current
        // and original direction the camera faces.
        private Vector3 cameraPosition;
        private Vector3 originalCameraPosition;
        private Vector3 cameraDirection;
        private Vector3 originalCameraDirection;
        private Vector3 cameraUp;
        private Matrix cameraProjection;
        private Matrix cameraView;
        private float rotation;
        private float aspectRatio;

        public Vector3 position
        {
            get { return this.cameraPosition; }
        }

        public Matrix projection
        {
            get { return this.cameraProjection; }            
        }
        public Matrix view
        {
            get { return this.cameraView; }
        }

        /// <summary>
        /// Initializes the camera class. It gets game object to calculate aspect ratio
        /// and uses position and target vertors to aling the camera position and target.
        /// lastly will give vector describing where is up for the camera.
        /// </summary>
        /// <param name="game">Sokoban3D object</param>
        /// <param name="position">Camera position.</param>
        /// <param name="target">Point where camera points to.</param>
        /// <param name="up">Direction of up for the camera.</param>
        public Camera(Sokoban3D game, Vector3 position, 
            Vector3 target, Vector3 up)
        {
            this.cameraPosition = position;
            this.originalCameraPosition = this.cameraPosition;
            this.cameraDirection = target - this.cameraPosition;
            this.originalCameraDirection = this.cameraDirection;
            this.cameraUp = up;
            this.rotation = 0;

            this.aspectRatio = (float)game.Window.ClientBounds.Width /
                (float)game.Window.ClientBounds.Height;            

            this.cameraView = Matrix.CreateLookAt(this.cameraPosition, 
                this.cameraPosition+this.cameraDirection, this.cameraUp);
            this.cameraProjection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4, this.aspectRatio, nearPlane, farPlane);            
        }

        /// <summary>
        /// Resets the camera in original position over the gameboard.
        /// </summary>
        public void reset()
        {
            this.cameraPosition = this.originalCameraPosition;
            this.cameraDirection = this.originalCameraDirection;
            this.cameraView = Matrix.CreateLookAt(this.cameraPosition,
                this.cameraPosition + this.cameraDirection, this.cameraUp);
            this.cameraProjection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4, this.aspectRatio, nearPlane, farPlane);
            this.rotation = 0;
        }

        /// <summary>
        /// Zooms the camera out for small amount. 
        /// </summary>
        public void zoomOut()
        {
            Vector3 dirnormal = this.cameraDirection;
            dirnormal.Normalize();
            this.cameraPosition -= dirnormal * 1.002f;
            if (this.cameraPosition.Y < -Camera.maxZoom)
                this.cameraPosition.Y = -Camera.maxZoom;
            if (this.cameraPosition.Z > Camera.maxZoom)
                this.cameraPosition.Z = Camera.maxZoom;
            this.cameraView = Matrix.CreateLookAt(this.cameraPosition,
                this.cameraPosition + this.cameraDirection, this.cameraUp);
            this.cameraView = Matrix.CreateRotationZ(rotation) * this.cameraView;
        }

        /// <summary>
        /// Zooms the camera in for small amount.
        /// </summary>
        public void zoomIn()
        {
            Vector3 dirnormal = this.cameraDirection;
            dirnormal.Normalize();
            this.cameraPosition += dirnormal * 1.002f;
            if (this.cameraPosition.Y > -Camera.minZoom)
                this.cameraPosition.Y = -Camera.minZoom;
            if (this.cameraPosition.Z < Camera.minZoom)
                this.cameraPosition.Z = Camera.minZoom;
            this.cameraView = Matrix.CreateLookAt(this.cameraPosition,
                this.cameraPosition + this.cameraDirection, this.cameraUp);
            this.cameraView = Matrix.CreateRotationZ(rotation) * this.cameraView;
        }

        /// <summary>
        /// Rotate camera around the origo of the scene.
        /// </summary>
        /// <param name="rotation">Amount of the rotation in radians.</param>
        public void rotate(float rotation)
        {
            this.rotation += rotation;
            this.cameraView = Matrix.CreateLookAt(this.cameraPosition,
                this.cameraPosition + this.cameraDirection, this.cameraUp);
            this.cameraView = Matrix.CreateRotationZ(this.rotation) * this.cameraView;
        }

    }
}
