#region File Description
//-----------------------------------------------------------------------------
// CollisionCameraPerson.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace BoxCollider
{
    // person camera (quake like camera)
    public class CollisionCameraPerson : CollisionCamera, IDisposable
    {
        Matrix transform;       // the person transform matrix (without up/down rot)
        Vector3 velocity;       // current velocity vector used only by gravity

        float headHeight;      // height from center of box to eye position
        float stepHeight;      // max height for step player can climb without jumping

        float gravity;          // gravity intensity
        bool onGround;         // is player on ground (false if in air)
        float jumpHeight;      // height player will reach when jumping

        float upDownRot;      // up/down view rotation
        float autoMoveY;      // distance to move in Y axis on next update
        // in order to climb up/down a step

        public CollisionCameraPerson(
            Vector3 position,
            Vector3 lookPosition,
            float angle,
            float aspect,
            float width,
            float height,
            float stepHeight,
            float headHeight,
            float upDownRot,
            float gravity,
            float jumpHeight )
            :
            base( position, lookPosition, angle, aspect )
        {
            width *= 0.5f;
            height *= 0.5f;

            this.stepHeight = stepHeight;
            this.headHeight = headHeight - height;
            this.upDownRot = upDownRot;
            this.gravity = gravity;
            this.jumpHeight = jumpHeight;

            transform = world;

            onGround = false;
            velocity = Vector3.Zero;

            box = new CollisionBox(
                        new Vector3( -width, -height + stepHeight, -width ),
                        new Vector3( width, height, width ) );
        }

        public override void Draw( GraphicsDevice gd )
        {
            box.min += world.Translation;
            box.max += world.Translation;
            box.min.Y -= headHeight;
            box.max.Y -= headHeight;

            box.Draw( gd );

            box.min -= world.Translation;
            box.max -= world.Translation;
            box.min.Y += headHeight;
            box.max.Y += headHeight;
        }

        public override void Reset( Matrix m )
        {
            // make sure matrix Y axis is (0,1,0)
            transform = m;
            if (transform.M22 < 0.9999f)
            {
                // rotate Y to (0,1,0)
                Vector3 axisY = new Vector3( transform.M21, transform.M22, transform.M23 );
                float ang = (float) Math.Acos( axisY.Y );
                Vector3 axis = Vector3.Normalize( Vector3.Cross( axisY, Vector3.UnitY ) );
                Vector3 pos = transform.Translation;
                transform.Translation = Vector3.Zero;
                transform = transform * Matrix.CreateFromAxisAngle( axis, ang );
                transform.Translation = pos;
            }
            upDownRot = 0.0f;
            world = transform;
            view = Matrix.Invert( world );
            frustum = new BoundingFrustum( view * projection );
        }

        public override void Update(
            TimeSpan elapsedTime,
            CollisionMesh collisionMesh,
            GamePadState gamepadState,
            KeyboardState keyboardState )
        {
            if (collisionMesh == null)
            {
                throw new ArgumentNullException( "collisionMesh" );
            }

            float timeSeconds = (float) elapsedTime.TotalSeconds;

            float speedBoost = gamepadState.Triggers.Left;
            if (keyboardState.IsKeyDown( Keys.LeftShift ))
                speedBoost = 1.0f;

            float rotSpeed = 2.0f * timeSeconds;
            float moveSpeed = ( 300.0f + 400.0f * speedBoost ) * timeSeconds;

            if (onGround == false)
                velocity.Y -= gravity * timeSeconds;
            else
            {
                if (gamepadState.Buttons.A == ButtonState.Pressed ||
                    keyboardState.IsKeyDown( Keys.Space ))
                {
                    velocity.Y = (float) Math.Sqrt( gravity * 2.0f * jumpHeight );
                    onGround = false;
                }
                else
                    velocity.Y = 0.0f;
            }

            Vector3 position = transform.Translation;

            Vector3 axisX = new Vector3( transform.M11, transform.M12, transform.M13 );
            Vector3 axisY = new Vector3( 0, 1, 0 );
            Vector3 axisZ = new Vector3( transform.M31, transform.M32, transform.M33 );

            Vector3 translate, rotate;
            GetInputVectors( gamepadState, keyboardState, out translate, out rotate );

            Vector3 newPosition = position;
            newPosition += axisX * ( moveSpeed * translate.X );
            newPosition -= axisZ * ( moveSpeed * translate.Z );
            newPosition += velocity * timeSeconds;

            float moveY = 12.5f * stepHeight * timeSeconds;
            if (autoMoveY >= 0)
            {
                if (moveY > autoMoveY)
                    moveY = autoMoveY;
            }
            else
            {
                moveY = -moveY;
                if (moveY < autoMoveY)
                    moveY = autoMoveY;
            }
            newPosition.Y += moveY;
            autoMoveY = 0;

            collisionMesh.BoxMove( box, position, newPosition,
                                1, 0, 3, out newPosition );

            if (Math.Abs( newPosition.Y - position.Y ) < 0.0001f && velocity.Y > 0.0f)
                velocity.Y = 0.0f;

            float dist;
            Vector3 pos, norm;
            if (velocity.Y <= 0)
                if (true == collisionMesh.BoxIntersect( box, newPosition,
                            newPosition + new Vector3( 0, -2 * stepHeight, 0 ),
                            out dist, out pos, out norm ))
                {
                    if (norm.Y > 0.70710678f)
                    {
                        onGround = true;
                        autoMoveY = stepHeight - dist;
                    }
                    else
                        onGround = false;
                }
                else
                    onGround = false;

            upDownRot -= rotSpeed * rotate.X;
            if (upDownRot > 1)
                upDownRot = 1;
            else
                if (upDownRot < -1)
                    upDownRot = -1;

            Matrix rotX = Matrix.CreateFromAxisAngle( axisX, upDownRot );
            Matrix rotY = Matrix.CreateFromAxisAngle( axisY, -rotSpeed * rotate.Y );

            transform.Translation = Vector3.Zero;
            transform = transform * rotY;
            Orthonormalize( ref transform );

            world.Translation = Vector3.Zero;
            world = transform * rotX;

            transform.Translation = newPosition;
            newPosition.Y += headHeight;
            world.Translation = newPosition;

            view = Matrix.Invert( world );

            frustum = new BoundingFrustum( view * projection );
        }

        #region IDisposable Members

        bool isDisposed = false;
        public bool IsDisposed
        {
            get { return isDisposed; }
        }

        public void Dispose( )
        {
            Dispose( true );
            GC.SuppressFinalize( this );
        }

        void Dispose( bool disposing )
        {
            if (disposing && !isDisposed)
            {
                if (box != null)
                {
                    box.Dispose( );
                    box = null;
                }
            }
        }

        #endregion
    }
}
