/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**
 * implementation of cWorldControl
 	*/

#include <GL/glut.h>

#include "cWorldControl.hh"
#include "cKeyEvent.hh"
#include "cMousePressEvent.hh"
#include "events_cWorld.hh"
#include "events_cEventInput.hh"
#include "events_cInteractiveObject.hh"
#include "events_cLight.hh"
#include "events_cSpotLight.hh"

#define DEBUG_RECEIVE 1
#define DEBUG_SEND 1

#ifndef DEBUG_RECEIVE
#define DEBUG_RECEIVE 0
#endif
#ifndef DEBUG_SEND
#define DEBUG_SEND 0
#endif

const int LEFT_BUTTON = (1 << GLUT_LEFT_BUTTON);
const int MIDDLE_BUTTON = (1 << GLUT_MIDDLE_BUTTON);
const int RIGHT_BUTTON = (1 << GLUT_RIGHT_BUTTON);

/**
 * constructor
 	*/
cWorldControl::cWorldControl (
	cEventDispatcher *disp)
:	cEventConsumer (disp)
{
	disp->SubscribeToEvent (
		cKeyEvent (ccEI_KeyPress), this);
	disp->SubscribeToEvent (
		cVertexEvent (ccEI_MouseMove), this);
	disp->SubscribeToEvent (
		cMousePressEvent (ccEI_MousePress), this); 
}

/**
 * destructor
 	*/
cWorldControl::~cWorldControl ()
{
	// all done by ~cEventConsumer...
}

/**
 * event receiver/translator
 	*/
int cWorldControl::ReceiveEvent (
	const cEvent &event)
{
    const string &en = event.GetName ();
	// ease casting to different event types
	const cEvent *ev = &event;


	if (en == ccEI_MousePress)
	{
		// mouse button status update
		const cMousePressEvent *mpe = (cMousePressEvent *)ev;
		int button, state;

		mpe->GetButtonState (button, state);

		if (state == GLUT_DOWN)	// button pressed or released?
		{
			mMouseButtonState |= (1 << button);	// update mask appropiately
		}
		else
		{
			mMouseButtonState &= !(1 << button);
		}

		return 0;
	}

	if (en == ccEI_KeyPress)
	{
		// cast to cKeyPressEvent
		const cKeyEvent *kpe = (cKeyEvent *)ev;

		unsigned char key = kpe->GetKey ();
		int special = kpe->GetSpecialKey ();

		switch (key)
		{
			case 'h':	
				mDispatcher->SendEvent (ccIO_HideObject);
				return 0;
				break;
			case 's':	
				mDispatcher->SendEvent (ccIO_ShowObject);
				return 0;
				break;
			case 'g':
				mGlobalMode = false; return 0;
				return 0;
				break;
			case 'G':
				mGlobalMode = true; return 0;
				return 0;
				break;
			case 'w':
				mControlMode = CM_WALK; return 0;
				return 0;
				break;
			case 'o':
				mControlMode = CM_ORBIT; return 0;
				return 0;
				break;
			case 'm':
				mControlMode = CM_MOVE_OBJECT; return 0;
				return 0;
				break;
			case 'r':
				mControlMode = CM_ROTATE_OBJECT; return 0;
				return 0;
				break;
			case 'z':
				mDispatcher->SendEvent (
					cEvent (ccW_DisableZBuffer));
				return 0;
				break;
			case 'Z':
				mDispatcher->SendEvent (
					cEvent (ccW_EnableZBuffer));
				return 0;
				break;
			case 'a':
				mDispatcher->SendEvent (
					cEvent (ccW_StopAnimation));
				return 0;
				break;
			case 'A':
				mDispatcher->SendEvent (
					cEvent (ccW_StartAnimation));
				return 0;
				break;
			case '+':
				mDispatcher->SendEvent (
					cEvent (ccW_IncrAnimationSpeed));
				return 0;
				break;
			case '-':
				mDispatcher->SendEvent (
					cEvent (ccW_DecrAnimationSpeed));
				return 0;
				break;
			case 'l':
				mDispatcher->SendEvent (
					cEvent (ccLight_SwitchOff));
				return 0;
				break;
			case 'L':
				mDispatcher->SendEvent (
					cEvent (ccLight_SwitchOn));
				return 0;
				break;
			case 't':
				mDispatcher->SendEvent (
					cEvent (ccSpotLight_DecrCutOff));
				return 0;
				break;
			case 'T':
				mDispatcher->SendEvent (
					cEvent (ccSpotLight_IncrCutOff));
				return 0;
				break;
			case 'y':
				mDispatcher->SendEvent (
					cEvent (ccSpotLight_DecrExponent));
				return 0;
				break;
			case 'Y':
				mDispatcher->SendEvent (
					cEvent (ccSpotLight_IncrExponent));
				return 0;
				break;
				
			case 27:	// esc
			case 'q':
				exit(0);
				break;
			case 0:	// special key
			switch (special)
			{
#if 0
				case GLUT_KEY_DOWN:
					mDispatcher->SendEvent (
						cRotationEvent (
							mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
							1.0, 
							cVertex (1.0, 0.0, 0.0)));
					return 0;
					break;
				case GLUT_KEY_UP:
					mDispatcher->SendEvent (
						cRotationEvent (
							mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
							-1.0, 
							cVertex (1.0, 0.0, 0.0)));
					return 0;
					break;
				case GLUT_KEY_LEFT:
					mDispatcher->SendEvent (
						cRotationEvent (
							mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
							-1.0, 
							cVertex (0.0, 1.0, 0.0)));
					return 0;
					break;
				case GLUT_KEY_RIGHT:
					mDispatcher->SendEvent (
						cRotationEvent (
							mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
							1.0, 
							cVertex (0.0, 1.0, 0.0)));
					return 0;
					break;
#else
				case GLUT_KEY_DOWN:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_MoveLookAt,
								cVertex (0, 1, 0)));
					return 0;
					break;
				case GLUT_KEY_UP:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_MoveLookAt,
								cVertex (0, -1, 0)));
					return 0;
					break;
				case GLUT_KEY_LEFT:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_MoveLookAt,
								cVertex (-1, 0, 0)));
					return 0;
					break;
				case GLUT_KEY_RIGHT:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_MoveLookAt,
								cVertex (1, 0, 0)));
					return 0;
					break;
#endif
				default:
					return -1;
					break;
			}

		}

		return -1;	// we did not accept the key...
	}
	
	if (en == ccEI_MouseMove)
	{
		cVertex coord = ((cVertexEvent *)ev)->GetVertex ();

		switch (mControlMode)
		{
			case CM_WALK:
				switch (mMouseButtonState & (LEFT_BUTTON | MIDDLE_BUTTON))
				{
					case LEFT_BUTTON:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_WalkMove,
								cVertex (coord.GetX(), 0, coord.GetY ())));
						break;
					case MIDDLE_BUTTON:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_WalkMove,
								cVertex (coord.GetX(), -coord.GetY(), 0)));
						break;
					default:
						// x and y are swapped since we're talking about
						// rotation
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_WalkTurn,
								cVertex (10*coord.GetY(), -10*coord.GetX(), 0)));
						break;
				}
				return 0;
				break;
			case CM_ORBIT:
				switch (mMouseButtonState & (LEFT_BUTTON | MIDDLE_BUTTON))
				{
					case LEFT_BUTTON:	// orbit around
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_OrbitCamera,
								cVertex (20*coord.GetY(), -20*coord.GetX(), 0)));
						break;
					case MIDDLE_BUTTON:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_MoveLookAt,
								cVertex (coord.GetX(), -coord.GetY(), 0)));
						break;
					default:
						mDispatcher->SendEvent (
							cVertexEvent (
								ccW_MoveLookAt,
								cVertex (coord.GetX(), 0, coord.GetY ())));
						break;
				}
				return 0;
				break;
			case CM_MOVE_OBJECT:
			{
				cVertex newcoord;

				if ((mMouseButtonState & MIDDLE_BUTTON) > 0) 
				{
					newcoord = cVertex (coord.GetX(), coord.GetZ(), coord.GetY());
				}
				else
				{
					newcoord = cVertex (coord.GetX(), -coord.GetY(), coord.GetZ());
				}

				mDispatcher->SendEvent (
					cVertexEvent (
						mGlobalMode ? ccIO_MoveObjectGlobal : ccIO_MoveObject, 
						newcoord));

				return 0;
				break;
			}
			case CM_ROTATE_OBJECT:
			{
				mDispatcher->SendEvent (
					cRotationEvent (
						mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
						coord.GetX(),
						cVertex (0.0, 1.0, 0.0)));

				if ((mMouseButtonState & MIDDLE_BUTTON) > 0)
				{
					mDispatcher->SendEvent (
							cRotationEvent (
								mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
								coord.GetY(),
								cVertex (0.0, 0.0, 1.0)));
				}
				else
				{
					mDispatcher->SendEvent (
							cRotationEvent (
								mGlobalMode ? ccIO_RotateObjectGlobal : ccIO_RotateObject, 
								coord.GetY(),
								cVertex (1.0, 0.0, 0.0)));
				}
				return 0;
				break;
			}
			default:
				return -1;
				break;
		}

	}


	return -1;
}

#undef DEBUG_RECEIVE
#undef DEBUG_SEND
