/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**
 * a class for managing an OpenGL directional light
 *
 * #include "cSpotLight.hh"
 *
 * -lMesaGL or -lGL
 * -lMesaGLU or -lGLU
 *
 * @see cWorld
 */

#include <GL/glut.h>

#include "cSpotLight.hh"
#include "events_cSpotLight.hh"
#include "events_cWorld.hh"

/*
s/extern const string ccSpotLight_\([^;]*\);/const string ccSpotLight_\1 = "\1";/
*/
const string ccSpotLight_SetExponent = "SetExponent";
const string ccSpotLight_IncrExponent = "IncrExponent";
const string ccSpotLight_DecrExponent = "DecrExponent";
const string ccSpotLight_SetCutOff = "SetCutOff";
const string ccSpotLight_IncrCutOff = "IncrCutOff";
const string ccSpotLight_DecrCutOff = "DecrCutOff";

/**
 * default constructor
 	*/
cSpotLight::cSpotLight (
	cEventDispatcher *disp,
	const char *name)
: 	cLight (disp, name),
	mDirection (0.0, 0.0, 1.0)	// default direction
{
}

/**
 * copy constructor (only if in debugging mode)
 	*/
#if DEBUG
cSpotLight::cSpotLight (const cSpotLight &l)
:	cLight (l),	// use inherited copy constructor
	mDirection (l.mDirection)
{
	ENTER_OBJECT_METHOD ("cSpotLight::cSpotLight (const cSpotLight &)");
}
#endif

/**
 * constructor with initialization of position, state and name
 	* @param pos initial position of light source
 	* @param dir initial direction of light source
	* @param name name of light source (optional)
	*/ 
cSpotLight::cSpotLight (
		cEventDispatcher *disp,
		const cVertex &pos, 
		const cVertex &dir, 
		const char *name)
: 	cLight (disp, pos, name),	// no default position
	mDirection (dir)
{
}

/**
 * constructor with initialization of position, state and name
 	* @param pos initial position of light source
 	* @param dir initial position of light source
	* @param state initial state of light source (default: on)
	* @param name name of light source (optional)
	*/ 
cSpotLight::cSpotLight (
		cEventDispatcher *disp,
		const cVertex &pos, 
		const cVertex &dir, 
		const teLightState state,
		const char *name)
: 	cLight (disp, pos, state, name),	// no default position
	mDirection (dir)
{
}

/**
 * constructor with initialization of position, ambient color, state and name
	*/ 
cSpotLight::cSpotLight (
		cEventDispatcher *disp,
		const cVertex &pos, 
		const cVertex &dir, 
		const cADSEColor &col,
		const cSpotLight::teLightState state,
		const char *name)
: 	cLight (disp, pos, col, state, name),
	mDirection (dir)
{
	// light source is not visible by default
	SetVisible (false);
}


/**
 * destructor
 	*/
cSpotLight::~cSpotLight ()
{
	ENTER_OBJECT_METHOD("cSpotLight::~cSpotLight");
	// nothing to do
}


void cSpotLight::SetDirection (const cVertex &dir)
{
	mDirection = dir;
	// make sure, we get that direction exactly
	mRotation.SetIdentity ();
}


void cSpotLight::SetCutOff (const GLfloat cutoff)
{
	mCutOff = cutoff;
}


void cSpotLight::SetExponent (const GLfloat exponent)
{
	mExponent = exponent;
}


void cSpotLight::ActivateLight ()
{
	GLenum light_no = (GLenum)((int) (GL_LIGHT0)+mLightSlot);

	// call inherited ActivateLight (sets light color, aquires light slot)
	cLight::ActivateLight ();

	// now calculate the effective light direction
	GLfloat lightdir[4];

	// rotate direction
	cVertex eff_dir = mRotation*mDirection;

	lightdir[0] = eff_dir.GetX();
	lightdir[1] = eff_dir.GetY();
	lightdir[2] = eff_dir.GetZ();
	lightdir[3] = 1.0;

	glLightfv (light_no, GL_SPOT_DIRECTION, (GLfloat *)&lightdir);

	// save old exponent (it's not touched by cLight)
	glGetLightfv (light_no, GL_SPOT_EXPONENT, &mOldExponent);
	// set our exponent
	glLightf (light_no, GL_SPOT_EXPONENT, mExponent);

	// save old cutoff
	glGetLightfv (light_no, GL_SPOT_CUTOFF, &mOldCutOff);
	// set our cutoff
	glLightf (light_no, GL_SPOT_CUTOFF, mCutOff);
}


void cSpotLight::DeactivateLight ()
{
	GLenum light_no = (GLenum)((int) (GL_LIGHT0)+mLightSlot);

	glLightf (light_no, GL_SPOT_EXPONENT, mOldExponent);
	glLightf (light_no, GL_SPOT_CUTOFF, mOldCutOff);
	cLight::DeactivateLight ();
}


void cSpotLight::DrawThisObject ()
{
	glPushMatrix ();
	glTranslatef (mPosition.GetX(), mPosition.GetY(), mPosition.GetZ());
	glutSolidSphere (0.2, 8, 8);

	cVertex dir_rot = ((mRotation*mDirection).Normalize())*2;

	glBegin (GL_LINES);
	glVertex3f (0.0, 0.0, 0.0);
	glVertex3fv ((GLfloat *)dir_rot);
	glEnd ();
	glPopMatrix ();
}


int cSpotLight::ReceiveEvent (const cEvent &event)
{
	const string en = event.GetName ();
	const cEvent *ev = &event;
	const cIntEvent *iev = (cIntEvent *)ev;

	if (en == ccSpotLight_SetExponent)
	{
		GLfloat new_exp = iev->GetValue ();

		if ((new_exp >= 0) && (new_exp <= 128))
		{
			SetExponent (new_exp);
			mDispatcher->SendEvent (ccW_DoRedisplay);
		}

		return 0;
	}

	if (en == ccSpotLight_IncrExponent)
	{
		if ((mExponent <= 127))
		{
			SetExponent (mExponent + 1);
			mDispatcher->SendEvent (ccW_DoRedisplay);
		}

		return 0;
	}

	if (en == ccSpotLight_DecrExponent)
	{
		if ((mExponent >= 1))
		{
			SetExponent (mExponent - 1);
			mDispatcher->SendEvent (ccW_DoRedisplay);
		}

		return 0;
	}

	if (en == ccSpotLight_SetCutOff)
	{
		int new_cutoff = iev->GetValue ();

		if (((new_cutoff >= 0) && (new_cutoff <= 90))
			|| (new_cutoff = 180))
		{
			SetCutOff (new_cutoff);
			mDispatcher->SendEvent (ccW_DoRedisplay);
		}

		return 0;
	}

	if (en == ccSpotLight_IncrCutOff)
	{
		if (mCutOff <= 89)
		{
			SetCutOff (mCutOff + 1);
			mDispatcher->SendEvent (ccW_DoRedisplay);
		}
		else
		{
			if (mCutOff != 180)
			{
				SetCutOff (180);
				mDispatcher->SendEvent (ccW_DoRedisplay);
			}

		}

		return 0;
	}

	if (en == ccSpotLight_DecrCutOff)
	{
		if (mCutOff == 180)
		{
			SetCutOff (90);
			mDispatcher->SendEvent (ccW_DoRedisplay);
		}
		else
		{

			if (mCutOff >= 1)
			{
				SetCutOff (mCutOff - 1);
				mDispatcher->SendEvent (ccW_DoRedisplay);
			}

		}

		return 0;
	}

	return cLight::ReceiveEvent (event);
}


void cSpotLight::SubscribeToActiveEvents()
{
	mDispatcher->SubscribeToEvent (cIntEvent (ccSpotLight_SetExponent), this);
	mDispatcher->SubscribeToEvent (cEvent (ccSpotLight_IncrExponent), this);
	mDispatcher->SubscribeToEvent (cEvent (ccSpotLight_DecrExponent), this);
	mDispatcher->SubscribeToEvent (cIntEvent (ccSpotLight_SetCutOff), this);
	mDispatcher->SubscribeToEvent (cEvent (ccSpotLight_IncrCutOff), this);
	mDispatcher->SubscribeToEvent (cEvent (ccSpotLight_DecrCutOff), this);

	cLight::SubscribeToActiveEvents();
}

void cSpotLight::UnsubscribeFromActiveEvents ()
{
	mDispatcher->UnsubscribeFromEvent (cIntEvent (ccSpotLight_SetExponent), this);
	mDispatcher->UnsubscribeFromEvent (cEvent (ccSpotLight_IncrExponent), this);
	mDispatcher->UnsubscribeFromEvent (cEvent (ccSpotLight_DecrExponent), this);
	mDispatcher->UnsubscribeFromEvent (cIntEvent (ccSpotLight_SetCutOff), this);
	mDispatcher->UnsubscribeFromEvent (cEvent (ccSpotLight_IncrCutOff), this);
	mDispatcher->UnsubscribeFromEvent (cEvent (ccSpotLight_DecrCutOff), this);

	cLight::UnsubscribeFromActiveEvents ();
}

