/// Copyright (c) 2009 Ruben Penalva. All rights reserved.
/// Redistribution and use in source and binary forms, with or without modification, 
/// are permitted provided that the following conditions are met:
///    1. Redistributions of source code must retain the above copyright notice, this 
///    list of conditions and the following disclaimer.
///    2. Redistributions in binary form must reproduce the above copyright notice,
///    this list of conditions and the following disclaimer in the documentation and/or
///    other materials provided with the distribution.
///    3. The name of the author may not be used to endorse or promote products derived
///    from this software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
/// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 
/// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
/// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
/// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/// @file Render.cpp
/// This source file is part of Render Gpu Quadratic Curve Render Test.
/// The file holds the Render class implementation.
/// @author Ruben Penalva (ruben.penalva@rpenalva.com)
#include "Render.h"

#include <GL/glew.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>

#include "OpenglGpuProgram.h"

#include <assert.h>

using namespace GpuQuadraticCurveRender;

Render::Render(void)	:	m_depth(-20.0f), m_pointSize(5.0f), 
							quadraticVertexShaderFile(L"glsl\\quadraticVertexShader.vsh"), 
							quadraticPixelShaderFile(L"glsl\\quadraticPixelShader.psh"),
							m_quadraticGpuProgram(0)

{
}

Render::~Render(void)
{	
	delete m_quadraticGpuProgram;
}

void Render::Initialize(int width, int height)
{
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		/// glewGetErrorString(err)
		throw L"[ERROR] Error in glew initialization. Render constructor.";
	}

	if (!GLEW_VERSION_2_1)
	{
		throw L"[ERROR] Opengl 2.1 required.";
	}

	/// @todo it should be used RAII through boost smart pointers
	m_quadraticGpuProgram = new OpenglGpuProgram(quadraticVertexShaderFile, quadraticPixelShaderFile);

	Reshape(width, height);
}

void Render::Reshape(int width, int height)
{
	glEnable(GL_TEXTURE_2D);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0.5, 0.5, 0.7, 0.0);
    glColor3f(1.0, 1.0, 1.0);

	glEnable(GL_LINE_SMOOTH);
    glDisable(GL_CULL_FACE);
    glFrontFace(GL_CCW);

    glDisable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
	
	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height, 0.1f, 100.0f);

	glMatrixMode(GL_MODELVIEW);	
	gluLookAt (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);  
	glLoadIdentity();

	glPointSize(m_pointSize);
}

void Render::BeginFrame(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void Render::SetMaterial(float red, float green, float blue)
{
	glColor3f(red, green, blue);
}

void Render::DrawPoints(const std::vector<Point>& points)
{
	int pointsCount = points.size();
	
    glBegin(GL_POINTS);
		for (int i = 0, j = 0; i < pointsCount; ++i, j )
		{
			const Point& point = points[i];
			glVertex3f(point.m_x, point.m_y, m_depth);
		}
    glEnd();
}

void Render::DrawLines(const std::vector<Line>& lines)
{
	int linesCount = lines.size();
	for (int i = 0; i < linesCount; ++i)
	{
		const Line& line = lines[i];
		glBegin(GL_LINES);
			glVertex3f(line.m_start.m_x, line.m_start.m_y, m_depth);
			glVertex3f(line.m_end.m_x, line.m_end.m_y, m_depth);	
		glEnd();
	}
}
	
void Render::DrawQuadraticSpline(const std::vector<Point>& points)
{
	assert(points.size() == 3);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);

	glUseProgram(m_quadraticGpuProgram->getGlProgramId());

	glBegin(GL_TRIANGLES);
		glTexCoord2f(0.0f, 0.0f);	
		glVertex3f(points[0].m_x, points[0].m_y, m_depth);
		
		glTexCoord2f(0.5f, 0.0f);
		glVertex3f(points[1].m_x, points[1].m_y, m_depth);
		
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(points[2].m_x, points[2].m_y, m_depth);
	glEnd();

	glUseProgram(0);

	glDisable(GL_BLEND);
}

void Render::DrawTriangles(const std::vector<Point>& points,
						   const std::vector<int>& indices)
{
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	glBegin(GL_TRIANGLES);
		int indicesCount = indices.size();
		for (int i = 0; i < indicesCount; i += 3)
		{
			int indexA = indices[i];
			glVertex3f(points[indexA].m_x, points[indexA].m_y, m_depth);

			int indexB = indices[i + 1];
			glVertex3f(points[indexB].m_x, points[indexB].m_y, m_depth);

			int indexC = indices[i + 2];
			glVertex3f(points[indexC].m_x, points[indexC].m_y, m_depth);
		}
	glEnd();	
	
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

void Render::EndFrame(void)
{
	glutSwapBuffers();
}

void Render::UnProjectScreenPoint(const Point& screenPoint, double& x, double& y, double& z)
{
	GLdouble modelViewMatrix[16];
	glGetDoublev(GL_MODELVIEW_MATRIX, modelViewMatrix);

	GLdouble projMatrix[16];
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);

	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);

	double nearX, nearY, nearZ;
	double farX, farY, farZ;
	gluUnProject(screenPoint.m_x, viewport[3]- screenPoint.m_y, 0.0f, modelViewMatrix, projMatrix, viewport, &nearX, &nearY, &nearZ);
	gluUnProject(screenPoint.m_x, viewport[3]- screenPoint.m_y, 1.0f, modelViewMatrix, projMatrix, viewport, &farX, &farY, &farZ);

	double directionX = farX - nearX;
	double directionY = farY - nearY;
	double directionZ = farZ - nearZ;
	
	double directionNorm = sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ);
	directionX /= directionNorm;
	directionY /= directionNorm;
	directionZ /= directionNorm;

	double t = (m_depth - nearZ) / directionZ;
	z = m_depth;

	x = nearX + t * directionX;
	y = nearY + t * directionY;
}

float Render::GetPointSize(void) const
{
	return m_pointSize;
}