// GLUT mouse motion handling

#include "motion.h"
#include "view.h"

#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
#include <list>

using std::list;

// data on button press or drag
static struct PressData {
    enum Event {PRESS,DRAG} event; // was this a press or drag?
    int button;			// which mouse button was pressed?
    int x, y;			// where was our last position?

    PressData(Event _event, int _button, int _x, int _y) 
	: event(_event), button(_button), x(_x), y(_y) {}
	
} lastPress(PressData::PRESS,0,0,0);

// artificial latency (ms)
static unsigned int latency = 0;
static list<PressData> queuedEvents;



// mouse drag delayed by latency
// use difference between last x,y and x,y to define a rotation
void drag(const PressData &pd)
{
    float dx = pd.x - lastPress.x; //record differences from last position
    float dy = pd.y - lastPress.y;

    lastPress.x = pd.x;	// update stored position
    lastPress.y = pd.y;

    if (lastPress.button == GLUT_LEFT_BUTTON) { // rotate
	// save current view state
	float matrix[16];
	glGetFloatv(GL_MODELVIEW_MATRIX, matrix);

	// From the starting state, rotate around axis perpendicular to
	// mouse motion, where the window x axis is aligned with the 3D
	// view x axis, while the window y axis is aligned with the 3D
	// view z axis. Find perpendicular using rule that (y,-x) is
	// perpendicular to (x,y). Rotation angle is scaled so 1/2
	// window width = about 90 degrees
	glLoadIdentity();
	glRotatef(180 * sqrt(dx*dx + dy*dy) / winWidth, dy,dx,0);
	glMultMatrixf(matrix);
    }
    else if (lastPress.button == GLUT_MIDDLE_BUTTON) { // zoom
	viewDistance -= dy;
	reshape(winWidth, winHeight);
    }
    else if (lastPress.button == GLUT_RIGHT_BUTTON) {
	// save current view state
	float matrix[16];
	glGetFloatv(GL_MODELVIEW_MATRIX, matrix);

	// apply translation to head of list
	glLoadIdentity();
	glTranslatef(dx,-dy,0);
	glMultMatrixf(matrix);
    }

    // tell GLUT that something has changed and we must redraw
    glutPostRedisplay();
}

// process one delayed press or drag
extern "C" void delayedEvent(int)
{
    if (! queuedEvents.empty()) {
	PressData pd = queuedEvents.front();
	queuedEvents.pop_front();

	if (pd.event == PressData::PRESS)
	    lastPress = pd;
	else
	    drag(pd);
    }
}

// called when a mouse button is pressed -- schedule delayed callback
extern "C" void
mousePress(int b, int state, int x, int y)
{
    queuedEvents.push_back(PressData(PressData::PRESS,b,x,y));
    if (latency == 0)
	delayedEvent(0);
    else
	glutTimerFunc(latency, delayedEvent, 0);
}

// called when the mouse moves -- schedule delayed callback
extern "C" void
mouseDrag(int x, int y)
{
    queuedEvents.push_back(PressData(PressData::DRAG,0,x,y));
    if (latency == 0)
	delayedEvent(0);
    else
	glutTimerFunc(latency,delayedEvent,0);
}

// adjust amount of latency
void adjustLatency(int ms) {
    // if shortening latency, empty queue to avoid out of order execution
    if (ms < 0) {
	while (! queuedEvents.empty())
	    delayedEvent(0);
    }

    // adjust latency
    if (ms > 0  ||  latency >= -ms) {
	latency += ms;
	printf("latency >= %g sec\n", latency/1000.);
    }
}

