#include <GL/gl.h>
#include <GL/glu.h>

#include "camera.h"
//#include "quaternion.h"

#ifndef M_PI
#define M_PI 3.141592653589793238462643383279502
#endif 

const double kMouseRotationSensitivity		= 1.0f/180.0f;
const double kMouseTranslationXSensitivity	= 0.02f;
const double kMouseTranslationYSensitivity	= 0.02f;
const double kMouseZoomSensitivity			= 0.1f;

void MakeDiagonal(Mat4d &m, double k)
{
	register int i,j;

	for (i=0; i<4; i++)
		for (j=0; j<4; j++)
			m[i][j] = (i==j) ? k : 0.0f;
}

void MakeHScale(Mat4d &m, const Vec3d &s)	
{
	MakeDiagonal(m,1.0f);
	m[0][0] = s[0]; m[1][1] = s[1];	m[2][2] = s[2];
}

void MakeHTrans(Mat4d &m, const Vec3d &s)
{
	MakeDiagonal(m,1.0f);
	m[0][3] = s[0]; m[1][3] = s[1]; m[2][3] = s[2];
}

void MakeHRotX(Mat4d &m, double theta)
{
	MakeDiagonal(m,1.0f);
	double cosTheta = cos(theta);
	double sinTheta = sin(theta);
	m[1][1] = cosTheta;
	m[1][2] = -sinTheta;
	m[2][1] = sinTheta;
	m[2][2] = cosTheta;
}

void MakeHRotY(Mat4d &m, double theta)
{
	MakeDiagonal(m,1.0f);
	double cosTheta = cos(theta);
	double sinTheta = sin(theta);
	m[0][0] = cosTheta;
	m[2][0] = -sinTheta;
	m[0][2] = sinTheta;
	m[2][2] = cosTheta;
}

void MakeHRotZ(Mat4d &m, double theta)
{
	MakeDiagonal(m,1.0f);
	double cosTheta = cos(theta);
	double sinTheta = sin(theta);
	m[0][0] = cosTheta;
	m[0][1] = -sinTheta;
	m[1][0] = sinTheta;
	m[1][1] = cosTheta;
}


void Camera::calculateViewingTransformParameters() 
{
	Mat4d dollyXform;
	Mat4d azimXform;
	Mat4d elevXform;
	Mat4d twistXform;
	Mat4d originXform;

	MakeHTrans(dollyXform, Vec3d(0,0,mDolly));
	MakeHRotY(azimXform, mAzimuth);
	MakeHRotX(elevXform, mElevation);
	MakeDiagonal(twistXform, 1.0f);
	MakeHTrans(originXform, mLookAt);
	
	mPosition = Vec3d(0,0,0);
	// grouped for (mat4 * vec3) ops instead of (mat4 * mat4) ops
	mPosition = xform(originXform, xform(azimXform, xform(elevXform, xform(dollyXform,mPosition))));


	if ( fmod(mElevation, 2.0*M_PI) < 3*M_PI/2 && fmod(mElevation, 2.0*M_PI) > M_PI/2 )
		mUpVector= Vec3d(0,-1,0);
	else
		mUpVector= Vec3d(0,1,0);

	mDirtyTransform = false;
}

Camera::Camera() {
	mElevation = mAzimuth = mTwist = 0.0f;
	mDolly = -10;
	mElevation = 0.2f;
	mAzimuth = 1.0f;

	mLookAt = Vec3d( 0, 0, 0 );
	mCurrentMouseAction = kActionNone;

	calculateViewingTransformParameters();
}

Camera::Camera( Vec3d position, Vec3d lookAt ) {
//	mPosition = position;
	mLookAt = lookAt;

	calculateViewingTransformParameters();
}

void Camera::clickMouse( MouseAction_t action, int x, int y )
{
	mCurrentMouseAction = action;
	mLastMousePosition[0] = x;
	mLastMousePosition[1] = y;
}

void Camera::dragMouse( int x, int y )
{
	Vec3d mouseDelta   = Vec3d(x,y,0.0f) - mLastMousePosition;
	mLastMousePosition = Vec3d(x,y,0.0f);

	switch(mCurrentMouseAction)
	{
	case kActionTranslate:
		{
			calculateViewingTransformParameters();

			double xTrack =  -mouseDelta[0] * kMouseTranslationXSensitivity;
			double yTrack =  -mouseDelta[1] * kMouseTranslationYSensitivity;

			Vec3d transXAxis = cross(mUpVector,(mPosition - mLookAt));
			transXAxis /= sqrt(dot(transXAxis,transXAxis));
			Vec3d transYAxis = cross((mPosition - mLookAt), transXAxis);
			transYAxis /= sqrt(dot(transYAxis,transYAxis));

			setLookAt(getLookAt() + transXAxis*xTrack + transYAxis*yTrack);
			
			break;
		}
	case kActionRotate:
		{
			double dAzimuth		=   -mouseDelta[0] * kMouseRotationSensitivity;
			double dElevation	=   -mouseDelta[1] * kMouseRotationSensitivity;
			
			setAzimuth(getAzimuth() + dAzimuth);
			setElevation(getElevation() + dElevation);
			
			break;
		}
	case kActionZoom:
		{
			double dDolly = mouseDelta[1] * kMouseZoomSensitivity;
			setDolly(getDolly() + dDolly);
			break;
		}
	case kActionTwist:
	default:
		break;
	}

}

void Camera::releaseMouse( int x, int y )
{
	mCurrentMouseAction = kActionNone;
}


void Camera::applyViewingTransform() {
	if( mDirtyTransform )
		calculateViewingTransformParameters();

	gluLookAt(	mPosition[0], mPosition[1], mPosition[2],
				mLookAt[0],   mLookAt[1],   mLookAt[2],
				mUpVector[0], mUpVector[1], mUpVector[2]);
}
