#include "MeshManager.h"

#include "Utilities/format.h"
#include "Utilities/Log.h"

#include "physfs.h"
#include "trimeshloader.h"

#include <iostream>
#include <vector>

using namespace std;

namespace BlueCore
{

Mesh::Mesh(RenderDevice* device) :
	_Device(device)
{
}

Mesh::~Mesh()
{
	clog << ">>> Mesh destructed ..."<< endline;
}

void Mesh::render()
{

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	glVertexPointer( 3, GL_FLOAT, sizeof(Vertex), &VertexBuffer[0].point.x);

	glTexCoordPointer( 2, GL_FLOAT, sizeof(Vertex), &VertexBuffer[0].u);

	glNormalPointer(GL_FLOAT, sizeof(Vertex), &VertexBuffer[0].normal.x);

	glDrawElements(GL_TRIANGLES, IndexBuffer.count() * 3, GL_UNSIGNED_SHORT,
			IndexBuffer.data() );

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
}

#if 0
//--------------------------------------------------------------------------
void Mesh::createVBO()
{
	if ( GLEW_ARB_vertex_buffer_object && !vbo )
	{
		glGenBuffersARB ( 1, &vbo );

		glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo );

		glBufferDataARB (
				GL_ARRAY_BUFFER_ARB,
				size(),
				data(),
				GL_STATIC_DRAW_ARB );
	}
}

//--------------------------------------------------------------------------
void Mesh::releaseVBO()
{
	if ( GLEW_ARB_vertex_buffer_object && vbo )
	{
		glDeleteBuffersARB ( 1, &vbo );
		vbo = 0;
	}
}

//--------------------------------------------------------------------------
void Mesh::bind()
{
	if ( GLEW_ARB_vertex_buffer_object && vbo )
	{
		glBindBufferARB ( GL_ARRAY_BUFFER_ARB, vbo );

		// hardcoded offset and stride
		glVertexPointer ( 3, GL_FLOAT, sizeof ( Vertex ), 0 );

		glTexCoordPointer ( 2, GL_FLOAT, sizeof ( Vertex ), ( const GLvoid* ) ( sizeof ( float ) * 3 ) );

		glNormalPointer ( GL_FLOAT, sizeof ( Vertex ), ( const GLvoid* ) ( sizeof ( float ) * 5 ) );
	}
	else
	{
		// hardcoded offset and stride
		glVertexPointer ( 3, GL_FLOAT, sizeof ( Vertex ), data() );
		glTexCoordPointer ( 2, GL_FLOAT, sizeof ( Vertex ), data() + sizeof ( float ) * 3 );
		glNormalPointer ( GL_FLOAT, sizeof ( Vertex ), data() + sizeof ( float ) * 5 );
	}
}
#endif

MeshManager::MeshManager(RenderDevice* device) :
	_Device(device)
{
	BlueCore::clog << ">>> MeshManager constructed..." << endlog;
}

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

bool loadTrimesh(const char* filename, Mesh *mesh)
{
	PHYSFS_file* file = PHYSFS_openRead(filename);
	if (file != 0)
	{
		char buffer[1024];
		unsigned int size = 0;

		if (tlObjCheckFileExtension(filename) == 0)
		{
			tlObjState *state = tlObjCreateState();
			while (PHYSFS_eof(file) == 0)
			{
				size = (unsigned int) PHYSFS_read(file, buffer, 1,
						sizeof(buffer));
				tlObjParse(state, buffer, size, size < sizeof(buffer) ? 1 : 0);
			}

			mesh->VertexBuffer.create(tlObjVertexCount(state));
			for (unsigned int i = 0; i < mesh->VertexBuffer.count(); i++)
				tlObjGetVertex(state, i, &mesh->VertexBuffer[i].point.x,
						&mesh->VertexBuffer[i].point.y,
						&mesh->VertexBuffer[i].point.z,
						&mesh->VertexBuffer[i].u, &mesh->VertexBuffer[i].v,
						&mesh->VertexBuffer[i].normal.x,
						&mesh->VertexBuffer[i].normal.y,
						&mesh->VertexBuffer[i].normal.z);

			mesh->IndexBuffer.create(tlObjFaceCount(state));
			for (unsigned int i = 0; i < mesh->IndexBuffer.count(); i++)
				tlObjGetFace(state, i, &mesh->IndexBuffer[i].a,
						&mesh->IndexBuffer[i].b, &mesh->IndexBuffer[i].c);

			mesh->SubsetBuffer.create(tlObjObjectCount(state));
			for (unsigned int i = 0; i < mesh->SubsetBuffer.count(); i++)
			{
				mesh->SubsetBuffer[i].first = tlObjObjectFaceIndex(state, i);
				mesh->SubsetBuffer[i].count = tlObjObjectFaceCount(state, i);
			}

			tlObjDestroyState(state);
		}
		else if (tl3dsCheckFileExtension(filename) == 0)
		{
			tl3dsState *state = tl3dsCreateState();
			while (PHYSFS_eof(file) == 0)
			{
				size = (unsigned int) PHYSFS_read(file, buffer, 1,
						sizeof(buffer));
				tl3dsParse(state, buffer, size, size < sizeof(buffer) ? 1 : 0);
			}

			mesh->VertexBuffer.create(tl3dsVertexCount(state));
			for (unsigned int i = 0; i < mesh->VertexBuffer.count(); i++)
				tl3dsGetVertex(state, i, &mesh->VertexBuffer[i].point.x,
						&mesh->VertexBuffer[i].point.y,
						&mesh->VertexBuffer[i].point.z,
						&mesh->VertexBuffer[i].u, &mesh->VertexBuffer[i].v,
						&mesh->VertexBuffer[i].normal.x,
						&mesh->VertexBuffer[i].normal.y,
						&mesh->VertexBuffer[i].normal.z);

			mesh->IndexBuffer.create(tl3dsFaceCount(state));
			for (unsigned int i = 0; i < mesh->IndexBuffer.count(); i++)
				tl3dsGetFace(state, i, &mesh->IndexBuffer[i].a,
						&mesh->IndexBuffer[i].b, &mesh->IndexBuffer[i].c);

			mesh->SubsetBuffer.create(tl3dsObjectCount(state));
			for (unsigned int i = 0; i < mesh->SubsetBuffer.count(); i++)
			{
				mesh->SubsetBuffer[i].first = tl3dsObjectFaceIndex(state, i);
				mesh->SubsetBuffer[i].count = tl3dsObjectFaceCount(state, i);
			}

			tl3dsDestroyState(state);
		}

		PHYSFS_close(file);

		// create normlas, tangents, and binomials
		clog << "    create normlas..." << endline;

		for (unsigned int i = 0; i < mesh->VertexBuffer.count(); i++)
		{
			mesh->VertexBuffer[i].normal.zero();
		}

		for (unsigned int i = 0; i < mesh->IndexBuffer.count(); i++)
		{
			const TriangleIndices& triangle = mesh->IndexBuffer[i];

			Vector3Template<float> ab = mesh->VertexBuffer[ triangle.a ].point
					- mesh->VertexBuffer[ triangle.b ].point;
			Vector3Template<float> ac = mesh->VertexBuffer[ triangle.a ].point
					- mesh->VertexBuffer[ triangle.c ].point;
			Vector3Template<float> n = ab.cross(ac);

			mesh->VertexBuffer[ triangle.a ].normal += n;
			mesh->VertexBuffer[ triangle.a ].normal += n;
			mesh->VertexBuffer[ triangle.a ].normal += n;
		}

		for (unsigned int i = 0; i < mesh->VertexBuffer.count(); i ++)
		{
			mesh->VertexBuffer[i].normal.normalize();
		}

		clog << "    create tangents..." << endline;

		mesh->TangentBuffer.create(mesh->VertexBuffer.count());
		mesh->BitangentBuffer.create(mesh->VertexBuffer.count());

		for (unsigned int i = 0; i < mesh->TangentBuffer.count(); i ++)
		{
			mesh->TangentBuffer[i].zero();
			mesh->BitangentBuffer[i].zero();
		}

		for (unsigned int i = 0; i < mesh->IndexBuffer.count(); i ++)
		{
			const TriangleIndices& triangle = mesh->IndexBuffer[i];

			const Vertex& v1 = mesh->VertexBuffer[triangle.a];
			const Vertex& v2 = mesh->VertexBuffer[triangle.b];
			const Vertex& v3 = mesh->VertexBuffer[triangle.c];

			float x1 = v2.point.x - v1.point.x;
			float x2 = v3.point.x - v1.point.x;
			float y1 = v2.point.y - v1.point.y;
			float y2 = v3.point.y - v1.point.y;
			float z1 = v2.point.z - v1.point.z;
			float z2 = v3.point.z - v1.point.z;
			float s1 = v2.u - v1.u;
			float s2 = v3.u - v1.u;
			float t1 = v2.v - v1.v;
			float t2 = v3.v - v1.v;
			float r = 1.0f / (s1 * t2 - s2 * t1 );

			Vector3Float sdir( (t2 * x1 - t1 * x2 ) * r, (t2 * y1 - t1 * y2 )
					* r, (t2 * z1 - t1 * z2 ) * r);

			Vector3Float tdir( (s1 * x2 - s2 * x1 ) * r, (s1 * y2 - s2 * y1 )
					* r, (s1 * z2 - s2 * z1 ) * r);

			mesh->TangentBuffer[triangle.a] += sdir;
			mesh->TangentBuffer[triangle.b] += sdir;
			mesh->TangentBuffer[triangle.c] += sdir;

			mesh->BitangentBuffer[triangle.a] += tdir;
			mesh->BitangentBuffer[triangle.b] += tdir;
			mesh->BitangentBuffer[triangle.c] += tdir;
		}

		for (unsigned int i = 0; i < mesh->TangentBuffer.count(); i ++)
		{
			mesh->TangentBuffer[i].normalize();
			mesh->BitangentBuffer[i].normalize();
		}

		return 0;
	}

	return 1;
}

Mesh *MeshManager::loadMesh(const string &filename)
{
	// check if this mesh is already loaded
	std::map<std::string, weak_ptr<Mesh> >::const_iterator result;
	result = _Meshes.find(filename);

	if (result != _Meshes.end() && result->second.valid())
	{
		return result->second.pointer();
	}

	Mesh *mesh = new Mesh (_Device);

	// check cache
	PHYSFS_sint64 mod_file = PHYSFS_getLastModTime(filename.c_str());

	std::string cachename = filename + ".msc";
	PHYSFS_sint64 mod_cache = PHYSFS_getLastModTime(cachename.c_str());

	if ( (mod_cache > mod_file) && loadFromCache(mesh, filename) )
	{
		clog << ">>> Mesh '" << filename << "' loaded from cache." << endline;

		_Meshes[filename] = mesh;

		return mesh;
	}

	clog << ">>> Mesh '" << filename << "': loading from file..." << endlog;

	if (loadTrimesh(filename.c_str(), mesh) != 0)
	{
		mesh->removeReference();
		clog << "!!! Mesh not found!" << endline;
		return 0;
	}

#if 0		
	mesh->buildShadowFaceBuffer();
	mesh->buildTangentBuffer();

	unsigned int counter;

	// calculate bounding sphere
	clog << "    calculate bounding sphere";

	mesh->bounding_sphere.center().zero();

	for ( counter = 0; counter < mesh->vertex_buffer.count(); counter++ )
	{
		mesh->bounding_sphere.center() += mesh->vertex_buffer[counter].point;
	}

	mesh->bounding_sphere.center() /= mesh->vertex_buffer.size();

	mesh->bounding_sphere.radius() = 0.;

	for ( counter = 0; counter < mesh->vertex_buffer.count(); counter++ )
	{
		Scalar distance = ( mesh->vertex_buffer[counter].point - mesh->bounding_sphere.center() ).length();

		if ( distance > mesh->bounding_sphere.radius() )
		mesh->bounding_sphere.radius() = distance;
	}

	clog << ": " << mesh->bounding_sphere.radius() << endline;

	// create normlas, tangents, and binomials
	clog << "    create normlas, tangents, and binomials" << endline;

	for ( counter = 0; counter < mesh->vertex_buffer.count(); counter += 1 )
	{
		mesh->vertex_buffer[counter].normal.zero();
	}

	for ( counter = 0; counter < mesh->index_buffer.count(); counter += 3 )
	{
		Index a = mesh->index_buffer[counter];
		Index b = mesh->index_buffer[counter+1];
		Index c = mesh->index_buffer[counter+2];

		Vector3Template<float> ab = mesh->vertex_buffer[ a ].point - mesh->vertex_buffer[ b ].point;
		Vector3Template<float> ac = mesh->vertex_buffer[ a ].point - mesh->vertex_buffer[ c ].point;
		Vector3Template<float> n = ab.cross ( ac );

		mesh->vertex_buffer[ a ].normal += n;
		mesh->vertex_buffer[ b ].normal += n;
		mesh->vertex_buffer[ c ].normal += n;
	}

	for ( counter = 0; counter < mesh->vertex_buffer.count(); counter += 1 )
	{
		mesh->vertex_buffer[counter].normal.normalize();
	}

	clog << "    create vbos" << endline;

	mesh->vertex_buffer.createVBO();
	mesh->index_buffer.createVBO();
	mesh->tangent_buffer.createVBO();
	mesh->bitangent_buffer.createVBO();
#endif

	_Meshes[filename] = mesh;

	saveToCache(mesh, filename);

	return mesh;
}

bool MeshManager::loadFromCache(Mesh *mesh, const std::string &name)
{
	std::string filename = name + ".msc";

	PHYSFS_file *file = PHYSFS_openRead(filename.c_str());

	if (file == 0)
		return false;

	unsigned int size;

	// read vertex_buffer
	PHYSFS_read(file, &size, sizeof (size ), 1);
	mesh->VertexBuffer.create(size);
	PHYSFS_read(file, mesh->VertexBuffer.data(), mesh->VertexBuffer.size(), 1);

	// read index buffer
	PHYSFS_read(file, &size, sizeof (size ), 1);
	mesh->IndexBuffer.create(size);
	PHYSFS_read(file, mesh->IndexBuffer.data(), mesh->IndexBuffer.size(), 1);

	// read subsets
	PHYSFS_read(file, &size, sizeof (size ), 1);
	mesh->SubsetBuffer.create(size);
	PHYSFS_read(file, mesh->SubsetBuffer.data(), mesh->SubsetBuffer.size(), 1);

	// read tangent buffer
	PHYSFS_read(file, &size, sizeof (size ), 1);
	mesh->TangentBuffer.create(size);
	PHYSFS_read(file, mesh->TangentBuffer.data(), mesh->TangentBuffer.size(), 1);

	// read bitangent buffer
	PHYSFS_read(file, &size, sizeof (size ), 1);
	mesh->BitangentBuffer.create(size);
	PHYSFS_read(file, mesh->BitangentBuffer.data(),
			mesh->BitangentBuffer.size(), 1);

#if 0
	// read bounding sphere
	PHYSFS_read ( file, &mesh->bounding_sphere, sizeof ( mesh->bounding_sphere ), 1 );
#endif

#if 0
	// read bitangent buffer
	PHYSFS_read ( file, &size, sizeof ( size ), 1 );
	mesh->shadowface_buffer.create ( size );
	PHYSFS_read ( file, mesh->shadowface_buffer.data(), mesh->shadowface_buffer.size(), 1 );
#endif

	PHYSFS_close(file);

	return true;
}

bool MeshManager::saveToCache(Mesh *mesh, const std::string &name)
{
	std::string filename = name + ".msc";

	PHYSFS_file *file = PHYSFS_openWrite(filename.c_str() );

	if (file == 0)
	{
		clog << ">>> cannot open cache file for mesh: " << name << endline;
		return false;
	}

	// write vertex_buffer
	unsigned int size = mesh->VertexBuffer.count();
	PHYSFS_write(file, &size, sizeof (size ), 1);
	PHYSFS_write(file, mesh->VertexBuffer.data(), mesh->VertexBuffer.size(), 1);

	// write index_buffer
	size = mesh->IndexBuffer.count();
	PHYSFS_write(file, &size, sizeof (size ), 1);
	PHYSFS_write(file, mesh->IndexBuffer.data(), mesh->IndexBuffer.size(), 1);

	// write subsets
	size = mesh->SubsetBuffer.count();
	PHYSFS_write(file, &size, sizeof (size ), 1);
	PHYSFS_write(file, mesh->SubsetBuffer.data(), mesh->SubsetBuffer.size(), 1);

	// write tangent_buffer
	size = mesh->TangentBuffer.count();
	PHYSFS_write(file, &size, sizeof (size ), 1);
	PHYSFS_write(file, mesh->TangentBuffer.data(), mesh->TangentBuffer.size(),
			1);

	// write bitangent_buffer
	size = mesh->BitangentBuffer.count();
	PHYSFS_write(file, &size, sizeof (size ), 1);
	PHYSFS_write(file, mesh->BitangentBuffer.data(),
			mesh->BitangentBuffer.size(), 1);

#if 0
	// write bounding sphere
	PHYSFS_write ( file, &mesh->bounding_sphere, sizeof ( mesh->bounding_sphere ), 1 );

	// write shadowfaces
	size = mesh->shadowface_buffer.count();
	PHYSFS_write ( file, &size, sizeof ( size ), 1 );
	PHYSFS_write ( file, mesh->shadowface_buffer.data(), mesh->shadowface_buffer.size(), 1 );
#endif 

	PHYSFS_close(file);

	return true;
}

} // namespace BlueCore
