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

// workaround a silly bug I could not track down
// (it compiled on several systems but not on grafik03)
// egcs was the same, Mesa was the same, but it did not work
#define APIENTRY
#define CALLBACK
// get GLU
#include <GL/glu.h>

#include "cLight.hh"
#include "events_cLight.hh"
// we need to issue an DoRedisplay event
#include "events_cWorld.hh"

#include "common.hh"

const string ccLight_SwitchOn = string ("Light_SwitchOn");
const string ccLight_SwitchOff = string ("Light_SwitchOff");

/*
 * static member of cLight class
 */
cLight::teLightState cLight::mgLightSlots[8] = {
	LIGHT_OFF, LIGHT_OFF, LIGHT_OFF, LIGHT_OFF,
	LIGHT_OFF, LIGHT_OFF, LIGHT_OFF, LIGHT_OFF };

/*
 * static member of cLight class
 */
int cLight::mgActiveLights = 0;

/**
 * default constructor
 	*/
cLight::cLight (
	cEventDispatcher *disp,
	const char *name)
: cInteractiveObject (disp, name),
  mLightSlot (-1)
{
	// light source is not visible by default
	SetVisible (false);
}

/**
 * default constructor
 	*/
cLight::cLight (
	cEventDispatcher *disp,
	const cVertex &pos,
	const char *name)
: cInteractiveObject (disp, pos, name),
  mLightSlot (-1)
{
	// light source is not visible by default
	SetVisible (false);
}

/**
 * copy constructor (only if in debugging mode)
 	*/
#if DEBUG
cLight::cLight (const cLight &l)
:	cInteractiveObject (l),	// use inherited copy constructor
	mState (l.mState),
	mLightColor (l.mLightColor),
	mLightSlot (-1)
{
	ENTER_OBJECT_METHOD ("cLight::cLight (const cLight &)");
}
#endif

/**
 * constructor with initialization of position, state and name
 	* @param pos initial position of light source
	* @param state initial state of light source (default: on)
	* @param name name of light source (optional)
	*/ 
cLight::cLight (
		cEventDispatcher *disp,
		const cVertex &v, 
		const teLightState state,
		const char *name)
: 	cInteractiveObject (disp, v, name),
	mState (state),
	mLightSlot (-1)
{
	// light source is not visible by default
	SetVisible (false);
}

/**
 * constructor with initialization of position, ambient color, state and name
 	* @param pos initial position of light source
	* @param amb ambient color of the light source
	* @param state initial state of light source (default: on)
	* @param name name of light source (optional)
	*/ 
cLight::cLight (
		cEventDispatcher *disp,
		const cVertex &v, 
		const cADSEColor &col,
		const cLight::teLightState state,
		const char *name)
: 	cInteractiveObject (disp, v, name),
	mState (state),
	mLightColor (col),
	mLightSlot (-1)
{
	// light source is not visible by default
	SetVisible (false);
}


/**
 * destructor
 	*/
cLight::~cLight ()
{
	ENTER_OBJECT_METHOD("cLight::~cLight");

	if (mQuadric != NULL)
	{
		gluDeleteQuadric (mQuadric);
	}

}


/**
 * set up for drawing (overloaded from cVisibleObject)
 	*/
void cLight::Activate ()
{
	// only activate if the light is on
	if ((IsOn ()) && (AllocateLightSlot () == 0))
	{
		// is this the first light we have activated?
		if (mgActiveLights == 1)
		{	// if so, activate lighting alltogether
			glEnable (GL_LIGHTING);	// TODO: Think about it again.
		}

		ActivateLight ();
		// finally enable the light
		glEnable ((GLenum) (GL_LIGHT0+mLightSlot));
	}

	// call inherited activate to Activate() all childs
	cVisibleObject::Activate ();

}


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

	// set light color; need to cast to an enum here... :-(
	glLightfv (light_no, 
			GL_AMBIENT, 
			mLightColor.Ambient());
	glLightfv (light_no, 
			GL_DIFFUSE, 
			mLightColor.Diffuse());
	glLightfv (light_no,
			GL_SPECULAR, 
			mLightColor.Specular());

	// A light's position has 4 values. if the fourth value
	// is zero, it's treated as a directional light.
	// That's a bit odd...
	GLfloat lightpos[4];

	lightpos[0] = mPosition.GetX();
	lightpos[1] = mPosition.GetY();
	lightpos[2] = mPosition.GetZ();
	lightpos[3] = 1.0;	// non-directional light!

	glLightfv (light_no,
			GL_POSITION, 
			(GLfloat *)&lightpos);
}

/**
 * suppress transformation
 	*/
void cLight::Transform ()
{
    ENTER_OBJECT_METHOD ("cLight::Transform ()");
	// do not transform anything but store global transformation
	// matrix (for moving light source)
    glGetFloatv (GL_MODELVIEW_MATRIX, mGlobalTransformation);
}


/**
 * clean up after drawing (overloaded from cVisibleObject)
 	*/
void cLight::Deactivate ()
{
	// check whether we've got something to disable
	if (mLightSlot >= 0)
	{
		DeactivateLight ();

		// deallocate light slot
		FreeLightSlot ();

		// deactivate lighting if we were the last light source
		// (The last one switches off the light... <g>)
		if (mgActiveLights == 0)
		{
			glDisable (GL_LIGHTING);
		}

	}

	// do not forget to call inherited deactivate
	cVisibleObject::Deactivate ();
}


void cLight::MoveTo (const cVertex &pos)
{
	// cVisibleObject multiplies by mRotation which we definitely
	// do not want here!
	mPosition = pos;
}


void cLight::MoveBy (const cVertex &pos)
{
	// cVisibleObject multiplies by mRotation which we definitely
	// do not want here!
	mPosition += pos;
}

void cLight::DeactivateLight ()
{
	// disable the light
	glDisable ((GLenum) (GL_LIGHT0+mLightSlot));
	// nothing more to do
}


void cLight::DrawThisObject ()
{
//	cerr << "cLight::DrawThisObject ()" << endl;

	/** making a light visible might be useful */
	glPushMatrix ();
	
	if (mQuadric == NULL)
	{
		mQuadric = gluNewQuadric ();
		// turn normals inside, so the light source is visible
		gluQuadricOrientation (mQuadric, GLU_INSIDE);
	}

	glTranslatef (mPosition.GetX(), mPosition.GetY(), mPosition.GetZ());
//	glutSolidSphere (0.2, 6, 6);
	gluSphere (mQuadric, 0.2, 8, 8);
	glPopMatrix ();
}

int cLight::AllocateLightSlot ()
{
	int check_slot = 0;

	// look for free slot
	while ((mgLightSlots[check_slot] == LIGHT_ON) 
			&& (check_slot < 8)) 
	{
		check_slot++;
	}

	// found a free slot?
	if (check_slot < 8)
	{
		// aquire light slot
		mLightSlot = check_slot;

		mgLightSlots[mLightSlot] = LIGHT_ON;

		mgActiveLights++;

		return 0;
	}
	else
	{
		return -1;
	}

}


int cLight::FreeLightSlot ()
{
	if (mLightSlot == -1)
	{
		return -1;
	}

	mgLightSlots[mLightSlot] = LIGHT_OFF;
	mgActiveLights --;

	mLightSlot = -1;

	return 0;
}

/**
 * set light status (on or off)
 	* @param state new state of light
 	*/
void cLight::SetState (cLight::teLightState state)
{
	mState = state;
}

/**
 * query light status
 	* @return cLight::teLightState with light status
	*/
bool cLight::IsOn () const
{
	return (mState == LIGHT_ON);
}


/**
 * set ambient color
 	* @param c new ambient color of light
	*/
void cLight::SetAmbientColor (const cColor &c)
{
	mLightColor.wDiffuse() = c;
}

/**
 * set diffuse color
 	* @param c new diffuse color of light
	*/
void cLight::SetDiffuseColor (const cColor &c)
{
	mLightColor.wDiffuse() = c;
}

/**
 * set specular color
 	* @param c new specular color of light
	*/
void cLight::SetSpecularColor (const cColor &c)
{
	mLightColor.wSpecular() = c;
}


int cLight::ReceiveEvent (const cEvent &event)
{
	const string &en = event.GetName ();

	if (en == ccLight_SwitchOn)
	{
		SetState (LIGHT_ON);
		mDispatcher->SendEvent (cEvent (ccW_DoRedisplay));
		return 0;
	}

	if (en == ccLight_SwitchOff)
	{
		SetState (LIGHT_OFF);
		mDispatcher->SendEvent (cEvent (ccW_DoRedisplay));
		return 0;
	}

	// event not accepted - pass it on
	return cInteractiveObject::ReceiveEvent (event);
}


void cLight::SubscribeToActiveEvents ()
{
	mDispatcher->SubscribeToEvent (cEvent (ccLight_SwitchOn), this);
	mDispatcher->SubscribeToEvent (cEvent (ccLight_SwitchOff), this);
	cInteractiveObject::SubscribeToActiveEvents ();
}

void cLight::UnsubscribeFromActiveEvents ()
{
	mDispatcher->UnsubscribeFromEvent (cEvent (ccLight_SwitchOn), this);
	mDispatcher->UnsubscribeFromEvent (cEvent (ccLight_SwitchOff), this);
	cInteractiveObject::UnsubscribeFromActiveEvents ();
}

