#include "PrecompiledHeader.h"
#include "OBJ.h"

bool OBJ::LoadFromFile(std::string filename)
{
	m_Path = boost::filesystem::path(filename);

	// http://paulbourke.net/dataformats/obj/
	std::ifstream file(m_Path.string());
	if (!file.is_open())
	{
		LOG_ERROR("Failed to open .obj \"%s\": %s", m_Path.string().c_str(), strerror(errno));
		return false;
	}

	LOG_INFO("Parsing .obj \"%s\"", m_Path.string().c_str());

	std::string line;
	while (std::getline(file, line))
	{
		if (line.length() == 0)
			continue;

		std::stringstream ss(line);

		std::string prefix;
		ss >> prefix;

		// Ignore comments
		if (prefix == "#")
			continue;

		// Material files
		if (prefix == "mtllib")
		{
			std::string materialFilename;
			ss >> materialFilename;
			m_MaterialPath = m_Path.branch_path() / materialFilename;
			ParseMaterial();
			continue;
		}

		// Material statement
		if (prefix == "usemtl")
		{
			std::string material;
			ss >> material;
			m_CurrentMaterial = &Materials[material];
			continue;
		}

		// Vertices
		if (prefix == "v")
		{
			float x, y, z;
			ss >> x >> y >> z;
			Vertices.push_back(std::make_tuple(x, y, z));
			continue;
		}

		// Normals
		if (prefix == "vn")
		{
			float x, y, z;
			ss >> x >> y >> z;
			Normals.push_back(std::make_tuple(x, y, z));
		}

		// Texture coordinates
		if (prefix == "vt")
		{
			float u, v, w;
			ss >> u >> v >> w;
			TextureCoords.push_back(std::make_tuple(u, v, w));
		}

		// Face definitions
		if (prefix == "f")
		{
			Face face;
			face.Material = m_CurrentMaterial;

			std::string faceDefString;
			while (ss >> faceDefString)
			{
				std::stringstream ss2(faceDefString);
				FaceDefinition faceDef = { 0, 0, 0 };

				ss2 >> faceDef.VertexIndex;
				ss2.ignore(); // Ignore first delimiter
				if (!ss2)
					continue;

				if (ss2.peek() == '/')
				{
					ss2.ignore();
					ss2 >> faceDef.NormalIndex;
				}
				else
				{
					ss2 >> faceDef.TextureCoordIndex;
					ss2.ignore();
					ss2 >> faceDef.NormalIndex;
				}
				face.Definitions.push_back(faceDef);
			}
			Faces.push_back(face);
		}
	}

	return true;
}

void OBJ::ParseMaterial()
{
	// http://paulbourke.net/dataformats/mtl/
	std::ifstream file(m_MaterialPath.string());
	if (!file.is_open())
	{
		LOG_ERROR("Failed to open .mtl \"%s\"", m_MaterialPath.string().c_str());
		return;
	}

	LOG_INFO("Parsing .mtl \"%s\"", m_MaterialPath.string().c_str());

	std::string currentMaterialName;
	MaterialInfo* currentMaterial = nullptr;

	unsigned int currentLine = 0;
	std::string line;
	while (std::getline(file, line))
	{
		currentLine++;
		if (line.length() == 0)
			continue;

		std::stringstream ss(line);

		std::string prefix;
		ss >> prefix;

		// Create a new material definition
		if (prefix == "newmtl")
		{
			MaterialInfo mat;
			ss >> currentMaterialName;
			LOG_INFO("Parsing material %s", currentMaterialName.c_str());
			Materials[currentMaterialName] = mat;
			currentMaterial = &Materials[currentMaterialName];
			continue;
		}

		if (!currentMaterial)
			continue;

		// Ambient color
		if (prefix == "Ka")
		{
			float r, g, b;
			ss >> r >> g >> b;
			currentMaterial->AmbientColor = std::make_tuple(r, g, b);
			continue;
		}
		// Diffuse color
		if (prefix == "Kd")
		{
			float r, g, b;
			ss >> r >> g >> b;
			currentMaterial->DiffuseColor = std::make_tuple(r, g, b);
			continue;
		}
		// Specular color
		if (prefix == "Ks")
		{
			float r, g, b;
			ss >> r >> g >> b;
			currentMaterial->SpecularColor = std::make_tuple(r, g, b);
			continue;
		}
		// Transmission filter
		if (prefix == "Tf")
		{
			std::stringstream ss2;
			ss2 << ss.str();

			std::string command;
			ss2 >> command;
			if (command == "xyz")
			{
				// TODO: "The "Ks xyz" statement specifies the specular reflectivity using CIEXYZ values."
			}
			else if (command == "spectral")
			{
				// TODO: "The "Tf spectral" statement specifies the transmission filter using a spectral curve."
			}
			else
			{
				float r, g, b;
				ss >> r;
				// G and B are optional
				if (!(ss >> g >> b))
				{
					g = r;
					b = r;
				}
				currentMaterial->TransmissionFilter = std::make_tuple(r, g, b);
			}
			continue;
		}
		// Optical density
		if (prefix == "Ni")
		{
			ss >> currentMaterial->OpticalDensity;
			continue;
		}
		// Alpha
		if (prefix == "d" || prefix == "Tr")
		{
			ss >> currentMaterial->Alpha;
			continue;
		}
		// Shininess
		if (prefix == "Ns")
		{
			ss >> currentMaterial->Shininess;
			continue;
		}
		// Illumination model
		if (prefix == "illum")
		{
			int illum = 0;
			ss >> illum;
			currentMaterial->IlluminationModel = illum;
			continue;
		}
		// Diffuse texture
		if (prefix == "map_Kd")
		{
			MaterialInfo::ColorMap colorMap;

			std::string fileName;
			std::string arg;
			while (ss >> arg)
			{
				ParseColorMap(currentLine, ss, prefix, arg, colorMap);
			}

			// HACK: Should we really have to specify the full path here?
			colorMap.FileName = (m_MaterialPath.branch_path() / colorMap.FileName).string();
			currentMaterial->DiffuseTexture = colorMap;
			continue;
		}
		// Specular map
		if (prefix == "map_Ks")
		{
			MaterialInfo::ColorMap colorMap;

			std::string fileName;
			std::string arg;
			while (ss >> arg)
			{
				ParseColorMap(currentLine, ss, prefix, arg, colorMap);
			}

			// HACK: Should we really have to specify the full path here?
			colorMap.FileName = (m_MaterialPath.branch_path() / colorMap.FileName).string();
			currentMaterial->SpecularMap = colorMap;
			continue;
		}
		// Normal map (bump map)
		if (prefix == "bump")
		{
			MaterialInfo::BumpMap bumpMap;

			std::string fileName;
			std::string arg;
			while (ss >> arg)
			{
				ParseBumpMap(currentLine, ss, prefix, arg, bumpMap);
			}

			// HACK: Should we really have to specify the full path here?
			bumpMap.FileName = (m_MaterialPath.branch_path() / bumpMap.FileName).string();
			currentMaterial->NormalMap = bumpMap;
			continue;
		}
	}
}

void OBJ::ParseTextureMap(unsigned int line, std::stringstream &ss, std::string prefix, std::string arg, MaterialInfo::TextureMap &textureMap)
{
	if (arg == "-blendu")
	{
		std::string val;
		ss >> val;
		if (val == "off")
			textureMap.blendu = false;
		else if (val == "on")
			textureMap.blendu = true;
		else
			LOG_ERROR("Unrecognized MTL value \"%s\" to argument \"%s %s\" on line %i", val.c_str(), prefix.c_str(), arg.c_str(), line);
	}
	else if (arg == "-blendv")
	{
		std::string val;
		ss >> val;
		if (val == "off")
			textureMap.blendv = false;
		else if (val == "on")
			textureMap.blendv = true;
		else
			LOG_ERROR("Unrecognized MTL value \"%s\" to argument \"%s %s\" on line %i", val.c_str(), prefix.c_str(), arg.c_str(), line);
	}
	else if (arg == "-clamp")
	{
		std::string val;
		ss >> val;
		if (val == "off")
			textureMap.clamp = false;
		else if (val == "on")
			textureMap.clamp = true;
		else
			LOG_ERROR("Unrecognized MTL value \"%s\" to argument \"%s %s\" on line %i", val.c_str(), prefix.c_str(), arg.c_str(), line);
	}
	else if (arg == "-o")
	{
		float u, v, w;
		if (ss >> u >> v >> w)
		{
			textureMap.o = std::make_tuple(u, v, w);
		}
		else
		{
			LOG_ERROR("Unrecognized MTL value to argument \"%s %s\" on line %i", prefix.c_str(), arg.c_str(), line);
		}
	}
	else if (arg == "-s")
	{
		float u, v, w;
		if (ss >> u >> v >> w)
		{
			textureMap.s = std::make_tuple(u, v, w);
		}
		else
		{
			LOG_ERROR("Unrecognized MTL value to argument \"%s %s\" on line %i", prefix.c_str(), arg.c_str(), line);
		}
	}
	// Assume unrecognized options is part of the file name
	else
	{
		if (!textureMap.FileName.empty())
			textureMap.FileName += " ";
		textureMap.FileName += arg;
	}
}

void OBJ::ParseColorMap(unsigned int line, std::stringstream &ss, std::string prefix, std::string arg, MaterialInfo::ColorMap &colorMap)
{
	if (arg == "-cc")
	{
		if (prefix != "map_Kd" || prefix != "map_Ks")
		{
			LOG_ERROR("Invalid MTL argument \"%s\" to option \"%s\" on line %i", arg.c_str(), prefix.c_str(), line);
			return;
		}

		std::string val;
		if (ss >> val)
		{
			if (val == "off")
				colorMap.cc = false;
			else if (val == "on")
				colorMap.cc = true;
			else
				LOG_ERROR("Unrecognized MTL value \"%s\" to argument \"%s %s\" on line %i", val.c_str(), prefix.c_str(), arg.c_str(), line);
		}
		else
		{
			LOG_ERROR("Unrecognized MTL value to argument \"%s %s\" on line %i", prefix.c_str(), arg.c_str(), line);
		}
	}
	else
	{
		ParseTextureMap(line, ss, prefix, arg, colorMap);
	}
}

void OBJ::ParseBumpMap(unsigned int line, std::stringstream &ss, std::string prefix, std::string arg, MaterialInfo::BumpMap &bumpMap)
{
	if (arg == "-bm")
	{
		if (prefix != "bump")
		{
			LOG_ERROR("Invalid MTL argument \"%s\" to option \"%s\" on line %i", arg.c_str(), prefix.c_str(), line);
			return;
		}

		float val;
		if (ss >> val)
			bumpMap.bm = val;
		else
			LOG_ERROR("Unrecognized MTL value to argument \"%s %s\" on line %i", prefix.c_str(), arg.c_str(), line);
	}
	else
	{
		ParseTextureMap(line, ss, prefix, arg, bumpMap);
	}
}