//==============================================================================
/*! \file
 * OpenMesh Toolkit for mesh analysis    \n
 * Copyright (c) 2010 by Rostislav Hulik     \n
 *
 * Author:  Rostislav Hulik, rosta.hulik@gmail.com  \n
 * Date:    2010/10/20                          \n
 *
 * This file is part of software developed for support of Rostislav Hulik's dissertation thesis at dcgm-robotics@FIT group.
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this file.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Description:
 * - Class for OpenMesh export into OpenSceneGraph.
 */

#ifndef _OM_GEOMETRY_HXX_
#define _OM_GEOMETRY_HXX_

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor - creates an instance of Geometry from an OpenMesh mesh
// @param mesh OpenMesh mesh to be converted
// @param binding Specifies additional attributes for mesh generation normal binding and color binding. If there is no normal in Mesh, they will be computed
// @param defaultColor Specifies default color which will be added, if there is no color attribute in Mesh
// @see AttributeBinding
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Mesh>
OMGeometry<Mesh>::OMGeometry(Mesh mesh, AttributeBinding binding, Vec4 &defaultColor) : Geometry()
{
	OpenMesh::Utils::Timer timer;
	timer.reset();
	timer.start();
	using namespace OMToolkit;

	// try to load vertices
	if (LoadVertices(mesh)) 
	{
		MDS_LOG_NOTE("Vertices successfully loaded into OSG...");
	}
	else 
	{
		MDS_LOG_NOTE("Problem with copying vertices into OSG, aborting...");
		loaded_done = false;
		timer.stop();
		return;
	}

	// try to load faces
	if (LoadFaces(mesh)) 
	{
		MDS_LOG_NOTE("Faces successfully loaded into OSG...");
	}
	else 
	{
		MDS_LOG_NOTE("Problem with copying faces into OSG, aborting...");
		loaded_done = false;
		timer.stop();
		return;
	}

	// test attributes and load normals
	if (!(binding & NORMAL_NONE))
	{
		if (binding & NORMAL_PER_FACE)
			if (LoadNormals(mesh, Geometry::BIND_PER_PRIMITIVE)) 
			{
				MDS_LOG_NOTE("Normals successfully loaded into OSG, bound per face...");
			}
			else 
			{
				MDS_LOG_NOTE("Normals bound per face failed to load, aborting...");
				loaded_done = false;
				timer.stop();
				return;
			}
		if (binding & NORMAL_PER_VERTEX)
			if (LoadNormals(mesh, Geometry::BIND_PER_VERTEX)) 
			{
				MDS_LOG_NOTE("Normals successfully loaded into OSG, bound per vertex...");
			}
			else 
			{
				MDS_LOG_NOTE("Normals bound per vertex failed to load, aborting...");
				loaded_done = false;
				timer.stop();
				return;
			}
	}

	// test attributes and load colors
	if (!(binding & COLOR_NONE))
	{
		if (binding & COLOR_PER_VERTEX)
			if (LoadColor(mesh, defaultColor, Geometry::BIND_PER_VERTEX)) 
			{
				MDS_LOG_NOTE("Colors successfully loaded into OSG, bound per vertex...");
			}
			else 
			{
				MDS_LOG_NOTE("Colors bound per vertex failed to load, aborting...");
				loaded_done = false;
				timer.stop();
				return;
			}
		if (binding & COLOR_PER_FACE)
			if (LoadColor(mesh, defaultColor, Geometry::BIND_PER_PRIMITIVE)) 
			{
				MDS_LOG_NOTE("Colors successfully loaded into OSG, bound per face...");
			}
			else 
			{
				MDS_LOG_NOTE("Colors bound per face failed to load, aborting...");
				loaded_done = false;
				timer.stop();
				return;
			}
	}
	timer.stop();
	MDS_LOG_NOTE("Loading done in: " << timer.as_string(OpenMesh::Utils::Timer::MSeconds));
	loaded_done = true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Allows testing if geometry was successfully loaded
// @return True, if all the model was loaded successfully.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Mesh>
bool OMGeometry<Mesh>::isLoadedOk()
{
	return loaded_done;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Loads vertices into osg from OpenMesh
// @param mesh Structure with a mesh to be loaded
// return True if there was no problem with adding all the vertices.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Mesh>
bool OMGeometry<Mesh>::LoadVertices(Mesh mesh)
{
	// create auxiliary variables
	ref_ptr<Vec3Array> vertices = new osg::Vec3Array;
	OpenMesh::IO::ExporterT<Mesh> exporter(mesh);
	Mesh::VertexIter vertexIt;
	Mesh::VertexIter vertexEnd = mesh.vertices_end();
	OpenMesh::Vec3f vertex;

	// pass all vertices of mesh and add them into osg
	for (vertexIt = mesh.vertices_begin(); vertexIt != vertexEnd; ++vertexIt)
	{
		OpenMesh::Vec3f vertex = exporter.point(vertexIt);
		vertices->push_back(Vec3(	vertex[0],
									vertex[1],
									vertex[2]));
	}

	// test if numbers correspond
	if (vertices->size() != mesh.n_vertices()) return false;
	setVertexArray(vertices);
	return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Loads faces into osg from OpenMesh
// @param mesh Structure with a mesh to be loaded
// return True if there was no problem with adding all the faces.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Mesh>
bool OMGeometry<Mesh>::LoadFaces(Mesh mesh)
{ 
	// auxiliary variables
	ref_ptr<DrawElementsUInt> face;
	OpenMesh::IO::ExporterT<Mesh> exporter(mesh);
	Mesh::FaceIter faceIt;
	Mesh::FaceIter faceEnd = mesh.faces_end();

	// if a mesh is pure triangle mesh, we will be adding only triangles
	if (exporter.is_triangle_mesh())
	{
		face = new DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
		std::vector<Mesh::VertexHandle> faceVertices(3);

		// pass all faces and add them into osg
		for (faceIt = mesh.faces_begin(); faceIt != faceEnd; ++faceIt)
		{
			exporter.get_vhandles(faceIt, faceVertices);
			face->push_back(faceVertices[0].idx());
			face->push_back(faceVertices[1].idx());
			face->push_back(faceVertices[2].idx());
		}
		if (face->size() != mesh.n_faces()*3) return false;
		addPrimitiveSet(face);
	}

	// if a mesh is a polygonal mesh, we must check each face
	else
	{
		std::vector<Mesh::VertexHandle> faceVertices;
		std::vector<Mesh::VertexHandle>::iterator it, end;

		// pass all faces
		for (faceIt = mesh.faces_begin(); faceIt != faceEnd; ++faceIt)
		{
			face = new DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0);
			exporter.get_vhandles(faceIt, faceVertices);
			end = faceVertices.end();
			// pass each vertex and add them into osg
			for (it = faceVertices.begin(); it != end; ++it)
			{
				face->push_back(it->idx());
			}

			addPrimitiveSet(face);
		}
		if (getPrimitiveSetList().size() != mesh.n_faces()) return false;
	}
				
	return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Loads and associes colors into osg from OpenMesh
// @param mesh Structure with a mesh to be loaded
// @param defaultColor If there is no color in OpenMesh, this color will be associated to every vertex/face
// @param binding OSG color binding.
// @see AttributeBinding
// @return True if there was no problem with adding all the colors.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Mesh>
bool OMGeometry<Mesh>::LoadColor(Mesh mesh, osg::Vec4 &defaultColor = osg::Vec4(1.0, 1.0, 1.0, 1.0), Geometry::AttributeBinding binding = Geometry::BIND_PER_VERTEX)
{
	// create auxiliary variables
	OpenMesh::IO::ExporterT<Mesh> exporter(mesh);
	ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
	OpenMesh::Vec4uc color;
	OpenMesh::Vec4uc zeros(1,0,0,255);

	// check attributes and do what is asked
	if (binding == Geometry::BIND_PER_VERTEX)
	{
		// if there is no colors, go default
		if (!exporter.has_vertex_colors())
		{
			colors->resize(exporter.n_vertices(), defaultColor);
		}
		else
		{
			// else pass all vertices
			Mesh::VertexIter vertex;
			Mesh::VertexIter end = mesh.vertices_end();

			/*bool hasColors = false;
			for (vertex = mesh.vertices_begin(); vertex != end; ++vertex)
			{
				color = exporter.colorA(vertex);
				if (color != zeros) 
				{
					hasColors = true;
					break;
				}
			}

			if (hasColors)*/
				for (vertex = mesh.vertices_begin(); vertex != end; ++vertex)
				{
					color = exporter.colorA(vertex);
					colors->push_back(Vec4(	(float)color[0]/255.0f,
											(float)color[1]/255.0f,
											(float)color[2]/255.0f,
											(float)color[3]/255.0f));
					/*std::cout <<	colors->back()[0] << " " << 
									colors->back()[1] << " " << 
									colors->back()[2] << " " << 
									colors->back()[3] << std::endl;*/
				}
			//else colors->resize(exporter.n_vertices(), defaultColor);
			

			if (colors->size() != mesh.n_vertices()) return false;
		}
	}
	else if (binding == Geometry::BIND_PER_PRIMITIVE)
	{
		// if there is no face colors
		if (!exporter.has_face_colors())
		{
			colors->resize(exporter.n_vertices(), defaultColor);
		}
		// pass all faces
		else
		{
			Mesh::FaceIter face;
			Mesh::FaceIter end = mesh.faces_end();

			/*bool hasColors = false;
			for (face = mesh.faces_begin(); face != end; ++face)
			{
				color = exporter.colorA(face);
				if (color != zeros) 
				{
					hasColors = true;
					break;
				}
			}

			if (hasColors)*/
				for (face = mesh.faces_begin(); face != end; ++face)
				{
					color = exporter.colorA(face);
					colors->push_back(Vec4(	(float)color[0]/255.0f,
											(float)color[1]/255.0f,
											(float)color[2]/255.0f,
											(float)color[3]/255.0f));
				}
			//else colors->resize(exporter.n_faces(), defaultColor);

			if (colors->size() != mesh.n_faces()) return false;
		}
	}
	else return false;

	// finally, add this array
	setColorArray(colors);
	setColorBinding(binding);
	return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Loads and associes normals into osg from OpenMesh (if there is no normals, they will be computed)
// @param mesh Structure with a mesh to be loaded
// @param binding OSG normal binding.
// @see AttributeBinding
// @return True if there was no problem with adding all the normals.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Mesh>
bool OMGeometry<Mesh>::LoadNormals(Mesh mesh, Geometry::AttributeBinding binding = Geometry::BIND_PER_PRIMITIVE)
{
	// load auxiliary variables
	OpenMesh::IO::ExporterT<Mesh> exporter(mesh);
	ref_ptr<Vec3Array> normals = new Vec3Array();

	// select binding options
	if (binding == Geometry::BIND_PER_PRIMITIVE)
	{
		Mesh::FaceIter face;
		Mesh::FaceIter end = mesh.faces_end();
		OpenMesh::Vec3f normal;
	
		// if there is no normals, we must compute them
		if (!exporter.has_face_normals())
		{
			mesh.request_face_normals();
			for (face = mesh.faces_begin(); face != end; ++face)
			{
				mesh.set_normal(face, mesh.calc_face_normal(face));
				normal = exporter.normal(face);
				normals->push_back(Vec3(	normal[0],
											normal[1],
											normal[2]));
			}
			mesh.release_face_normals();
		}
		// if there are normals, we use them
		else
		{
			for (face = mesh.faces_begin(); face != end; ++face)
			{
				normal = exporter.normal(face);
				normals->push_back(Vec3(	normal[0],
											normal[1],
											normal[2]));
			}
		}
	}
	// binding
	else if (binding == Geometry::BIND_PER_VERTEX)
	{
		Mesh::VertexIter vertex;
		Mesh::VertexIter end = mesh.vertices_end();
		OpenMesh::Vec3f normal;

		// if there is no vertex normals
		if (!exporter.has_vertex_normals())
		{
			bool faceNormalsDelete = false;

			// we must have face normals
			if (!exporter.has_face_normals())
			{
				faceNormalsDelete = true;
				
				mesh.request_face_normals();
				Mesh::FaceIter face;
				Mesh::FaceIter end = mesh.faces_end();
				for (face = mesh.faces_begin(); face != end; ++face)
				{
					mesh.set_normal(face, mesh.calc_face_normal(face));
				}
			}
			// then, we compute missing vertex normals
			mesh.request_vertex_normals();
			for (vertex = mesh.vertices_begin(); vertex != end; ++vertex)
			{
				mesh.set_normal(vertex, mesh.calc_vertex_normal(vertex));
				normal = exporter.normal(vertex);
				normals->push_back(Vec3(	normal[0],
											normal[1],
											normal[2]));
			}

			// release all aux
			mesh.release_vertex_normals();
			if (faceNormalsDelete) mesh.release_face_normals();
		}
		// if we already have vertex normals
		else
		{
			for (vertex = mesh.vertices_begin(); vertex != end; ++vertex)
			{
				normal = exporter.normal(vertex);
				normals->push_back(Vec3(	normal[0],
											normal[1],
											normal[2]));
			}
		}
	}

	// if binding is invalid, return false
	else return false;
	
	setNormalArray(normals);
	setNormalBinding(binding);
	
	return true;
}

#endif