/**
 * package for classes concerning light
 */
package simogl.effect.light;

import javax.media.opengl.GL;
import simogl.geometry.Point3D;

/**
 * This class is used to set light sources in the scene
 * @author Frank Bruns
 *
 */
public class Light
{

	float position[] = { 0.0F, 0.0F, 1.0F, 0.0F };

	float ambientColor[] = { 0.0F, 0.0F, 0.0F, 0.0F };
	float diffuseColor[] = { 1.0F, 1.0F, 1.0F, 0.0F };
	float specularColor[] = { 1.0F, 1.0F, 1.0F, 0.0F };

	float constAtten = 1.0f;
	float linearAtten = 0.0f;
	float quadAtten = 0.0f;

	float spotCutOff = 180.0f;
	Point3D spotDirection = new Point3D(0.0f, 0.0f, 1.0f);
	float spotExponent = 0.0f;

	int lightNr = 0;

	boolean activated = true;
	boolean activatedAmbient = false;
	boolean activatedDiffuse = false;
	boolean activatedSpecular = false;

	/**
	 * Creates a new light source at the specified position
	 * @param x x-coordinate
	 * @param y y-coordinate
	 * @param z z-coordinate
	 * @param lightNr number from 0 to 7
	 */
	public Light(float x, float y, float z, int lightNr)
	{
		this.setPosition(x, y, z);
		this.setDirectional();
		this.lightNr = this.getGLLightConstant(lightNr);
	}

	/**
	 * Sets the position of the light source
	 * @param x x-coordinate
	 * @param y y-coordinate
	 * @param z z-coordinate
	 */
	public void setPosition(float x, float y, float z)
	{
		this.position[0] = x;
		this.position[1] = y;
		this.position[2] = z;
	}

	/**
	 * Returns the current position of the light source
	 * @return light's position
	 */
	public Point3D getPosition()
	{
		return new Point3D(position[0], position[1], position[2]);
	}

	/**
	 * Turns the light source into a directional light source
	 *
	 */
	public void setDirectional()
	{
		this.position[3] = 0.0F;
	}

	/**
	 * Determines whether this light is a directional light
	 * @return is directional light? -> true or false
	 */
	public boolean isDirectional()
	{
		return position[3] == 0.0F;
	}

	/**
	 * Turns the light source into a positional light source
	 *
	 */
	public void setPositional()
	{
		this.position[3] = 1.0F;
	}

	/**
	 * Determines whether this light is a positional light
	 * @return is positional light? -> true or false
	 */
	public boolean isPositional()
	{
		return position[3] == 1.0F;
	}

	/**
	 * Turns this light to a spot light. Beeing a spot light means
	 * usually being a positional light, too, becaus it commonly does
	 * make no sense to use a directional light as a spot light.
	 * <br>The parameter <i>spotCutOff</i> describes the angle of the 
	 * light spot's cone. It accepts values from 0.0f - 90.0f or excatly
	 * 180.0f (simple positional light). The parameter <i>spotExponent</i>
	 * describes the focus intensity of the spot light. The higher the value
	 * the more focused is the spot light to its center. You can compare the
	 * effect with a focused flashlight.
	 * @param spotCutOff angle from 0.0f-90.0f or exactly 180.0f degrees
	 * @param spotExponent factor to focus the spot light
	 */
	public void setSpot(float spotCutOff, float spotExponent)
	{
		setPositional();
		this.spotCutOff = spotCutOff;
		this.spotExponent = spotExponent;
	}

	/**
	 * Determines whether this light is a spot light
	 * @return is spot light? -> true or false
	 */
	public boolean isSpot()
	{
		return isPositional() && spotCutOff <= 90F;
	}

	/**
	 * Set the direction of the spot light
	 * @param x x-coordinate
	 * @param y y-coordinate
	 * @param z z-coordinate
	 */
	public void setSpotDirection(float x, float y, float z)
	{
		this.spotDirection.setTo(x, y, z);
	}

	/**
	 * Returns the direction of the spot light
	 * @return direction of the spotlight
	 */
	public Point3D getSpotDirection()
	{
		return spotDirection;
	}

	/**
	 * Sets the value for the spot exponent. Only use values in the 
	 * intervall from 0.0f - 128.0f. Values other than that will cause
	 * an openGl error
	 * @param value spot exponent (0.0f - 128.0f)
	 */
	public void setSpotExponent(float value)
	{
		this.spotExponent = value;
	}

	/**
	 * Returns the value for spot exponent
	 * @return spot exponent
	 */
	public float getSpotExponent()
	{
		return spotExponent;
	}

	/**
	 * Sets the RGBA color of the ambient light
	 * @param red amount of red
	 * @param green amount of green
	 * @param blue amount of blue
	 * @param alpha amount of alpha
	 */
	public void setColorAmbient(float red, float green, float blue, float alpha)
	{
		this.ambientColor[0] = red;
		this.ambientColor[1] = green;
		this.ambientColor[2] = blue;
		this.ambientColor[3] = alpha;
	}

	/**
	 * Returns the color of the ambient light
	 * @return color of ambient light
	 */
	public float[] getColorAmbient()
	{
		return ambientColor;
	}

	/**
	 * Sets the RGBA color of the diffuse light
	 * @param red amount of red
	 * @param green amount of green
	 * @param blue amount of blue
	 * @param alpha amount of alpha
	 */
	public void setColorDiffuse(float red, float green, float blue, float alpha)
	{
		this.diffuseColor[0] = red;
		this.diffuseColor[1] = green;
		this.diffuseColor[2] = blue;
		this.diffuseColor[3] = alpha;
	}

	/**
	 * Returns the color of the diffuse light
	 * @return color of diffuse light
	 */
	public float[] getColorDiffuese()
	{
		return diffuseColor;
	}

	/**
	 * Sets the RGBA color of the specular light
	 * @param red amount of red
	 * @param green amount of green
	 * @param blue amount of blue
	 * @param alpha amount of alpha
	 */
	public void setColorSpecular(float red, float green, float blue, float alpha)
	{
		this.specularColor[0] = red;
		this.specularColor[1] = green;
		this.specularColor[2] = blue;
		this.specularColor[3] = alpha;
	}

	/**
	 * Returns the color of the specular light
	 * @return color of specular light
	 */
	public float[] getColorSpecular()
	{
		return specularColor;
	}

	/**
	 * Enables (true) or disables (false) the light source.
	 * That completetly switches this light of. Ambient, diffuse and 
	 * specular parts of the light are off, too.
	 * @param activate set enabled? -> true or false
	 */
	public void setEnabled(boolean activate)
	{
		this.activated = activate;
	}

	/**
	 * Determins whether the light source is enabled
	 * @return is enabled? -> true or false
	 */
	public boolean isEnabled()
	{
		return activated;
	}

	/**
	 * Enables (true) or disables (false) the ambient light
	 * @param activate set enabled? -> true or false
	 */
	public void setEnabledAmbient(boolean activate)
	{
		this.activatedAmbient = activate;
	}

	/**
	 * Determins whether the ambient light is enabled
	 * @return is enabled? -> true or false
	 */
	public boolean isEnabledAmbient()
	{
		return activatedAmbient;
	}

	/**
	 * Enables (true) or disables (false) the diffuse light
	 * @param activate set enabled? -> true or false
	 */
	public void setEnabledDiffuse(boolean activate)
	{
		this.activatedDiffuse = activate;
	}

	/**
	 * Determins whether the diffuse light is enabled
	 * @return is enabled? -> true or false
	 */
	public boolean isEnabledDiffuse()
	{
		return activatedDiffuse;
	}

	/**
	 * Enables (true) or disables (false) the specular light
	 * @param activate set enabled? -> true or false
	 */
	public void setEnabledSpecular(boolean activate)
	{
		this.activatedSpecular = activate;
	}

	/**
	 * Determins whether the specular light is enabled
	 * @return is enabled? -> true or false
	 */
	public boolean isEnabledSpecular()
	{
		return activatedSpecular;
	}

	/**
	 * Sets the constant attenuation value. Calling this method set the
	 * other attenuation behaviors to zero
	 * @param factor constant attenuation factor
	 */
	public void setAttenuationConstant(float factor)
	{
		this.constAtten = factor;
		this.linearAtten = 0.0F;
		this.quadAtten = 0.0F;
	}

	/**
	 * Returns the constant attenuation value
	 * @return constant attenuation factor
	 */
	public float getAttenuationConstant()
	{
		return constAtten;
	}

	/**
	 * Sets the linear attenuation value. Calling this method set the
	 * other attenuation behaviors to zero
	 * @param factor linear attenuation factor
	 */
	public void setAttenuationLinear(float factor)
	{
		this.linearAtten = factor;
		this.constAtten = 0.0F;
		this.quadAtten = 0.0F;
	}

	/**
	 * Returns the linear attenuation value
	 * @return linear attenuation factor
	 */
	public float getAttenuationLinear()
	{
		return linearAtten;
	}

	/**
	 * Sets the quadric attenuation value. Calling this method set the
	 * other attenuation behaviors to zero
	 * @param factor quadric attenuation factor
	 */
	public void setAttenuationQuadratic(float factor)
	{
		this.quadAtten = factor;
		this.constAtten = 0.0F;
		this.linearAtten = 0.0F;
	}

	/**
	 * Returns the quadric attenuation
	 * @return quadric attenuation
	 */
	public float getAttenuationQuadratic()
	{
		return quadAtten;
	}

	/**
	 * Returns the according openGL light constant to the simple
	 * integer value specified as a parameter
	 * @param lightNr light number as a simple integer value
	 * @return openGL light constant
	 */
	private int getGLLightConstant(int lightNr)
	{
		int nr = 0;
		switch (lightNr)
		{
		case 0 : nr = GL.GL_LIGHT0;	break;
		case 1 : nr = GL.GL_LIGHT1;	break;
		case 2 : nr = GL.GL_LIGHT2;	break;
		case 3 : nr = GL.GL_LIGHT3;	break;
		case 4 : nr = GL.GL_LIGHT4;	break;
		case 5 : nr = GL.GL_LIGHT5;	break;
		case 6 : nr = GL.GL_LIGHT6;	break;
		case 7 : nr = GL.GL_LIGHT7;	break;
		}
		return nr;
	}

	/**
	 * Returns an simple integer value (0-7) representing the openGl constants
	 * GL.GL_LIGHT0 upto GL.GL_LIGHT7
	 * @return integer value instead of openGL constant
	 */
	public int getLightNumberFrom0to7()
	{
		int nr = 0;
		switch (lightNr)
		{
		case GL.GL_LIGHT0 : nr = 0;	break;
		case GL.GL_LIGHT1 :	nr = 1;	break;
		case GL.GL_LIGHT2 :	nr = 2;	break;
		case GL.GL_LIGHT3 :	nr = 3;	break;
		case GL.GL_LIGHT4 :	nr = 4;	break;
		case GL.GL_LIGHT5 :	nr = 5;	break;
		case GL.GL_LIGHT6 :	nr = 6;	break;
		case GL.GL_LIGHT7 :	nr = 7;	break;
		}
		return nr;
	}

	/**
	 * This method draws the light to the scene with all its properties
	 * @param gl
	 */
	public void draw(GL gl)
	{
		// only use the OpenGL commands, if the light is active
		if (this.activated)
		{
			gl.glEnable(GL.GL_LIGHTING);
			if (this.activatedAmbient)
			{
				gl.glLightfv(lightNr, GL.GL_AMBIENT, this.ambientColor,0);
			}
			if (this.activatedDiffuse)
			{
				gl.glLightfv(lightNr, GL.GL_DIFFUSE, this.diffuseColor,0);
			}
			if (this.activatedSpecular)
			{
				gl.glLightfv(this.lightNr, GL.GL_SPECULAR, this.specularColor,0);
			}
			gl.glLightf(this.lightNr, GL.GL_CONSTANT_ATTENUATION, this.constAtten);
			gl.glLightf(this.lightNr, GL.GL_LINEAR_ATTENUATION, this.linearAtten);
			gl.glLightf(this.lightNr, GL.GL_QUADRATIC_ATTENUATION, this.quadAtten);
			gl.glLightfv(this.lightNr, GL.GL_POSITION, this.position,0);
			
			// only use this commands in case of an spot light
			if (this.isSpot())
			{
				gl.glLightf(this.lightNr, GL.GL_SPOT_CUTOFF, this.spotCutOff);
				gl.glLightfv(this.lightNr, GL.GL_SPOT_DIRECTION, this.spotDirection.getPoint(),0);
				gl.glLightf(this.lightNr, GL.GL_SPOT_EXPONENT, this.spotExponent);
			}
			
			// finally enable the light
			gl.glEnable(this.lightNr);					
		}
		else
		{
			gl.glDisable(this.lightNr);			
		}
	}
	
	/**
	 * Sets the color of the global ambient background light. Set all
	 * parameters to 0.0f, if you would like to archieve perfect darkness
	 * in your scene, while all other light sources must be disabled, too.
	 * @param red amount of red
	 * @param green amount of green
	 * @param blue amount of blue
	 * @param alpha alpha-value
	 */
	public static void setGlobalAmbientLight(GL gl, float red, float green, float blue, float alpha)
	{
		float[] globalAmbient = {red,green,blue,alpha};
		gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, globalAmbient,0);
	}
}
