/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**
 * class providing input interface to OpenGL/GLUT
 *
 * #include "cEventInput.hh"
 *
 * -lglut
 * -lMesaGL or -lGL
 * -lMesaGLU or -lGLU
 *
 */

#include <string>	// include STL strings
#include <GL/glut.h>

#include "cEventInput.hh"
// we need some constants for events
#include "cInteractiveObject.hh"
#include "cKeyEvent.hh"
#include "cMousePressEvent.hh"
#include "events_cEventInput.hh"

const string ccEI_MouseMove = string("MouseMove");
const string ccEI_MousePress = string ("MousePress");
const string ccEI_KeyPress = string ("KeyPress");

/**
 * As we need to use GLUT's callback functions which are C functions,
 * we cannot use cEventInput methods for that purpose. Therefore we need to
 * have a pointer to the EventInput object to be able to call any of it's
 * methods.
 *
 */
static cEventInput *gsEventInput = NULL; // gs == global static

static int gsmLastX;
static int gsmLastY;

/*
 * Forward declarations of all used CALLBACKs.
 */
// called if an "ordinary" key was pressed
static void GLUTKeyboardCB (
	unsigned char key,	// key pressed (ASCII)
	int x,				// position of mouse pointer
	int y);

// called if a special key was pressed (function keys, cursor movement)
static void GLUTSpecialKeyCB (
	int key,			// key pressed (see GLUT docs for possible values)
	int x,
	int y);

// called if a mouse button was pressed or released
static void GLUTMouseStateCB (
	int button,	// GLUT_x_BUTTON, x = (LEFT, MIDDLE, RIGHT)
	int state,	// GLUT_UP or GLUT_DOWN
	int x,
	int y);

// called if mouse is moved
static void GLUTMouseMoveCB (
	int x,
	int y);

/* called if the visibility of the current window changes
static void GLUTVisibilityCB (
	int state	// GLUT_VISIBLE or GLUT_NOT_VISIBLE
	);
*/

/* called if the mouse enters or leaves the current window
static void GLUTMouseEnterLeaveCB (
	int state	// GLUT_LEFT or GLUT_ENTERED
	);
*/

/* called if the menu status changes (menu gets visible or invisible)
static void GLUTMenuStatusCB (
	int state, 	// GLUT_MENU_IN_USE or GLUT_MENU_NOT_IN_USE
	int x, 		// mouse position
	int y);
*/


/**
 * default constructor w/ optional name
 	* @param disp cEventDispatcher to receive events from
 	* @param name optional name of world object
	*/
cEventInput::cEventInput (
	cEventDispatcher *disp)
:	cEventProducer (disp)
{
#if DEBUG
	if (gsEventInput != NULL)
	{
		cerr << "Interal error: There may be only one cEventInput instance at a time!" << endl;
		exit (1);
	}
#endif

	gsEventInput = this;

	// bind keyboard callbacks
	glutKeyboardFunc (GLUTKeyboardCB);
	glutSpecialFunc (GLUTSpecialKeyCB);

	// bind mouse callbacks
	glutMouseFunc (GLUTMouseStateCB);
	glutMotionFunc (GLUTMouseMoveCB);
	//glutPassiveMotionFunc (GLUTMouseMoveCB);
}

/**
 * copy constructor
 	*/
cEventInput::cEventInput (const cEventInput &w)
:	cEventProducer (w)
{
	ENTER_METHOD("cEventInput::cEventInput ()");
#if DEBUG
	cerr << "Error: cEventInput::cEventInput (const cEventInput &) called." << endl;
	cerr << "cEventInput may not be instantiated more than once!" << endl;
	exit (1);
#endif
}


/**
 * destructor
 	*/
cEventInput::~cEventInput ()
{
	ENTER_METHOD("cEventInput::~cEventInput ()");

	gsEventInput = NULL;
	glutKeyboardFunc (NULL);
	glutSpecialFunc (NULL);

	// bind mouse callbacks
	glutMouseFunc (NULL);
	glutMotionFunc (NULL);
	//glutPassiveMotionFunc (NULL);
}


/** 
 * trigger event bound to key press
 */
void cEventInput::SendKeyEvent (
	unsigned char key,
	int specialkey,
	int modifiers)
{
	mDispatcher->SendEvent (
		cKeyEvent (ccEI_KeyPress, key, specialkey, modifiers));
}

/**
 * send mouse move event
 */
void cEventInput::SendMouseMoveEvent (int x, int y)
{
	mDispatcher->SendEvent (
		cVertexEvent (ccEI_MouseMove, x/10.0, y/10.0, 0));
}

/**
 * send mouse press event
 */
void cEventInput::SendMousePressEvent (
	int button, 
	int state, 
	int x, int y)
{
	mDispatcher->SendEvent (
		cMousePressEvent (ccEI_MousePress, button, state, x/10.0, y/10.0));
}





// called if an "ordinary" key was pressed
static void GLUTKeyboardCB (
	unsigned char key,	// key pressed (ASCII)
	int x,				// position of mouse pointer
	int y)
{
	gsmLastX = x;
	gsmLastY = y;

	if (gsEventInput != NULL)
	{
		gsEventInput->SendKeyEvent (key, 0, glutGetModifiers ());
	}

}

// called if a special key was pressed (function keys, cursor movement)
static void GLUTSpecialKeyCB (
	int key,			// key pressed (see GLUT docs for possible values)
	int x,
	int y)
{
	gsmLastX = x;
	gsmLastY = y;

	if (gsEventInput != NULL)
	{
		gsEventInput->SendKeyEvent (0, key, glutGetModifiers ());
	}

}

// called if a mouse button was pressed or released
static void GLUTMouseStateCB (
	int button,	// GLUT_x_BUTTON, x = (LEFT, MIDDLE, RIGHT)
	int state,	// GLUT_UP or GLUT_DOWN
	int x,
	int y)
{
	int dx = x - gsmLastX;
	int dy = y - gsmLastY;

	gsmLastX = x;
	gsmLastY = y;

	if (gsEventInput != NULL)
	{
		gsEventInput->SendMousePressEvent (
			button, state, dx, dy);
	}
}

// called if mouse is moved
// (used as glutPassiveMotionFunc and glutMotionFunc)
static void GLUTMouseMoveCB (
	int x,
	int y)
{
	int dx = x - gsmLastX;
	int dy = y - gsmLastY;

	gsmLastX = x;
	gsmLastY = y;

	if (gsEventInput != NULL)
	{
		gsEventInput->SendMouseMoveEvent (dx, dy);
	}
}

/* called if the visibility of the current window changes
static void GLUTVisibilityCB (
	int state	// GLUT_VISIBLE or GLUT_NOT_VISIBLE
	)
{}
*/

/* called if the mouse enters or leaves the current window
static void GLUTMouseEnterLeaveCB (
	int state	// GLUT_LEFT or GLUT_ENTERED
	)
{}
*/
