////////////////////////////////////////////////////////////////////
// OBJ.cpp
//
// Copyright 2007 cDc@seacave
// Distributed under the Boost Software License, Version 1.0
// (See http://www.boost.org/LICENSE_1_0.txt)

#include "Common.h"
#include "OBJ.h"

using namespace SEACAVE;


// D E F I N E S ///////////////////////////////////////////////////

// uncomment to enable multi-threading based on OpenMP
#ifdef _USE_OPENMP
#define OBJ_USE_OPENMP
#endif

#define OBJ_INDEX_OFFSET 1


// S T R U C T S ///////////////////////////////////////////////////

ObjModel::MaterialLib::Material::Material(const Image8U3& _diffuse_map, const Color& _Kd)
	:
	diffuse_map(_diffuse_map),
	Kd(_Kd)
{
}

bool ObjModel::MaterialLib::Material::LoadDiffuseMap()
{
	if (diffuse_map.empty())
		return diffuse_map.Load(diffuse_name);
	return true;
}
/*----------------------------------------------------------------*/



// S T R U C T S ///////////////////////////////////////////////////

ObjModel::MaterialLib::MaterialLib()
{
}

bool ObjModel::MaterialLib::Save(const String& prefix, bool texLossless) const
{
	std::ofstream out((prefix+".mtl").c_str());
	if (!out.good())
		return false;

	const String pathName(Util::getFilePath(prefix));
	const String name(Util::getFileNameExt(prefix));
	#ifdef OBJ_USE_OPENMP
	bool bSuccess(true);
	#pragma omp parallel for
	#endif
	for (int_t i = 0; i < (int_t)materials.size(); ++i) {
		const Material& mat = materials[i];
		// save material description
		std::stringstream ss;
		ss << "newmtl " << mat.name << "\n"
			<< "Ka 1.000000 1.000000 1.000000" << "\n"
			<< "Kd " << mat.Kd.r << " " << mat.Kd.g << " " << mat.Kd.b << "\n"
			<< "Ks 0.000000 0.000000 0.000000" << "\n"
			<< "Tr 1.000000" << "\n"
			<< "illum 1" << "\n"
			<< "Ns 1.000000" << "\n";
		// save material maps
		if (mat.diffuse_map.empty()) {
			#ifdef OBJ_USE_OPENMP
			#pragma omp critical
			#endif
			out << ss.str();
			continue;
		}
		if (mat.diffuse_name.IsEmpty())
			const_cast<String&>(mat.diffuse_name) = name+"_"+mat.name+"_map_Kd."+(texLossless?"png":"jpg");
		ss << "map_Kd " << mat.diffuse_name << "\n";
		#ifdef OBJ_USE_OPENMP
		#pragma omp critical
		#endif
		out << ss.str();
		const bool bRet(mat.diffuse_map.Save(pathName+mat.diffuse_name));
		#ifdef OBJ_USE_OPENMP
		#pragma omp critical
		if (!bRet)
			bSuccess = false;
		#else
		if (!bRet)
			return false;
		#endif
	}
	#ifdef OBJ_USE_OPENMP
	return bSuccess;
	#else
	return true;
	#endif
}

bool ObjModel::MaterialLib::Load(const String& fileName)
{
	const size_t numMaterials(materials.size());
	std::ifstream in(fileName.c_str());
	String keyword;
	while (in.good() && in >> keyword) {
		if (keyword == "newmtl") {
			in >> keyword;
			materials.push_back(Material(keyword));
		} else if (keyword == "Kd") {
			ASSERT(numMaterials < materials.size());
			Color c;
			in >> c.r >> c.g >> c.b;
			materials.back().Kd = c;
		} else if (keyword == "map_Kd") {
			ASSERT(numMaterials < materials.size());
			String& diffuse_name = materials.back().diffuse_name;
			in >> diffuse_name;
			diffuse_name = Util::getFilePath(fileName) + diffuse_name;
		}
	}
	return numMaterials < materials.size();
}
/*----------------------------------------------------------------*/



// S T R U C T S ///////////////////////////////////////////////////

bool ObjModel::Save(const String& fileName, unsigned precision, bool texLossless) const
{
	if (vertices.empty())
		return false;
	const String prefix(Util::getFileFullName(fileName));
	const String name(Util::getFileNameExt(prefix));

	if (!material_lib.Save(prefix, texLossless))
		return false;

	std::ofstream out((prefix + ".obj").c_str());
	if (!out.good())
		return false;

	out << "mtllib " << name << ".mtl" << "\n";

	out << std::fixed << std::setprecision(precision);
	for (size_t i = 0; i < vertices.size(); ++i) {
		out << "v "
			<< vertices[i][0] << " "
			<< vertices[i][1] << " "
			<< vertices[i][2] << "\n";
	}

	for (size_t i = 0; i < texcoords.size(); ++i) {
		out << "vt "
			<< texcoords[i][0] << " "
			<< texcoords[i][1] << "\n";
	}

	for (size_t i = 0; i < normals.size(); ++i) {
		out << "vn "
			<< normals[i][0] << " "
			<< normals[i][1] << " "
			<< normals[i][2] << "\n";
	}

	for (size_t i = 0; i < groups.size(); ++i) {
		out << "usemtl " << groups[i].material_name << "\n";
		for (size_t j = 0; j < groups[i].faces.size(); ++j) {
			const Face& face =  groups[i].faces[j];
			out << "f";
			for (size_t k = 0; k < 3; ++k) {
				out << " " << face.vertices[k]  + OBJ_INDEX_OFFSET;
				if (!texcoords.empty()) {
					out << "/" << face.texcoords[k]  + OBJ_INDEX_OFFSET;
					if (!normals.empty())
						out << "/" << face.normals[k]  + OBJ_INDEX_OFFSET;
				} else
				if (!normals.empty())
					out << "//" << face.normals[k]  + OBJ_INDEX_OFFSET;
			}
			out << "\n";
		}
	}
	return true;
}

bool ObjModel::Load(const String& fileName)
{
	ASSERT(vertices.empty() && groups.empty() && material_lib.materials.empty());
	std::ifstream fin(fileName.c_str());
	String line, keyword;
	std::istringstream in;
	while (fin.good()) {
		std::getline(fin, line);
		if (line.empty() || line[0u] == '#')
			continue;
		in.str(line);
		in >> keyword;
		if (keyword == "v") {
			Vertex v;
			in >> v[0] >> v[1] >> v[2];
			if (in.fail())
				return false;
			vertices.push_back(v);
		} else
		if (keyword == "vt") {
			TexCoord vt;
			in >> vt[0] >> vt[1];
			if (in.fail())
				return false;
			texcoords.push_back(vt);
		} else
		if (keyword == "vn") {
			Normal vn;
			in >> vn[0] >> vn[1] >> vn[2];
			if (in.fail())
				return false;
			normals.push_back(vn);
		} else
		if (keyword == "f") {
			Face f;
			memset(&f, 0xFF, sizeof(Face));
			for (size_t k = 0; k < 3; ++k) {
				in >> keyword;
				switch (sscanf(keyword, "%u/%u/%u", f.vertices+k, f.texcoords+k, f.normals+k)) {
				case 1:
					f.vertices[k] -= OBJ_INDEX_OFFSET;
					break;
				case 2:
					f.vertices[k] -= OBJ_INDEX_OFFSET;
					if (f.texcoords[k] != NO_ID)
						f.texcoords[k] -= OBJ_INDEX_OFFSET;
					if (f.normals[k] != NO_ID)
						f.normals[k] -= OBJ_INDEX_OFFSET;
					break;
				case 3:
					f.vertices[k] -= OBJ_INDEX_OFFSET;
					f.texcoords[k] -= OBJ_INDEX_OFFSET;
					f.normals[k] -= OBJ_INDEX_OFFSET;
					break;
				default:
					return false;
				}
			}
			if (in.fail())
				return false;
			if (groups.empty())
				AddGroup("");
			groups.back().faces.push_back(f);
		} else
		if (keyword == "mtllib") {
			in >> keyword;
			if (!material_lib.Load(keyword))
				return false;
		} else
		if (keyword == "usemtl") {
			Group group;
			in >> group.material_name;
			if (in.fail())
				return false;
			groups.push_back(group);
		}
		in.clear();
	}
	return !vertices.empty();
}


ObjModel::Group& ObjModel::AddGroup(const String& material_name)
{
	groups.push_back(Group());
	Group& group = groups.back();
	group.material_name = material_name;
	if (!GetMaterial(material_name))
		material_lib.materials.push_back(MaterialLib::Material(material_name));
	return group;
}

ObjModel::MaterialLib::Material* ObjModel::GetMaterial(const String& name)
{
	MaterialLib::Materials::iterator it(std::find_if(material_lib.materials.begin(), material_lib.materials.end(), [&name](const MaterialLib::Material& mat) { return mat.name == name; }));
	if (it == material_lib.materials.end())
		return NULL;
	return &(*it);
}
/*----------------------------------------------------------------*/
