//BSD License  
//DXGfx® - http://www.eteractions.com
//Copyright (c) 2005
//by Guillaume Randon
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software
//and associated documentation files (the "Software"), to deal in the Software without restriction,
//including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial
//portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;

namespace DXGfxLib
{
    /// <summary>
    /// Thie class is a third person camera. The camera will stay behind the specified scene node. YOu can rotate around
    /// it to a certain extend with the mouse while pressing the third button.
    /// </summary>
    public class ThirdPersonCamera : Camera
    {
        /// <summary>
        /// The scene node that this camera object is going to follow. The camera always look at 0,0,0 of the specified scene node.
        /// </summary>
        private SceneNode person;

        /// <summary>
        /// A scene node associated with the camera. Give all the functionnalities of a scene node to our camera object.
        /// </summary>
        public SceneNode camera;

        public Vector3 position;

        public Vector3 up;

        /// <summary>
        /// This value is used to determine distance of the camera to the node it is attached to!
        /// </summary>
        public float radius = 49.0f;

        /// <summary>
        /// The maximum distance between the observed node and the camera scene node.
        /// </summary>
        public float maxRadius = 150.0f;

        /// <summary>
        /// The minimum distance to the observed object.
        /// </summary>
        public float minRadius = 4.0f;

        /// <summary>
        /// This help define which button should be used for rotation. By default it is the third mouse button.
        /// </summary>
        protected int rotateButtonMask = (int)MouseButtonMask.Right;

        /// <summary>
        /// Button used for the zoom. By default mid button.
        /// </summary>
        protected int zoomButtonMask = (int)MouseButtonMask.Wheel;

        /// <summary>
        /// The world matrix for positionning and orientation of our camera!
        /// </summary>
        public Matrix WorldMatrix = Matrix.Identity;

        /// <summary>
        /// This define a constraint on the rotation around our observed node.
        /// </summary>
        protected float angle = (float)Math.PI / 8;


        protected float behindAngle = 0.0f;

        protected bool rightDown = false;
        protected float lastX = 0.0f;
        protected float lastY = 0.0f;

        protected float maxAngle = 0.25f*(float)Math.PI;
        protected float minAngle = -0.25f * (float)Math.PI;
        protected float maxAngleVert = 0.5f * 0.25f * (float)Math.PI;
        protected float minAngleVert = 0.0f;
        protected float angleMult = 0.0025f * (float)Math.PI;

        /// <summary>
        /// Public accessor for the Person SceneNode. The 'observed' scene node!!
        /// </summary>
        public SceneNode Person
        {
            set
            {
                person = value;

                camera.parent = person;
                person.AddChild(camera);

                Matrix trans = Matrix.Translation(0.0f, radius * (float)Math.Sin(angle), -radius * (float)Math.Cos(angle));
                Matrix rot = Matrix.RotationX(angle);

                camera.LocalMat = Matrix.Multiply(rot, trans);
            }
            get { return person; }
        }

        /// <summary>
        /// Default constructor for our camera. After calling the constructor the camera is not attached to any preexisting
        /// scene node it is attached to a dummy one created for the occasion.
        /// </summary>
        public ThirdPersonCamera()
        {
            camera = new SceneNode();
            Person = new SceneNode();
        }

        /// <summary>
        /// This will handle windows messages for correct positionning of the camera based on mouse inputs!!
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="msg"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        public override bool HandleMessages(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam)
        {
            // Call base first
            base.HandleMessages(hWnd, msg, wParam, lParam);

            // Current mouse position
            short mouseX = NativeMethods.LoWord((uint)lParam.ToInt32());
            short mouseY = NativeMethods.HiWord((uint)lParam.ToInt32());

            if (msg == NativeMethods.WindowMessage.RightButtonDown)
            {
                rightDown = true;
                lastX = mouseX;
                lastY = mouseY;
            }
            if ((msg == NativeMethods.WindowMessage.MouseMove) && rightDown)
            {
                behindAngle += angleMult * (mouseX - lastX);
                if (behindAngle > maxAngle)
                    behindAngle = maxAngle;
                if (behindAngle < minAngle)
                    behindAngle = minAngle;
                angle += angleMult * (mouseY - lastY);
                if (angle > maxAngleVert)
                    angle = maxAngleVert;
                if (angle < minAngleVert)
                    angle = minAngleVert;
            }

            if (msg == NativeMethods.WindowMessage.RightButtonUp)
            {
                rightDown = false;
            }

            return false;
        }

        /// <summary>
        /// Each frame we update quite a lot of things, it is done there. This needs to be called each frame.
        /// </summary>
        /// <param name="elapsedTime"></param>
        public override void FrameMove(float elapsedTime)
        {
            // Change the radius from the camera to the model based on wheel scrolling
            if ((mouseWheelDelta != 0) && (zoomButtonMask == (int)MouseButtonMask.Wheel))
            {
                radius -= mouseWheelDelta * radius * 0.1f;
                radius = Math.Min(maxRadius, radius);
                radius = Math.Max(minRadius, radius);
                mouseWheelDelta = 0;
            }
            Matrix trans = Matrix.Translation(0.0f, radius * (float)Math.Sin(angle), -radius * (float)Math.Cos(angle));
            Matrix rot = Matrix.RotationX(angle);

            camera.LocalMat = Matrix.Multiply(rot, trans);

            // Get the mouse movement (if any) if the mouse buttons are down
            if (currentButtonMask != 0)
                UpdateMouseDelta(elapsedTime);

            // Get amount of velocity based on the keyboard input and drag (if any)
            UpdateVelocity(elapsedTime);

            // Simple euler method to calculate position delta
            Vector3 posDelta = velocity * elapsedTime;

            person.ApplyLocalTransform(Matrix.RotationY(0.1f*posDelta.X));

            posDelta.X = 0.0f;
            posDelta.Y = 0.0f;

            person.ApplyLocalTransform(Matrix.Translation(posDelta));

            camera.PositionToParent = Vector3.TransformCoordinate(camera.PositionToParent,
                Matrix.RotationY(behindAngle));

            //if (person.positionConstraint != null)
            //{
            //    person.positionConstraint.ValidateOrMove(ref person);
            //}
            if (camera.positionConstraint != null)
            {
                camera.positionConstraint.ValidateCameraOrMove(ref camera, ref person); 
            }

            Vector3 localUp = new Vector3(0.0f, 1.0f, 0.0f);

            position = camera.Position;
            lookAt = person.Position;
            up = localUp;
            viewMatrix = Matrix.LookAtLH(camera.Position, person.Position, localUp);
        }
    }
}
