// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "stdopengl.h"

#include "driver_opengl.h"
#include "nel/3d/light.h"

namespace NL3D
{


// ***************************************************************************
uint	CDriverGL::getMaxLight () const
{
	H_AUTO_OGL(CDriverGL_getMaxLight )
	// return min(maxLight supported by openGL, MaxLight=8).
	return _MaxDriverLight;
}


// ***************************************************************************
void	CDriverGL::setLight (uint8 num, const CLight& light)
{
	H_AUTO_OGL(CDriverGL_setLight )
	// bkup real light, for lightmap dynamic lighting purpose
	if(num==0)
	{
		_UserLight0= light;
		// because the GL setup change, must dirt lightmap rendering
		_LightMapDynamicLightDirty= true;
	}

	setLightInternal(num, light);
}


// ***************************************************************************
void	CDriverGL::setLightInternal(uint8 num, const CLight& light)
{
	H_AUTO_OGL(CDriverGL_setLightInternal)
	// Check light count is good
//	nlassert (num<_MaxDriverLight);

	// Set the light
	if (num<_MaxDriverLight)
	{
		// GL light number
		GLenum lightNum=(GLenum)(GL_LIGHT0+num);

		// Get light mode
		CLight::TLightMode mode=light.getMode ();

		// Copy the mode
		_LightMode[num]=mode;

		// Set the ambiant color
		GLfloat colorGL[4];
		CRGBA colorNeL=light.getAmbiant ();
		colorGL[0]=(float)colorNeL.R/255.f;
		colorGL[1]=(float)colorNeL.G/255.f;
		colorGL[2]=(float)colorNeL.B/255.f;
		colorGL[3]=1.f;
		glLightfv (lightNum, GL_AMBIENT, colorGL);

		// Set the diffuse color
		colorNeL=light.getDiffuse ();
		colorGL[0]=(float)colorNeL.R/255.f;
		colorGL[1]=(float)colorNeL.G/255.f;
		colorGL[2]=(float)colorNeL.B/255.f;
		colorGL[3]=1.f;
		glLightfv (lightNum, GL_DIFFUSE, colorGL);

		// Set the specular color
		colorNeL=light.getSpecular ();
		// don't know why, but with ATI cards, specular of 0 causes incorrect rendering (random specular is added)
		if (_Extensions.ATITextureEnvCombine3)
		{
			// special case for ATI (there will be some specular, but there's a bug otherwise)
			colorGL[0]=std::max(1.f / 1024.f, (float)colorNeL.R/255.f);
			colorGL[1]=std::max(1.f / 1024.f, (float)colorNeL.G/255.f);
			colorGL[2]=std::max(1.f / 1024.f, (float)colorNeL.B/255.f);
			colorGL[3]=1.f;
		}
		else
		{
			colorGL[0]=(float)colorNeL.R/255.f;
			colorGL[1]=(float)colorNeL.G/255.f;
			colorGL[2]=(float)colorNeL.B/255.f;
			colorGL[3]=1.f;
		}
		glLightfv (lightNum, GL_SPECULAR, colorGL);

		// Set light attenuation
		glLightf (lightNum, GL_CONSTANT_ATTENUATION, light.getConstantAttenuation());
		glLightf (lightNum, GL_LINEAR_ATTENUATION, light.getLinearAttenuation());
		glLightf (lightNum, GL_QUADRATIC_ATTENUATION, light.getQuadraticAttenuation());

		// Set the position
		if ((mode==CLight::DirectionalLight)||(mode==CLight::SpotLight))
		{
			// Get the direction of the light
			_WorldLightDirection[num]=light.getDirection ();
		}

		if (mode!=CLight::DirectionalLight)
		{
			// Get the position of the light
			_WorldLightPos[num]=light.getPosition ();
		}

		if (mode==CLight::SpotLight)
		{
			// Get the exponent of the spot
			float exponent=light.getExponent ();

			// Set it
			glLightf (lightNum, GL_SPOT_EXPONENT, exponent);

			// Get the cutoff of the spot
			float cutoff=180.f*(light.getCutoff ()/(float)NLMISC::Pi);

			// Set it
			glLightf (lightNum, GL_SPOT_CUTOFF, cutoff);
		}
		else
		{
			// Disactive spot properties
			glLighti (lightNum, GL_SPOT_CUTOFF, 180);
			glLighti (lightNum, GL_SPOT_EXPONENT, 0);
		}

		// Flag this light as dirt.
		_LightDirty[num]= true;

		// dirt the lightSetup and hence the render setup
		_LightSetupDirty= true;
		_RenderSetupDirty=true;
	}
}

// ***************************************************************************
void	CDriverGL::enableLight (uint8 num, bool enable)
{
	H_AUTO_OGL(CDriverGL_enableLight )
	// User call => set the User flag
	// Geforce FX tmp fix
	if (_Extensions.IsGeforceFXOrAbove && num >=3) return;
	if(num<_MaxDriverLight)
	{
		_UserLightEnable[num]= enable;
	}

	// enable the light in GL
	enableLightInternal(num, enable);

	// because the GL setup has changed, must dirt lightmap rendering
	_LightMapDynamicLightDirty= true;
}


// ***************************************************************************
void	CDriverGL::enableLightInternal(uint8 num, bool enable)
{
	H_AUTO_OGL(CDriverGL_enableLightInternal)
	// Check light count is good
	//	nlassert (num<_MaxDriverLight);

	// Enable glLight
	if (num<_MaxDriverLight)
	{
		_DriverGLStates.enableLight(num, enable);

		// If this light is dirty, and reenabled, then it must be refresh at next render => set the global flag.
		if (enable && _LightDirty[num])
		{
			_LightSetupDirty= true;
			_RenderSetupDirty= true;
		}
	}
}


// ***************************************************************************

void	CDriverGL::setAmbientColor (CRGBA color)
{
	H_AUTO_OGL(CDriverGL_setAmbientColor )
	// Gl array
	GLfloat array[4];
	array[0]=(float)color.R/255.f;
	array[1]=(float)color.G/255.f;
	array[2]=(float)color.B/255.f;
	array[3]=1.f;

	// Set the color
	glLightModelfv (GL_LIGHT_MODEL_AMBIENT, array);
}


// ***************************************************************************
void				CDriverGL::cleanLightSetup ()
{
	H_AUTO_OGL(CDriverGL_cleanLightSetup )
	// Should be dirty
	nlassert (_LightSetupDirty);

	// First light
	bool first=true;

	// For each lights
	for (uint i=0; i<_MaxDriverLight; i++)
	{
		// Is this light enabled and dirty?
		if (_DriverGLStates.isLightEnabled(i) && _LightDirty[i])
		{
			// If first light
			if (first)
			{
				first=false;

				// Push the matrix
				glPushMatrix ();

				// Load the view matrix
				glLoadMatrixf (_ViewMtx.get());
			}

			// Light is directionnal ?
			if (_LightMode[i]==(uint)CLight::DirectionalLight)
			{
				// GL vector
				GLfloat vectorGL[4];

				// Set the GL array
				vectorGL[0]=-_WorldLightDirection[i].x;
				vectorGL[1]=-_WorldLightDirection[i].y;
				vectorGL[2]=-_WorldLightDirection[i].z;
				vectorGL[3]=0.f;

				// Set it
				glLightfv ((GLenum)(GL_LIGHT0+i), (GLenum)GL_POSITION, vectorGL);
			}

			// Spotlight ?
			if (_LightMode[i]==(uint)CLight::SpotLight)
			{
				// GL vector
				GLfloat vectorGL[4];

				// Set the GL array
				vectorGL[0]=_WorldLightDirection[i].x;
				vectorGL[1]=_WorldLightDirection[i].y;
				vectorGL[2]=_WorldLightDirection[i].z;

				// Set it
				glLightfv ((GLenum)(GL_LIGHT0+i), (GLenum)GL_SPOT_DIRECTION, vectorGL);
			}

			// Position
			if (_LightMode[i]!=(uint)CLight::DirectionalLight)
			{
				// GL vector
				GLfloat vectorGL[4];

				// Set the GL array
				// Must Substract CameraPos, because ViewMtx may not be the exact view.
				vectorGL[0]=_WorldLightPos[i].x - _PZBCameraPos.x;
				vectorGL[1]=_WorldLightPos[i].y - _PZBCameraPos.y;
				vectorGL[2]=_WorldLightPos[i].z - _PZBCameraPos.z;
				vectorGL[3]=1.f;

				// Set it
				glLightfv ((GLenum)(GL_LIGHT0+i), (GLenum)GL_POSITION, vectorGL);
			}

			// Cleaned!
			_LightDirty[i]= false;
		}
	}

	// Pop old matrix
	if (!first)
		glPopMatrix ();

	// Clean flag
	_LightSetupDirty=false;
}


// ***************************************************************************
void			CDriverGL::setLightMapDynamicLight (bool enable, const CLight& light)
{
	H_AUTO_OGL(CDriverGL_setLightMapDynamicLight )
	// just store, for future setup in lightmap material rendering
	_LightMapDynamicLightEnabled= enable;
	_LightMapDynamicLight= light;
	_LightMapDynamicLightDirty= true;
}


// ***************************************************************************
void			CDriverGL::setupLightMapDynamicLighting(bool enable)
{
	H_AUTO_OGL(CDriverGL_setupLightMapDynamicLighting)
	// start lightmap dynamic lighting
	if(enable)
	{
		// disable all lights but the 0th.
		for(uint i=1;i<_MaxDriverLight;i++)
			enableLightInternal(i, false);

		// if the dynamic light is really enabled
		if(_LightMapDynamicLightEnabled)
		{
			// then setup and enable
			setLightInternal(0, _LightMapDynamicLight);
			enableLightInternal(0, true);
		}
		// else just disable also the light 0
		else
		{
			enableLightInternal(0, false);
		}

		// ok it has been setup
		_LightMapDynamicLightDirty= false;
	}
	// restore old lighting
	else
	{
		// restore the light 0
		setLightInternal(0, _UserLight0);

		// restore all standard light enable states
		for(uint i=0;i<_MaxDriverLight;i++)
			enableLightInternal(i, _UserLightEnable[i]);
	}

	// in all case, must refresh render setup, cause lighting may be modified
	refreshRenderSetup();
}


} // NL3D