using Microsoft.Xna.Framework;
using System.ComponentModel;

namespace Innovation
{
    // Basic Camera class
    public class Camera : Component, I3DComponent
    {
        // Internal values
        Vector3 position = Vector3.Zero;
        Matrix rotationMatrix = Matrix.Identity;
        Vector3 target = new Vector3(0, 0, -1);
        Vector3 up = Vector3.Up;
        Matrix view;
        Matrix projection;

        // The point the camera is looking at
        [Category("Point Camera")]
        public virtual Vector3 Target
        { get { return target; } set { target = value; } }

        // The View and Projection matrices commonly used for rendering
        [Browsable(false)]
        public virtual Matrix View
        { get { return view; } set { view = value; } }
        [Browsable(false)]
        public virtual Matrix Projection
        { get { return projection; } set { projection = value; } }
        [Browsable(false)]
        public virtual Vector3 Up
        { get { return up; } set { up = value; } }

        // Public I3DComponent values
        [Category("3D Component")]
        public virtual Vector3 Position { get { return position; } set { position = value; } }
        [Category("3D Component")]
        public virtual Vector3 Scale { get { return Vector3.One; } set { } }
        [Category("3D Component")]
        [DisplayName("Rotation")]
        public Vector3 EulerRotation
        {
            get { return MathUtil.MatrixToVector3(Rotation); }
            set { Rotation = MathUtil.Vector3ToMatrix(value); }
        }
        // The rotation matrix used by the camera and the current up vector
        [Browsable(false)]
        public virtual Matrix Rotation
        { get { return rotationMatrix; } set { rotationMatrix = value; } }
        [Browsable(false)]
        public virtual BoundingBox BoundingBox
        { get { return new BoundingBox(Position - Vector3.One, Position + Vector3.One); } }

        // Constructors
        public Camera() : base() { }

        // Update the camera
        public override void Update()
        {
            // Calculate the direction from the position to the target, and normalize
            Vector3 newForward = Target - Position;
            newForward.Normalize();

            // Set the rotation matrix's forward to this vector
            Matrix rotationMatrixCopy = this.Rotation;
            rotationMatrixCopy.Forward = newForward;

            // Save a copy of "Up" (0, 1, 0)
            Vector3 referenceVector = Vector3.Up;

            // On the slim chance that the camera is pointed perfectly parallel with
            // the Y Axis, we cannot use cross product with a parallel axis, so we
            // change the reference vector to the forward axis (Z).
            if (rotationMatrixCopy.Forward.Y == referenceVector.Y
                || rotationMatrixCopy.Forward.Y == -referenceVector.Y)
                referenceVector = Vector3.Backward;

            // Calculate the other parts of the rotation matrix
            rotationMatrixCopy.Right = Vector3.Cross(this.Rotation.Forward,
                referenceVector);
            rotationMatrixCopy.Up = Vector3.Cross(this.Rotation.Right,
                this.Rotation.Forward);

            this.Rotation = rotationMatrixCopy;

            // Use the rotation matrix to find the new up
            Up = Rotation.Up;

            // Recalculate View and Projection using the new Position, Target, and Up
            View = Matrix.CreateLookAt(Position, Target, Up);
            Projection = MathUtil.CreateProjectionMatrix();
        }
    }
}