/*
 * Copyright (C) 2003 Clemens Fuchslocher
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <math.h>

#include <iostream>
#include <algorithm>

#include "Utility.h"
#include "Trackball.h"


/*
 * A Virtual Trackball.
 *
 * Interactive Computer Graphics, Second Edition (Addision Wesley)
 *    Chapter 4: Geometric Objects and Transformations
 *    4.10.2 A Virtual Trackball
 */

namespace nsToolkit {

using nsUtility::PI;

Trackball::Trackball(void)
{
	DEBUG_MESSAGE("Trackball::Trackball()")

	this->zoom = 5.0F;
	this->zoomMin = 1.0F;
	this->zoomMax = 20.0F;

	this->translateXMin = 0.0F;
	this->translateYMin = 0.0F;
	this->translateXMax = 0.0F;
	this->translateYMax = 0.0F;

	this->reset();
}


void Trackball::projectToSphere(int x, int y, int windowWidth, int windowHeight, float vector[3])
{
	DEBUG_MESSAGE("Trackball::projectToSphere()")

	// Project X, Y onto a hemi-sphere centered within width, height
	vector[0] = (2.0F * x - windowWidth) / windowWidth;
	vector[1] = (windowHeight - 2.0F * y) / windowHeight;

	// Compute the value on the third axis
	float d = sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
	if (d < 1.0F)
	{
		vector[2] = cos((PI / 2.0F) * d);
	}
	else
	{
		vector[2] = cos(PI / 2.0F);
	}

	// Normalize the vector
	float a = 1.0F / sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
	vector[0] *= a;
	vector[1] *= a;
	vector[2] *= a;
}


bool Trackball::getFrictionless(void)
{
	DEBUG_MESSAGE("Trackball::getFrictionless()")

	return this->frictionless;
}


void Trackball::setFrictionless(bool status)
{
	DEBUG_MESSAGE("Trackball::setFrictionless()")

	this->frictionless = status;
}


float Trackball::getZoom(void)
{
	DEBUG_MESSAGE("Trackball::getZoom()")

	return this->zoom;
}


void Trackball::setZoom(float zoom)
{
	DEBUG_MESSAGE("Trackball::setZoom()")

	this->zoom = zoom;
}


void Trackball::setZoomMin(float min)
{
	DEBUG_MESSAGE("Trackball::setZoomMin()")

	this->zoomMin = min;
}


float Trackball::getZoomMin(void)
{
	DEBUG_MESSAGE("Trackball::getZoomMin()")

	return this->zoomMin;
}


void Trackball::setZoomMax(float max)
{
	DEBUG_MESSAGE("Trackball::setZoomMax()")

	this->zoomMax = max;
}


float Trackball::getZoomMax(void)
{
	DEBUG_MESSAGE("Trackball::getZoomMax()")

	return this->zoomMin;
}


float Trackball::getTranslateX(void)
{
	DEBUG_MESSAGE("Trackball::getTranslateX()")

	return this->translateX;
}


float Trackball::getTranslateY(void)
{
	DEBUG_MESSAGE("Trackball::getTranslateY()")

	return this->translateY;
}


void Trackball::setTranslateRectangle(float xmin, float ymin, float xmax, float ymax)
{
	DEBUG_MESSAGE("Trackball::setTranslateRectangle()")

	this->translateXMin = xmin;
	this->translateYMin = ymin;
	this->translateXMax = xmax;
	this->translateYMax = ymax;
}


void Trackball::reset(void)
{
	DEBUG_MESSAGE("Trackball::reset()")

	this->update = false;
	this->frictionless = false;
	this->temporaryStop = false;

	this->angle = 0.0F;
	this->axis[0] = 0.0F;
	this->axis[1] = 0.0F;
	this->axis[2] = 0.0F;

	this->lastX = -1;
	this->lastY = -1;

	this->lastPosition[0] = 0.0F;
	this->lastPosition[1] = 0.0F;
	this->lastPosition[2] = 0.0F;

	this->translateX = 0.0F;
	this->translateY = 0.0F;

	this->lastTranslateX = 0.0F;
	this->lastTranslateY = 0.0F;

	/*
	 * this->transform[4][] = {
	 *   {1.0, 0.0, 0.0, 0.0},
	 *   {0.0, 1.0, 0.0, 0.0},
	 *   {0.0, 0.0, 1.0, 0.0},
	 *   {0.0, 0.0, 0.0, 1.0}
	 * };
	 */
	for (int y = 0; y < 4; y++)
	{
		for (int x = 0; x < 4; x++)
		{
			if (x == y)
			{
				this->transform[y * 4 + x] = 1.0F;
			}
			else
			{
				this->transform[y * 4 + x] = 0.0F;
			}
		}
	}
}


void Trackball::eventExpose(void)
{
	DEBUG_MESSAGE("Trackball::eventExpose()")

	if (this->update || this->frictionless)
	{
		if (this->temporaryStop == false)
		{
			glPushMatrix();
			glLoadIdentity();
			glRotatef(this->angle, this->axis[0], this->axis[1], this->axis[2]);
			glMultMatrixf(this->transform);
			glGetFloatv(GL_MODELVIEW_MATRIX, this->transform);
			glPopMatrix();
		}
	}
	this->update = false;

	glMultMatrixf(this->transform);
}


// Rotation
void Trackball::eventRotationBegin(int x, int y, int windowWidth, int windowHeight)
{
	DEBUG_MESSAGE("Trackball::eventRotationBegin()")

	// Stop frictionless?
	if (((this->lastX - x) == 0) && ((this->lastY - y) == 0))
	{
		this->update = false;
		this->temporaryStop = true;
	}
	else
	{
		this->update = true;
		this->temporaryStop = false;
	}

	this->lastX = x;
	this->lastY = y;

	this->projectToSphere(x, y, windowWidth, windowHeight, lastPosition);
}


void Trackball::eventRotationUpdate(int x, int y, int windowWidth, int windowHeight)
{
	DEBUG_MESSAGE("Trackball::eventRotationUpdate()")

	float currentPosition[3];
	this->projectToSphere(x, y, windowWidth, windowHeight, currentPosition);

	float dx = currentPosition[0] - this->lastPosition[0];
	float dy = currentPosition[1] - this->lastPosition[1];
	float dz = currentPosition[2] - this->lastPosition[2];

	if (dx || dy || dz)
	{
		this->angle = 45.0F * sqrt(dx*dx + dy*dy + dz*dz);

		this->axis[0] = this->lastPosition[1] * currentPosition[2] - this->lastPosition[2] * currentPosition[1];
		this->axis[1] = this->lastPosition[2] * currentPosition[0] - this->lastPosition[0] * currentPosition[2];
		this->axis[2] = this->lastPosition[0] * currentPosition[1] - this->lastPosition[1] * currentPosition[0];

		this->lastPosition[0] = currentPosition[0];
		this->lastPosition[1] = currentPosition[1];
		this->lastPosition[2] = currentPosition[2];
	}

	this->update = true;
}


// Zoom
void Trackball::eventZoomBegin(int y)
{
	DEBUG_MESSAGE("Trackball::eventZoomBegin()")

	this->lastY = y;
}


void Trackball::eventZoomUpdate(int y, int windowHeight)
{
	DEBUG_MESSAGE("Trackball::eventZoomUpdate()")

	this->zoom -= (((float) (this->lastY - y)) / windowHeight) * 20.0;

	if (this->zoom < this->zoomMin)
	{
		this->zoom = this->zoomMin;
	}
	else if (this->zoom > this->zoomMax)
	{
		this->zoom = this->zoomMax;
	}

	this->lastY = y;
}


// Translate
void Trackball::eventTranslateBegin(int x, int y)
{
	DEBUG_MESSAGE("Trackball::eventTranslateBegin()")

	this->lastX = x;
	this->lastY = y;
}


void Trackball::eventTranslateUpdate(int x, int y, int windowWidth, int windowHeight)
{
	DEBUG_MESSAGE("Trackball::eventTranslateUpdate()")

	this->translateX = ((float) x - this->lastX) / (windowWidth / this->zoom) + this->lastTranslateX;
	this->translateY = ((float) this->lastY - y) / (windowHeight / this->zoom) + this->lastTranslateY;

	if (this->translateX > this->translateXMax)
	{
		this->translateX = this->translateXMax;
	}
	else if (this->translateX < this->translateXMin)
	{
		this->translateX = this->translateXMin;
	}

	if (this->translateY > this->translateYMax)
	{
		this->translateY = this->translateYMax;
	}
	else if (this->translateY < this->translateYMin)
	{
		this->translateY = this->translateYMin;
	}
}


void Trackball::eventTranslateEnd(void)
{
	DEBUG_MESSAGE("Trackball::eventTranslateEnd()")

	this->lastTranslateX = this->translateX;
	this->lastTranslateY = this->translateY;
}

}

