#include "RenderDevice.h"

#include "Utilities/Log.h"
#include "Math/Matrix.h"

#include "GL/gl.h"
#include "GL/glu.h"

namespace BlueCore
{

void RenderDevice::WindowResizeSlot(int width, int height)
{
	glViewport( 0, 0, width, height);
	_ViewportWidth = width;
	_ViewportHeight = height;
}

RenderDevice::RenderDevice(RenderWindow* renderWindow) :
	_RenderWindow(renderWindow), _ActiveTextures(0)
{
	if (_RenderWindow.valid())
	{
		_RenderWindow->WindowResizeSignal.connect(this,
				&RenderDevice::WindowResizeSlot);

		_RenderWindow->WindowCloseSignal.connect(this,
				&RenderDevice::WindowCloseSlot);

		_ViewportWidth = _RenderWindow->getWidth();
		_ViewportHeight = _RenderWindow->getHeight();
		glViewport( 0, 0, _ViewportWidth, _ViewportHeight);
	}

	clog << ">>> RenderDevice constructed..." << endlog;
}

RenderDevice::~RenderDevice()
{
	clog << ">>> RenderDevice destructed..."<< endlog;
}

void RenderDevice::WindowCloseSlot()
{
	DeviceShutdownSignal();
}

int RenderDevice::getViewportWidth()
{
	return _ViewportWidth;
}

int RenderDevice::getViewportHeight()
{
	return _ViewportHeight;
}

void RenderDevice::begin2D()
{
	// prepare state
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glNormal3f( 0.0, 0.0, 1.0);

	// set projection matrix
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	//int newheight = (_width / 16.0) * 9.0;
	gluOrtho2D( 0, _ViewportWidth-1, 0, _ViewportHeight-1);

	// prepare model matrix
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

void RenderDevice::end2D()
{
	// restore projection matrix
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	// restore model matrix
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	// restore old state
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
}

void RenderDevice::clear()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void RenderDevice::swap()
{
	glfwSwapBuffers();
}

void RenderDevice::setAmbientLight(float r, float g, float b)
{
	GLfloat lightAmbient[] =
	{ r, g, b, 1.0f };
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightAmbient);
}

void RenderDevice::begin3D(Camera *camera)
{
	_Camera = camera;
	setupProjectionMatrix();
	setupViewMatrix();
}

void RenderDevice::end3D()
{
}

void RenderDevice::setupProjectionMatrix()
{
	if (_Camera.valid())
	{
		Scalar fH = tan( (_Camera->getFov() / 2) / 180 * Pi)
				* _Camera->getNearPlane();
		Scalar fW = fH * _ViewportWidth / _ViewportHeight;

		// setup projectiom matrix
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glFrustum( -fW, fW, -fH, fH, _Camera->getNearPlane(),
				_Camera->getFarPlane());
	}
}

void RenderDevice::setupViewMatrix()
{
	if (_Camera.valid())
	{
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		Matrix4x4 m(_Camera->getRotation(), _Camera->getRotation().applyInversed(_Camera->getPosition() * -1.0) );
		glMultMatrixd( ( GLdouble * ) &m.m );
	}
}

void RenderDevice::pushTransformation(const Vector3& position,
		const Quaternion& orientation)
{
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	Matrix4x4 m(orientation, position);
	glMultMatrixd( ( GLdouble * ) &m.m );
}

void RenderDevice::pushAbsoluteTransformation(const Vector3& position,
		const Quaternion& orientation)
{
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	Matrix4x4 m(orientation, position);
	glLoadMatrixd( ( GLdouble * ) &m.m );
}

void RenderDevice::popTransformation()
{
	glMatrixMode( GL_MODELVIEW);
	glPopMatrix();
}

void RenderDevice::setTexture(unsigned int unit, Texture* texture, bool last)
{
	glActiveTextureARB(GL_TEXTURE0_ARB + unit);
	glBindTexture(GL_TEXTURE_2D, texture->getId());
	glEnable(GL_TEXTURE_2D);

	if (unit > _ActiveTextures)
		_ActiveTextures = unit;

	if (last)
	{
		for (unsigned int i = unit + 1; i < _ActiveTextures; i++)
		{
			glActiveTextureARB(GL_TEXTURE0_ARB + i);
			glDisable( GL_TEXTURE_2D);
		}

		_ActiveTextures = unit;
	}
}

void RenderDevice::setShaderProgram(ShaderProgram shader_program)
{
	//	if ( !_usingShaders)
	//		return;

	glUseProgramObjectARB(shader_program);
}

void RenderDevice::setTexture(ShaderProgram shader_program,
		unsigned int texture, const std::string &name)
{
	//	if ( !_usingShaders)
	//		return;

	GLint location = glGetUniformLocationARB(shader_program, name.c_str() );

	//if( location > 0 )
	glUniform1iARB(location, texture);
}
#if 0
void RenderDevice::setTangentBuffer(ShaderProgram shader_program,
		const Buffer<Vector3Float>& tangent_buffer, const std::string &name)
{
//	if ( !_usingShaders || !tangent_buffer)
//		return;

	GLuint location = glGetAttribLocationARB(shader_program, name.c_str() );

	glEnableVertexAttribArrayARB(location);

	//tangent_buffer->bind();

	glVertexAttribPointerARB(location, 3, GL_FLOAT, 0, 0, 0);
}

//------------------------------------------------------------------------------
void ShaderManager::disableTangentBuffer(ShaderProgram shader_program,
		const std::string &name)
{
//	if ( !_usingShaders)
//		return;

	GLuint location = glGetAttribLocationARB(shader_program, name.c_str() );

	glDisableVertexAttribArrayARB(location);
}
#endif
} // namespace BlueCore
