#include "Light.h"


#include "cgSupport/GPUcomputing.h"

GLUquadric* quadric=gluNewQuadric();

//global variables to set light size & poly count for point lights
int LIGHT_QUALITY = 15;
float LIGHT_SIZE = 2.0;

Phong::Phong( const Phong& light)
{
	for (int i=0; i<3; ++i)
	{
		this->color[i] = light.color[i];
	}

	this->attenuationExp = light.attenuationExp;
	this->intensity = light.intensity;
}

void renderLight(const Light* light, const CTriCol *color)
{
	switch ( light->getLightType() )
	{
	case OMNI_PHONG:
		renderOmniLight( (OmniLightPhong*) light, color );
		break;

	case SPOT_PHONG_VRML:
		renderSpotLight( (SpotLightPhongVRML*) light, color );
		break;

	case SPOT_PHONG:
		renderSpotLight( (SpotLightPhong*) light, color );
		break;

	case RECTANGLE_PHONG:
		renderRectangleLight( (RectangleLightPhong*) light, color );
		break;

	case DIRECTIONAL_PHONG:
		renderDirectionalLight( (DirectionalLightPhong*) light, color );
		break;

	default:
		assert(!" Unkown light type" );
		break;
	}	
}

void renderSpotLight( const SpotLightPhongAbstract *light, const CTriCol *color)
{
	if (color == NULL)
	{
		//use light color (implicit color request)
		glColor3f(light->color[0], light->color[1], light->color[2]);
	}
	else
	{
		//use given color (explicit color request)
		glColor3f( color->r, color->g, color->b );
	}

	glPushMatrix();
		glBegin(GL_LINES);
			glVertex3f( light->position.x(), light->position.y(), light->position.z() );
			glVertex3f(  light->position.x() +  light->direction.x() * 15*LIGHT_SIZE, light->position.y() +  light->direction.y() * 15*LIGHT_SIZE , light->position.z() + light->direction.z() * 15*LIGHT_SIZE );
		glEnd();	
	
		glTranslatef(light->position.x(), light->position.y(), light->position.z() );
		gluSphere(	quadric, LIGHT_SIZE, LIGHT_QUALITY, LIGHT_QUALITY);
	glPopMatrix();

}
void renderOmniLight( const OmniLightPhong *light, const CTriCol *color)
{
	if (color == NULL)
	{
		//use light color (implicit color request)
		glColor3f(light->color[0], light->color[1], light->color[2]);
	}
	else
	{
		//use given color (explicit color request)
		glColor3f( color->r, color->g, color->b );
	}

	glPushMatrix();
		glTranslatef(light->position.x(), light->position.y(), light->position.z() );
		gluSphere(	quadric, LIGHT_SIZE, LIGHT_QUALITY, LIGHT_QUALITY);
	glPopMatrix();
}
void renderRectangleLight(const RectangleLightPhong* light, const CTriCol *color, bool wire)
{
	if (color == NULL)
	{
		//use light color (implicit color request)
		glColor3f(light->color[0], light->color[1], light->color[2]);
	}
	else
	{
		//use given color (explicit color request)
		glColor3f( color->r, color->g, color->b );
	}

	Point_3<float> vertex;

	if ( wire )
	{
		glBegin(GL_LINES);
				//edge1
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position - light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//edge2
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position - light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//edge3
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position + light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//edge4
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//uhlopricka1
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position - light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//uhlopricka2
				vertex= light->position - light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position + light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

		glEnd();

	}
	else
	{
		glBegin(GL_QUADS);
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				vertex= light->position - light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				vertex= light->position - light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				vertex= light->position + light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
		glEnd();
	}
}

void renderDirectionalLight(const DirectionalLightPhong* light, const CTriCol *color, bool wire)
{
	if (color == NULL)
	{
		//use light color (implicit color request)
		glColor3f(light->color[0], light->color[1], light->color[2]);
	}
	else
	{
		//use given color (explicit color request)
		glColor3f( color->r, color->g, color->b );
	}

	Point_3<float> vertex;

	Vector_3<float> direction;
	direction.calculate_cross_product( light->vec1, light->vec2 );
	direction.normalize();

	glBegin(GL_LINES);
		glVertex3f( light->position.x(), light->position.y(), light->position.z() );
		glVertex3f(  light->position.x() +  direction.x() * 15, light->position.y() +  direction.y() * 15 , light->position.z() + direction.z() * 15 );
	glEnd();	

	if ( wire )
	{
		glBegin(GL_LINES);
				//edge1
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position - light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//edge2
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position - light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//edge3
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position + light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//edge4
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//uhlopricka1
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position - light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				//uhlopricka2
				vertex= light->position - light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
				vertex= light->position + light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

		glEnd();

	}
	else
	{
		glBegin(GL_QUADS);
				vertex= light->position + light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				vertex= light->position - light->vec1 + light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				vertex= light->position - light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );

				vertex= light->position + light->vec1 - light->vec2;
				glVertex3f( vertex.x(), vertex.y(), vertex.z() );
		glEnd();
	}
}

bool LightManager::insertLight( const string &name, Light* light )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() )
	{
		return false;	//light with such name is already in database -> insertion failed
	}


	this->lights.insert( make_pair( name, light ) );

	return true;	//insertion OK
}


OmniLightPhong* LightManager::createOmniLight( const string &name, const OmniLightPhong& omniLight)
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() )
	{
		return NULL;	//light with such name is already in database -> insertion failed
	}
	

	OmniLightPhong *omni = new OmniLightPhong();
	
	*omni=omniLight;

	this->lights.insert( it, make_pair( name, omni ) );

	return omni;
}

SpotLightPhong* LightManager::createSpotLight( const string &name, const SpotLightPhong& spotLight)
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() )
	{
		return NULL;	//light with such name is already in database -> insertion failed
	}

	SpotLightPhong *spot = new SpotLightPhong();
	
	*spot=spotLight;

	this->lights.insert( it, make_pair( name, spot ) );

	return spot;
}

SpotLightPhongVRML* LightManager::createSpotLightVRML( const string &name, const SpotLightPhongVRML& spotLightVRML)
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() )
	{
		return NULL;	//light with such name is already in database -> insertion failed
	}

	SpotLightPhongVRML *spot = new SpotLightPhongVRML();
	
	*spot=spotLightVRML;

	this->lights.insert( it, make_pair( name, spot ) );

	return spot;
}

RectangleLightPhong* LightManager::createRectangleLight( const string &name, const RectangleLightPhong& rectangleLight)
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() )
	{
		return NULL;	//light with such name is already in database -> insertion failed
	}
	

	RectangleLightPhong *rect = new RectangleLightPhong();
	
	*rect=rectangleLight;

	this->lights.insert( it, make_pair( name, rect ) );

	return rect;
}

Light*	LightManager::getLight( const string &name )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() )
	{
		return it->second;
	}

	return NULL;	//light with given name not found
}

PhongLight*	LightManager::getPhongLight( const string &name )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() && ( it->second->getLightType() == OMNI_PHONG || it->second->getLightType() == SPOT_PHONG_VRML || it->second->getLightType() == SPOT_PHONG || it->second->getLightType() == RECTANGLE_PHONG ) )
	{
		return (PhongLight*) it->second;
	}

	return NULL;	//light with given name not found
}

OmniLightPhong*	LightManager::getOmniLight( const string &name )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() && it->second->getLightType() == OMNI_PHONG )
	{
		return (OmniLightPhong* ) it->second;
	}

	return NULL;	//light with given name not found
}

SpotLightPhongVRML* LightManager::getSpotLightVRML( const string &name )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() && it->second->getLightType() == SPOT_PHONG_VRML )
	{
		return (SpotLightPhongVRML* ) it->second;
	}

	return NULL;	//light with given name not found
}

SpotLightPhong* LightManager::getSpotLight( const string &name )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() && it->second->getLightType() == SPOT_PHONG )
	{
		return (SpotLightPhong* ) it->second;
	}

	return NULL;	//light with given name not found
}

RectangleLightPhong* LightManager::getRectangleLight( const string &name )
{
	map<string, Light* >::iterator it =this->lights.find( name );

	if ( it!= this->lights.end() && it->second->getLightType() == RECTANGLE_PHONG )
	{
		return (RectangleLightPhong*) it->second;
	}

	return NULL;	//light with given name not found
}

vector< Light* > LightManager::getLightArray(void)
{
	vector< Light* > lightArr;
	
	for ( map<string, Light* >::iterator it=this->lights.begin(); it!= this->lights.end(); ++it)
	{
		lightArr.push_back( (PhongLight*) it->second );
	}

	return lightArr;
}

LightManager::iterator LightManager::begin(void)
{
	return this->lights.begin();
}
LightManager::const_iterator LightManager::begin(void) const
{
	return this->lights.begin();
}

LightManager::iterator LightManager::end(void)
{
	return this->lights.end();
}
LightManager::const_iterator LightManager::end(void) const
{
	return this->lights.end();
}
bool LightManager::empty(void) const
{
	return this->lights.empty();
}
unsigned int LightManager::size(void) const
{
	return this->lights.size();
}


LightManager::~LightManager(void)
{
	for ( map<string, Light* >::iterator it=this->lights.begin(); it!= this->lights.end(); ++it)
	{
		delete it->second;
	}
	this->lights.clear();
}

LightManager::LightManager(const LightManager& lightManager)
{
	*this = lightManager;
}
LightManager& LightManager::operator=(const LightManager& lightManager )
{
	for ( map<string, Light* >::iterator it=this->lights.begin(); it!= this->lights.end(); ++it)
	{
		delete it->second;
	}
	this->lights.clear();

	for ( map<string, Light* >::const_iterator it = lightManager.lights.begin(); it!= lightManager.lights.end(); ++it)
	{
		Light *lightCopy = NULL;

		switch ( it->second->getLightType() )
		{
		case SPOT_PHONG_VRML:
			lightCopy = new SpotLightPhongVRML( *((SpotLightPhongVRML*) it->second) );
			break;

		case SPOT_PHONG:
			lightCopy = new SpotLightPhong( *((SpotLightPhong*) it->second) );
			break;

		case OMNI_PHONG:
			lightCopy = new OmniLightPhong( *((OmniLightPhong*) it->second) );
			break;

		case RECTANGLE_PHONG:
			lightCopy = new RectangleLightPhong( *((RectangleLightPhong*) it->second) );
			break;
	
		default:
			assert(!" Unkown light type!");
			continue;
		}

		this->lights.insert( make_pair(it->first, lightCopy ) );
	}

	return *this;
}




LightType LightWrapper::getLightSubType() const
{
	return this->light->getLightType();
}

LightType LightWrapper::getLightType() const
{
	return WRAPPER_LIGHT;
}

LightWrapper::~LightWrapper(void)
{
	if ( this->light !=NULL)
	{
		delete this->light;
	}
}

LightWrapper& LightWrapper::operator=(const LightWrapper& light)
{
	
	//FIXME : memory leak if something was allocated
	if ( this->light !=NULL)
	{
		delete this->light;
	}
	
	if ( light.light ==NULL)
	{
		this->light=NULL;
		return *this;
	}

	switch ( light.light->getLightType() )
	{
		case SPOT_PHONG:
		{
			this->light = new SpotLightPhong();
			*(SpotLightPhong*)this->light = *(SpotLightPhong*)light.light;
		}
		break;

		case SPOT_PHONG_VRML:
			assert( !"Unsupported light type" );
		break;

		case OMNI_PHONG:
		{
			this->light = new OmniLightPhong();
			* (OmniLightPhong*)this->light = * (OmniLightPhong*)light.light;
		}
		break;

		case RECTANGLE_PHONG:
		{
			this->light = new RectangleLightPhong();
			*(RectangleLightPhong*)this->light = *(RectangleLightPhong*)light.light;
		}
		break;

		case DIRECTIONAL_PHONG:
		{
			this->light = new DirectionalLightPhong();
			*(DirectionalLightPhong*)this->light = *(DirectionalLightPhong*)light.light;
		}
		break;

		default:
			assert( !"Unexpected light type" );
			break;
	}

	return *this;
}
LightWrapper::LightWrapper(const LightWrapper& light)
{
	if ( light.light ==NULL)
	{
		this->light=NULL;
		return;
	}

	switch ( light.light->getLightType() )
	{
		case SPOT_PHONG:
		{
			this->light = new SpotLightPhong();
			*(SpotLightPhong*)this->light = *(SpotLightPhong*)light.light;
		}
		break;

		case SPOT_PHONG_VRML:
			assert( !"Unsupported light type" );
		break;

		case OMNI_PHONG:
		{
			this->light = new OmniLightPhong();
			* (OmniLightPhong*)this->light = * (OmniLightPhong*)light.light;
		}
		break;

		case RECTANGLE_PHONG:
		{
			this->light = new RectangleLightPhong();
			*(RectangleLightPhong*)this->light = *(RectangleLightPhong*)light.light;
		}
		break;

		case DIRECTIONAL_PHONG:
		{
			this->light = new DirectionalLightPhong();
			*(DirectionalLightPhong*)this->light = *(DirectionalLightPhong*)light.light;
		}
		break;

		default:
			assert( !"Unexpected light type" );
			break;
	}

}
LightWrapper::LightWrapper( void )
{
	this->light = NULL;
}
LightWrapper::LightWrapper( const Light& light )
{
	switch ( light.getLightType() )
	{
		case SPOT_PHONG:
			this->light = new SpotLightPhong( *(SpotLightPhong*) &light );
		break;

		case SPOT_PHONG_VRML:
			this->light = new SpotLightPhongVRML( *(SpotLightPhongVRML*) &light );
		break;

		case OMNI_PHONG:
			this->light = new OmniLightPhong( *(OmniLightPhong*) &light );
		break;

		case RECTANGLE_PHONG:
			this->light = new RectangleLightPhong( *(RectangleLightPhong*) &light );
		break;

		case DIRECTIONAL_PHONG:
			this->light = new DirectionalLightPhong( *(DirectionalLightPhong*) &light );
		break;

		default:
			assert( !"Unexpected light type" );
			break;
	}
}
Light* LightWrapper::getRealLightPtr(void)
{
	return this->light; 
}

