/// Copyright (c) 2010 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.

#include "TestRender.h"

#include <assert.h>
#include "OpenglVertexBuffer.h"

#include "Matrix4x4.h"
#include <boost\thread\shared_mutex.hpp>
using namespace TaskBasedFlockSim;

TestRender::TestRender(TestFrameWork::ScenePtr scene, HDC canvasContext, 
					   size_t width, size_t height)	:	m_scene(boost::shared_polymorphic_downcast<TestScene>(scene)),
														m_canvasContext(canvasContext), m_width(width), m_height(height)
{
}

TestRender::~TestRender(void)
{
}

void TestRender::Initialize(void)
{
	m_driver.reset(new TestFrameWork::OpenglDriver(TestFrameWork::OpenglVersion_2_1, m_canvasContext, m_width, m_height, 24, 4));

	SetMaxFlockSize();

	CreateFlockTextureData();
}

void TestRender::Setup(void)
{
	CreateTeapotBuffers();

	ModifyFlockTextureData();

	CreateProgram();

	m_driver->SetProgram(m_program);

	SetProgramParameters();
}

void TestRender::Run(float elapsedTime)
{
	SetProgramParameters();

	ModifyFlockTextureData();

	m_driver->BeginFrame();

	RenderMetrics(elapsedTime, m_scene->GetLogicElapsedTime());

	m_driver->DrawIndexedPrimitive(TestFrameWork::Triangles, m_teapotVertexBuffer, m_teapotIndexBuffer, m_scene->GetFlock().GetBoidsCount());

	m_driver->EndFrame();
}

void TestRender::CreateTeapotBuffers(void)
{
	const TestFrameWork::Geometry& teapotGeometry(m_scene->GetTeapot());

	/// Create vertex buffer
	/// Convert Positions and Normals to Vertices
	const TestFrameWork::Normals& normals = teapotGeometry.GetNormals();
	const TestFrameWork::Positions& positions = teapotGeometry.GetPositions();
	
	assert(positions.size() > 0);

	assert(normals.size() > 0);

	assert(positions.size() == normals.size());

	size_t verticesCount = positions.size();

	TestFrameWork::Vertices vertices(new TestFrameWork::Vertex[verticesCount]);
	for (size_t i = 0; i < verticesCount; ++i)
	{
		vertices[i].m_normal = normals[i];
		vertices[i].m_position = positions[i];
	}
	
	m_teapotVertexBuffer = m_driver->CreateVertexBuffer(vertices, verticesCount);

	/// Create index buffer
	const TestFrameWork::Indices& teapotIndices = teapotGeometry.GetIndices();
	size_t indicesCount = teapotIndices.size();

	assert(indicesCount > 0);

	TestFrameWork::Indices32 indices(new int[indicesCount]);
	for (size_t i = 0; i < indicesCount; ++i)
	{
		indices[i] = teapotIndices[i];
	}

	m_teapotIndexBuffer = m_driver->CreateIndexBuffer(indices, indicesCount);
}

void TestRender::CreateProgram(void)
{
	TestFrameWork::ShaderFileDesc vertexShaderDesc;
	vertexShaderDesc.m_fileName = m_scene->GetVertexShaderFile();
	vertexShaderDesc.m_stage = TestFrameWork::VertexStage;

	TestFrameWork::ShaderFileDesc pixelShaderDesc;
	pixelShaderDesc.m_fileName = m_scene->GetPixelShaderFile();
	pixelShaderDesc.m_stage = TestFrameWork::FragmentStage;

	std::vector<TestFrameWork::ShaderFileDesc> shaders;
	shaders.push_back(vertexShaderDesc);
	shaders.push_back(pixelShaderDesc);

	m_program = m_driver->CreateProgram(shaders);
}

void TestRender::SetProgramParameters(void)
{
	const TestFrameWork::Matrix4x4& viewMatrix(m_scene->GetCamera().GetViewTransform());
	const TestFrameWork::Matrix4x4& projectionMatrix(m_scene->GetCamera().GetProjectionTransform());

	TestFrameWork::OpenglProgramPtr program = m_program.lock();
	program->SetParameter("localMatrix", m_scene->GetTeapotTransformation());
	program->SetParameter("viewMatrix", viewMatrix);
	program->SetParameter("projectionMatrix", projectionMatrix);
	program->SetTextureUnit("flockData", 0);

	TestFrameWork::Vector3 lightDirCameraSpace(Normalize(m_scene->GetCamera().GetPosition()));
	program->SetParameter("lightDir", lightDirCameraSpace);
	
	program->SetParameter("dataWidth", static_cast<float>(m_maximumTexture2DSize));

	program->SetParameter("boidsCount", static_cast<float>(m_scene->GetFlock().GetBoidsCount()));
}

void TestRender::CreateFlockTextureData(void)
{
	m_textureDataSize = sizeof(float) * 3 * m_maximumTexture2DSize * m_maximumTexture2DSize;
	m_textureData.reset(new char[m_textureDataSize]);
	memset(m_textureData.get(), 0.0f, m_textureDataSize);

	m_flockDataTexture = m_driver->CreateTexture(m_textureData, TestFrameWork::RGB32F, 
												 m_maximumTexture2DSize, m_maximumTexture2DSize, 
												 TestFrameWork::Nearest, TestFrameWork::Clamp);

	m_driver->SetTexture(m_flockDataTexture, 0);
}

void TestRender::ModifyFlockTextureData(void)
{
	Flock& flock = m_scene->GetFlock();
	const Positions& positions = flock.GetPositions();
	const Velocities& velocities = flock.GetVelocities();
	size_t boidsCount = flock.GetBoidsCount();

	float* dataFloat = reinterpret_cast<float*>(m_textureData.get());

	for (size_t i = 0; i < boidsCount; ++i)
	{
		dataFloat[i * 6] = positions[i].m_x;
		dataFloat[i * 6 + 1] = positions[i].m_y;
		dataFloat[i * 6 + 2] = positions[i].m_z;

		dataFloat[i * 6 + 3] = velocities[i].m_x;
		dataFloat[i * 6 + 4] = velocities[i].m_y;
		dataFloat[i * 6 + 5] = velocities[i].m_z;
	}

	size_t width = m_maximumTexture2DSize;
	size_t height = boidsCount * 2 / m_maximumTexture2DSize;
	height += (boidsCount * 2) % width != 0? 1 : 0;
	
	TestFrameWork::OpenglTexturePtr texture(m_flockDataTexture.lock());
	texture->ModifyData(m_textureData, width, height);
}

void TestRender::SetMaxFlockSize(void)
{
	size_t width = 0;
	size_t height = 0;

	m_driver->GetMaximumTextureSize(TestFrameWork::RGB32F, width, height);
	size_t maxFlockSize = width * width * 0.5f;
	m_scene->GetFlock().SetMaxSize(maxFlockSize);

	m_maximumTexture2DSize = width;
}

void TestRender::RenderMetrics(float renderElapsedTime, float logicElapsedTime)
{
	std::stringstream converter;
	converter << "Flock simulation elapsed time: " << logicElapsedTime << "ms";

	m_driver->Draw2DString(converter.str(), m_width - 390, 40, TestFrameWork::Vector3(1.0f, 0.0f, 0.0f));

	converter.str("");
	converter << "Render elapsed time: " << renderElapsedTime << "ms";

	m_driver->Draw2DString(converter.str(), m_width - 300, 20, TestFrameWork::Vector3(1.0f, 0.0f, 0.0f));
}