#pragma warning ( disable: 4786 )
#include <vector>
#include <cmath>

#include "DTSQuaternion.h"

namespace DTS
{
   Quaternion Quaternion::identity (0,0,0,1) ;

   /*! Create a quaternion from three spherical rotation angles
       (XY rotation angle, latitude and longitude) in radians */

   Quaternion::Quaternion (float angle, float latitude, float longitude)
   {
      Quaternion x (Point( 1, 0, 0), angle) ;
      Quaternion y (Point( 0, 1, 0), latitude) ;
      Quaternion z (Point( 0, 0, 1), longitude) ;

      // This order is a little wierd, but we flip the x & y 
      // axis when importing from MilkShape.
      (*this) = x * z * y;
   }

   /*! Calculate the angle in radians between this and another quaternion */

   float Quaternion::angleBetween (const Quaternion &b) const 
   {
      return acos (x()*b.x() + y()*b.y() + z()*b.z() + w()*b.w()) ;
   }

   /*! Calculate the rotation matrix */

   Matrix<4,4> Quaternion::toMatrix () const
   {
      float xx = x() * x() ;
      float xy = x() * y(), yy = y() * y() ;
      float xz = x() * z(), yz = y() * z(), zz = z() * z() ;
      float xw = x() * w(), yw = y() * w(), zw = z() * w(), ww = w() * w()  ;

      Matrix<4,4>::Cell data[] = {
         1 - 2 * (yy+zz),     2 * (xy-zw),     2 * (xz+yw),     0,
             2 * (xy+zw), 1 - 2 * (xx+zz),     2 * (yz-xw),     0,
             2 * (xz-yw),     2 * (yz+xw), 1 - 2 * (xx+yy),     0,
             0,               0,               0,               1
      } ;
      return Matrix<4,4>(data) ;
   }

   /*! Create a quaternion from a rotation matrix */

   Quaternion::Quaternion (const Matrix<> &m)
   {
      fromMatrix(m) ;
   }

   void Quaternion::fromAxis (Point axis, float angle)
   {
      axis.normalize() ;
      float s = sin(angle / 2.0f) ;
      x(axis.x() * s) ;
      y(axis.y() * s) ;
      z(axis.z() * s) ;
      w(cos(angle / 2.0f)) ;
   }

   void Quaternion::fromMatrix (const Matrix<> &m)
   {
      float qw2 = (1+ m[0][0] + m[1][1] + m[2][2]) * 0.25f ;
      float qx2 = qw2 - 0.5f * (m[1][1] + m[2][2]) ;
      float qy2 = qw2 - 0.5f * (m[0][0] + m[2][2]) ;
      float qz2 = qw2 - 0.5f * (m[0][0] + m[1][1]) ;
      float s ;

      if (qw2 > qx2 && qw2 > qy2 && qw2 > qz2)
      {
         w( sqrtf(qw2) ); s = 0.25f / w() ;
         x( (m[2][1] - m[1][2]) * s );
         y( (m[0][2] - m[2][0]) * s );
         z( (m[1][0] - m[0][1]) * s );
      }
      else if (qx2 > qy2 && qx2 > qz2)
      {
         x( sqrtf(qx2) ); s = 0.25f / x() ;
         w( (m[2][1] - m[1][2]) * s );
         y( (m[0][1] - m[1][0]) * s );
         z( (m[2][0] - m[0][2]) * s );
      }
      else if (qy2 > qz2)
      {
         y( sqrtf(qy2) ); s = 0.25f / y() ;
         w( (m[0][2] - m[2][0]) * s );
         x( (m[0][1] - m[1][0]) * s );
         z( (m[1][2] - m[2][1]) * s );
      }
      else
      {
         z( sqrtf(qz2) ); s = 0.25f / z() ;
         w( (m[1][0] - m[0][1]) * s );
         x( (m[0][2] - m[2][0]) * s );
         y( (m[1][2] - m[2][1]) * s );
      }

      //normalize() ;
   }

   /*! Return the inverse of a quaternion */

   Quaternion Quaternion::inverse() const
   {
      float norm = x()*x() + y()*y() + z()*z() + w()*w() ;
      
      return conjugate() * (1.0f / norm) ;
   }

   Quaternion operator * (const Quaternion &a, const Quaternion &b) 
   {
      return Quaternion (
         +a[0]*b[3] +a[1]*b[2] -a[2]*b[1] +a[3]*b[0],
         -a[0]*b[2] +a[1]*b[3] +a[2]*b[0] +a[3]*b[1],
         +a[0]*b[1] -a[1]*b[0] +a[2]*b[3] +a[3]*b[2],
         -a[0]*b[0] -a[1]*b[1] -a[2]*b[2] +a[3]*b[3]
         ) ;
   }

   Point Quaternion::apply (const Point &v) const
   {
      // Torque uses column vectors, which means quaternions
      // rotate backwards from what you might normally expect.
      Quaternion q(v.x(), v.y(), v.z(), 0) ;
      Quaternion r =  conjugate() * q * (*this);
      return Point(r.x(), r.y(), r.z()) ;

      // The following should give the same result:
      //Matrix<> m = toMatrix() ;
      //return m * v ;
   }

}