/*
obj.cxx/h

Created 2005.08 by christopher.cameron@gmail.com
Contains code to load and save an OBJ 3d model
*/

#include "obj.h"
#include <fstream>
#include <sstream>
#include<map>
using namespace std;

// read an obj file
bool Obj::Load (const string& fileName)
{
	// clear anything that existed before
	vertices.resize (0);
	normals.resize (0);
	texcoords.resize (0);
	faces.resize (0);
	
	// open up file
	ifstream in (fileName.c_str () );
	if (in.fail () ) 
	{
		cerr << "Can't read input file " << fileName << endl;
		return false;
	}

	// read through line by line
	int lineNumber = 0;
	string type;
	while (true)
	{
		string line;
		lineNumber++;
		getline (in, line);
		if (in.eof () ) break;

		// get the type of command (v, vt, vn, or f supported)
		istringstream is (line);
		is >> type;

		// reading vertices
		if (type == "v")
		{
			Vec3 v;
			is >> v;
			vertices.push_back (v);
		}

		// vertex normals
		if (type == "vn")
		{
			Vec3 vn;
			is >> vn;
			normals.push_back (vn);
		}

		// texcoords
		if (type == "vt")
		{
			Vec2 vt;
			is >> vt;
			texcoords.push_back (vt);
		}

		// reading faces
		else if (type == "f")
		{
			Face f;
			while (is.eof () == false)
			{
				// read vertex index, texture index, and normal index as v/t/n
				string indexString;
				is >> indexString;

				// sometimes there will be whitespace after lines, so we'll need
				// to read all the way past the last entry to get an eof, which
				// will result in a fail here
				if (is.fail () ) 
				{
					continue;
				}

				// read the indices of vertex/texcoord/normal
				int indicesIndex = 0;
				int indices[] = {0, 0, 0};
				int sign[] = {1, 1, 1};
				for (unsigned int k = 0; k < indexString.size (); k++)
				{
					char c = indexString[k];
					if (c == '\0') 
					{
						break;
					}
					else if (c == '/') 
					{
						if (indicesIndex == 2)
						{
							cerr << "Malformed face at line " << lineNumber << endl;
							goto Fail;
						}
						indicesIndex++;
					}
					else if (c >= '0' && c <= '9')
					{
						indices[indicesIndex] *= 10;
						indices[indicesIndex] += c - '0';
					}
					else if (c == '-' && indices[indicesIndex] == 0)
					{
						sign[indicesIndex] *= -1;
					}
					else 
					{
						cerr << "Malformed face at line " << lineNumber << endl;
						goto Fail;
					}
				}

				// handle offset-relative indices
				if (sign[0] == -1) indices[0] = vertices.size ()  + 1 - indices[0];
				if (sign[1] == -1) indices[1] = texcoords.size () + 1 - indices[1];
				if (sign[2] == -1) indices[2] = normals.size ()   + 1 - indices[2];

				// verify that indices given are sane
				if (indices[0] < 1 || indices[0] > vertices.size () )
				{
					cerr << "Invalid vertex index at line " << lineNumber << endl;
					goto Fail;
				}
				if (indices[1] < 0 || indices[1] > texcoords.size () )
				{
					cerr << "Invalid texcoord index at line " << lineNumber << endl;
					goto Fail;
				}
				if (indices[2] < 0 || indices[2] > normals.size () )
				{
					cerr << "Invalid normal index at line " << lineNumber << endl;
					goto Fail;
				}

				// subtract one and store
				f.vertices.push_back (indices[0] - 1);
				f.texcoords.push_back   (indices[1] - 1);
				f.normals.push_back  (indices[2] - 1);
			}

			// store file
			faces.push_back (f);
		}
		if (in.fail () ) 
		{
			cerr << "Failure reading model at line " << lineNumber << endl;
			goto Fail;
		}
	}

	in.close ();
	return true;

Fail:
	in.close ();
	vertices.resize (0);
	normals.resize (0);
	texcoords.resize (0);
	faces.resize (0);
	return false;
}

// save
bool Obj::Save (const string& fileName)
{
	// try to open out stream
	ofstream  out (fileName.c_str () );
	if (out.fail () )
	{
		cerr << "Failed to open " << fileName << " to save Obj" << endl;
		return false;
	}
	// spew vertices
	for (int i = 0; i < vertices.size (); i++)
	{
		out << "v " << vertices[i] << endl;
	}
	// normals
	for (int i = 0; i < normals.size (); i++)
	{
		out << "vn " << normals[i] << endl;
	}
	// texcoords
	for (int i = 0; i < texcoords.size (); i++)
	{
		out << "vt " << texcoords[i] << endl;
	}
	// faces
	for (int i = 0; i < faces.size (); i++)
	{
		out << "f ";
		for (int j = 0; j < faces[i].vertices.size (); j++)
		{
			out << faces[i].vertices[j] + 1;
			if (faces[i].normals[j] != -1 || faces[i].texcoords[j] != -1)
			{
				out << "/";
				if (faces[i].texcoords[j] != -1)
				{
					out << faces[i].texcoords[j] + 1;
				}
				if (faces[i].normals[j] != -1)
				{
					out << "/";
					out << faces[i].normals[j] + 1;
				}
				out << " ";
			}
		}
		out << endl;
	}
	// perfunctory error checking
	if (out.fail () )
	{
		cerr << "There was an error writing " << fileName << endl;
		return false;
	}
	out.close ();
	return true;
}

template<typename T>
void MergeList (vector<T>& merged, const vector<T>& original, map<int,int>& indices, double minDist)
{
	indices[-1] = -1;
	for (int i = 0; i < original.size (); i++)
	{
		int match = -1;
		for (int j = 0; j < merged.size (); j++)
		{
			if (norm (original[i] - merged[j]) <= minDist)
			{
				match = j;
				continue;
			}
		}
		if (match == -1)
		{
			match = merged.size ();
			merged.push_back (original[i]);
		}
		indices[i] = match;
	}	
}

void Obj::Merge (double minDist)
{
	// the new data for the obj
	vector<Vec3> mergedVertices;
	vector<Vec3> mergedNormals;
	vector<Vec2> mergedTexcoords;
	vector<Face> mergedFaces;
	// a mapping from old to new
	map<int,int> mergedVertexIndices;
	map<int,int> mergedNormalIndices;
	map<int,int> mergedTexelIndices;

	// merge vertices, normals, and texcoords
	MergeList (mergedVertices,  vertices,  mergedVertexIndices, minDist);
	MergeList (mergedNormals,   normals,   mergedNormalIndices, minDist);
	MergeList (mergedTexcoords, texcoords, mergedTexelIndices,  minDist);
	// create faces for new indices
	for (int i = 0; i < faces.size (); i++)
	{
		Face face;
		for (unsigned int j = 0; j < faces[i].vertices.size (); j++)
		{
			face.vertices.push_back (mergedVertexIndices[faces[i].vertices[j]]);
			face.normals.push_back  (mergedNormalIndices[faces[i].normals[j]]);
			face.texcoords.push_back   (mergedTexelIndices[faces[i].texcoords[j]]);
		}
		mergedFaces.push_back (face);
	}

	// swap the old data out for the new
	vertices = mergedVertices;
	normals = mergedNormals;
	texcoords = mergedTexcoords;
	faces = mergedFaces;
}

void Obj::Linearize ()
{
	#define triple pair<int,pair<int,int> >
	#define triplify(x,y,z) triple (x,pair<int,int> (y,z) );

	// create new vertices, normals, texcoords, and faces for the linearized obj
	map<triple,int> indices;

	for (unsigned int i = 0; i < faces.size (); i++)
	{
		for (unsigned int j = 0; j < faces[i].vertices.size (); j++)
		{
			triple t = triplify (faces[i].vertices[j], faces[i].normals[j], faces[i].texcoords[j]);
			int& m = indices[t];
			if (m == 0) m = indices.size ();
			faces[i].vertices[j] = m - 1;
			faces[i].normals[j] = m - 1;
			faces[i].texcoords[j] = m - 1;
		}
	}

	vector<Vec3> oldVertices = vertices;
	vector<Vec3> oldNormals = normals; 
	vector<Vec2> oldTexcoords = texcoords; texcoords.clear ();

	vertices.resize (indices.size () );
	normals.resize (indices.size () );
	texcoords.resize (indices.size () );

	for (map<triple,int>::iterator i = indices.begin (); i != indices.end (); i++)
	{
		int newIndex = (*i).second - 1;
		int oldIndexVertex   = (*i).first.first;
		int oldIndexNormal   = (*i).first.second.first;
		int oldIndexTexcoord = (*i).first.second.second;
		vertices[newIndex] = oldVertices[oldIndexVertex];
		normals[newIndex] = oldNormals[oldIndexNormal];
		texcoords[newIndex] = oldTexcoords[oldIndexTexcoord];
	}
}

void Obj::AverageNormals ()
{
	normals.resize (vertices.size () );
	for (int i = 0; i < normals.size (); i++)
	{
		normals[i] = 0;
	}
	for (int i = 0; i < faces.size (); i++)
	{
		Vec3 normal = cross (
			vertices[faces[i].vertices[1]] - vertices[faces[i].vertices[0]],
			vertices[faces[i].vertices[2]] - vertices[faces[i].vertices[0]]);
		for (int j = 0; j < faces[i].vertices.size (); j++)
		{
			normals[faces[i].vertices[j]] += normal;
			faces[i].normals[j] = faces[i].vertices[j];
		}
	}
	for (int i = 0; i < normals.size (); i++)
	{
		unitize (normals[i]);
	}
}

bool Obj::HasNormals ()
{
	for (int i = 0; i < faces.size (); i++)
	{
		for (int j = 0; j < faces[i].vertices.size (); j++)
		{
			if (faces[i].normals[j] == -1) return false;
		}
	}
	return true;
}

bool Obj::HasTexcoords ()
{
	for (int i = 0; i < faces.size (); i++)
	{
		for (int j = 0; j < faces[i].vertices.size (); j++)
		{
			if (faces[i].texcoords[j] == -1) return false;
		}
	}
	return true;
}

