#include "GLphongRender.h"
#include "streams\ErrorStream.h"

extern ExceptionStream< ostream&, ContinueFunctor  >		KODIAK_WARNING;

GLPhongRender::GLPhongRender( Context &context, CGprogram vertexShader, CGprogram fragmentShader  /*, unsigned int shadowTexture, unsigned int shadowTextureRes*/ ):Render(context),vertexShader(vertexShader), fragmentShader(fragmentShader) /*, shadowMapRender( context, shadowTexture, shadowTextureRes, shadowTextureRes), lightShadower( context, shadowTexture, shadowTextureRes)*/
{
	if ( vertexShader == 0 || fragmentShader == 0 )
	{
		KODIAK_WARNING<<"Vertex or fragment shader program are NULL!!!";
		return;
	}

	//POZNAMKA: musi zvenku dostat shader ktery pouziva prate tyto parametry 
	this->vertexShader.insertParam("modelMatrix", MATRIX4X4_FLOAT);
	this->vertexShader.insertParam("modelViewProj", MATRIX4X4_FLOAT);
	this->vertexShader.insertParam("textureMatrix", MATRIX4X4_FLOAT);

	this->fragmentShader.insertParam("falloff", ARRAY_FLOAT1);
	this->fragmentShader.insertParam("spotType", ARRAY_FLOAT1);
	this->fragmentShader.insertParam("directional", ARRAY_FLOAT1);
	this->fragmentShader.insertParam("fillLight", ARRAY_FLOAT1);

	this->fragmentShader.insertParam("radiance", ARRAY_FLOAT3);
	this->fragmentShader.insertParam("lightPosition", ARRAY_FLOAT3);
	this->fragmentShader.insertParam("lightDirection", ARRAY_FLOAT3);
	this->fragmentShader.insertParam("eyePosition", FLOAT3);
	this->fragmentShader.insertParam("beamWidth", ARRAY_FLOAT1);
	this->fragmentShader.insertParam("cutOffAngle", ARRAY_FLOAT1);
	this->fragmentShader.insertParam("lightCount", FLOAT1);
	this->fragmentShader.insertParam("Pa", FLOAT3);		//ambient
	this->fragmentShader.insertParam("Pd", FLOAT3);		//diffuse
	this->fragmentShader.insertParam("Ps", FLOAT3);		//speculat
	this->fragmentShader.insertParam("exponent", FLOAT1);
	this->fragmentShader.insertParam("useTexture", FLOAT1);
	
}

#ifdef NOTIMPLEMENTED
void GLPhongRender::calculateShadowMapping( vector<PhongLight*> &lights, vector<Model*> &models )
{
	//vytvoreni SHADOW MAPY a predpocitani casti TEXTURE MATRIX=====================
	//Camera<float> tmpCam(0,95,0,0,-1,0,1,0,0); //TODO ...u omni light je to slozitejsi!!!

	ShadowMappingConfiguration dstConfig;

	for (int i=0; i< lights.size(); ++i)
	{
		switch ( lights[i]->getLightType())
		{
		case SPOT_PHONG:
		//fall throught;

		case SPOT_PHONG_VRML:
			ShadowMappingConfigurator::configurate(  *((SpotLightPhongVRML*)lights[0]), MAKE_VECTOR<float>(1,0,0), dstConfig );
			//this->lightShadower.calculateShadowMap( dstConfig, models);
			break;
		default:
			assert( !"zatim je podporovan pouze spot light stin");

		}
	
		assert(i<1 && "zatim je podporovano pouze 1 svetelny zdroj pro shadow mapping");
	}
	
	/*
	this->shadowMapRender.beginFrame(tmpCam);
	this->shadowMapRender.renderModels(models);
	this->shadowMapRender.endFrame(false);
	*/


//==============================================================================
}
#endif

void GLPhongRender::renderLights( vector<Light*> &lights, const vector< CTriCol* >& lightColors )
{
	//WARNING: this function doest use shaders -> shaders have to be deactivated
	//cgGLBindProgram( NULL ); //context Null pointer warning!

	assert ( lights.size() == lightColors.size() );

	this->bind();

	for ( int i=0; i < lights.size(); ++i )
	{
		renderLight( lights[i], lightColors[i] );
	}
}

void GLPhongRender::renderLights( vector<Light*> &lights, const CTriCol* color )
{
	//WARNING: this function doest use shaders -> shaders have to be deactivated
	//cgGLBindProgram( NULL ); //context Null pointer warning!

	this->bind();

	for ( int i=0; i < lights.size(); ++i )
	{
		renderLight( lights[i], color );
	}
}

void GLPhongRender::renderPhongModels( vector<Light*> &lights, vector<Model*> &models )
{
	glPushMatrix();
	this->bind();	//bind + sets viewMatrix
	
	//Bind shaders
	cgGLBindProgram( this->vertexShader.getShader() );
	cgGLBindProgram( this->fragmentShader.getShader());

	float cameraPosArr[] = { this->camera.pos.x(), this->camera.pos.y(), this->camera.pos.z()};
	this->fragmentShader.setParam("eyePosition", cameraPosArr );

	float tmp[3];
	float lightCount=0;

	for (int i=0; i< lights.size(); ++i)
	{
		switch (lights[i]->getLightType())
		{

		case SPOT_PHONG:
		{
			SpotLightPhong *spotLightBasic=(SpotLightPhong*)lights[i];

			tmp[0]=spotLightBasic->color[0] * spotLightBasic->intensity;
			tmp[1]=spotLightBasic->color[1] * spotLightBasic->intensity;
			tmp[2]=spotLightBasic->color[2] * spotLightBasic->intensity;

			this->fragmentShader.setArrayParamOnIndex("radiance",i , tmp);

			tmp[0]=spotLightBasic->direction.x();
			tmp[1]=spotLightBasic->direction.y();
			tmp[2]=spotLightBasic->direction.z();

			this->fragmentShader.setArrayParamOnIndex("lightDirection",i , tmp);
			this->fragmentShader.setArrayParamOnIndex("falloff", i, &spotLightBasic->attenuationExp );
			this->fragmentShader.setArrayParamOnIndex("directional", i, &spotLightBasic->directionalExp);
			this->fragmentShader.setArrayParamOnIndex("spotType", i, &SPOT_BASIC);
			this->fragmentShader.setArrayParamOnIndex("fillLight", i, &NO_FILL_LIGHT);

			tmp[0]=spotLightBasic->position.x();
			tmp[1]=spotLightBasic->position.y();
			tmp[2]=spotLightBasic->position.z();

			this->fragmentShader.setArrayParamOnIndex("lightPosition",i , tmp);

			++lightCount;
		}
		break;

		case SPOT_PHONG_VRML:
		{
			SpotLightPhongVRML *spotLightVRML=(SpotLightPhongVRML*)lights[i];

			tmp[0]=spotLightVRML->color[0] * spotLightVRML->intensity;
			tmp[1]=spotLightVRML->color[1] * spotLightVRML->intensity;
			tmp[2]=spotLightVRML->color[2] * spotLightVRML->intensity;

			this->fragmentShader.setArrayParamOnIndex("radiance",i , tmp);

			tmp[0]=spotLightVRML->direction.x();
			tmp[1]=spotLightVRML->direction.y();
			tmp[2]=spotLightVRML->direction.z();

			this->fragmentShader.setArrayParamOnIndex("lightDirection",i , tmp);
			this->fragmentShader.setArrayParamOnIndex("beamWidth", i, &spotLightVRML->innerConeCos);
			this->fragmentShader.setArrayParamOnIndex("cutOffAngle", i, &spotLightVRML->outerConeCos);
			this->fragmentShader.setArrayParamOnIndex("falloff", i, &spotLightVRML->attenuationExp );
			this->fragmentShader.setArrayParamOnIndex("spotType", i, &SPOT_VRML);
			this->fragmentShader.setArrayParamOnIndex("fillLight", i, &NO_FILL_LIGHT);

			tmp[0]=spotLightVRML->position.x();
			tmp[1]=spotLightVRML->position.y();
			tmp[2]=spotLightVRML->position.z();

			this->fragmentShader.setArrayParamOnIndex("lightPosition",i , tmp);

			++lightCount;
		}
		break;

		case OMNI_PHONG:
		{
			OmniLightPhong *omniLight=(OmniLightPhong*)lights[i];

			tmp[0]=omniLight->color[0] * omniLight->intensity;
			tmp[1]=omniLight->color[1] * omniLight->intensity;
			tmp[2]=omniLight->color[2] * omniLight->intensity;

			this->fragmentShader.setArrayParamOnIndex("radiance",i , tmp );
			this->fragmentShader.setArrayParamOnIndex("falloff", i, &omniLight->attenuationExp );
			this->fragmentShader.setArrayParamOnIndex("spotType", i, &NO_SPOT);
			this->fragmentShader.setArrayParamOnIndex("fillLight", i, &NO_FILL_LIGHT);
			
			tmp[0]=omniLight->position.x();
			tmp[1]=omniLight->position.y();
			tmp[2]=omniLight->position.z();

			this->fragmentShader.setArrayParamOnIndex("lightPosition",i , tmp);

			++lightCount;
		}
		break;

		case DIRECTIONAL_PHONG:
			//fall through ( will be rendered like Rectangle light for now )

		case RECTANGLE_PHONG:
		{
			//until Rectangle light shader not implemented use OmniLight
			RectangleLightPhong *rectLight=(RectangleLightPhong*)lights[i];

			tmp[0]=rectLight->color[0] * rectLight->intensity;
			tmp[1]=rectLight->color[1] * rectLight->intensity;
			tmp[2]=rectLight->color[2] * rectLight->intensity;

			this->fragmentShader.setArrayParamOnIndex("radiance",i , tmp );
			this->fragmentShader.setArrayParamOnIndex("falloff", i, &rectLight->attenuationExp );
			this->fragmentShader.setArrayParamOnIndex("spotType", i, &NO_SPOT);
			this->fragmentShader.setArrayParamOnIndex("fillLight", i, &NO_FILL_LIGHT);
			
			tmp[0]=rectLight->position.x();
			tmp[1]=rectLight->position.y();
			tmp[2]=rectLight->position.z();

			this->fragmentShader.setArrayParamOnIndex("lightPosition",i , tmp);

			++lightCount;
		}
		break;

		default:
			assert("Unsupported light type");
		break;
		}
	}
	

	this->fragmentShader.setParam("lightCount", &lightCount );

	for (int i=0; i<models.size(); ++i)
	{
		if ( models[i] == NULL )
		{
			KODIAK_WARNING<<"void GLPhongRender::renderPhongModels( vector<Light*> &lights, vector<Model*> &models ) null pointer model - ignored";
			continue;
		}

		glPushMatrix();
			
			Matrix4X4<float> modelMatrix;
			models[i]->getModelMatrix(modelMatrix);

			this->vertexShader.setMatrixParam("modelMatrix", modelMatrix.getPtr());

			glMatrixMode(GL_MODELVIEW);			
			glMultMatrixf( modelMatrix.getPtr() );

			for (int n=0; n<models[i]->getMeshes().size(); ++n)
			{
				if (models[i]->getMeshes()[n].material!=NULL)
				{
					assert(models[i]->getMeshes()[n].material->getMaterialType()== PHONG && "Only phong material is supported" );

					PhongMaterial *material= (PhongMaterial*) models[i]->getMeshes()[n].material;

					this->fragmentShader.setParam("Pa", material->ambient);
					this->fragmentShader.setParam("Pd", material->diffuse);
					this->fragmentShader.setParam("Ps", material->specular);
					this->fragmentShader.setParam("exponent", &material->specular_exp ); 
				}
				else
				{
					float def[]={0,0,0};
					float defS=32;
					
					this->fragmentShader.setParam("Pd", def);
					this->fragmentShader.setParam("Ps", def);
					this->fragmentShader.setParam("exponent", &defS);
				}
				
				//use texture
				float useTexture[1];
				if ( models[i]->getMeshes()[n].textures.empty() )	//no tex coord availible
				{
					useTexture[0]=0.0; //dont use texture
				}
				else
				{
					useTexture[0]=1.0; //use texture
				}

				this->fragmentShader.setParam("useTexture", useTexture);
				VBOmanager::renderVBO(models[i]->getMeshes()[n].vbo_data, models[i]->getMeshes()[n].textures );

			}
		glPopMatrix();
	}

	glPopMatrix();
}
void GLPhongRender::beginFrame(const Camera<double>& camera)
{
	Render::beginFrame(camera);
}

bool GLPhongRender::isVertexShaderValid(void)
{
	return this->vertexShader.getShader() != NULL;
}

bool GLPhongRender::isFragmentShaderValid(void)
{
	return this->fragmentShader.getShader() != NULL;
}
