/*
 * $Id: objparser.cpp,v 1.56 2008-06-25 22:50:29 kybav1 Exp $
 *
 * Description  : One line description of file.
 * Author       : Antonin Misek <antonin.misek@skoda-auto.cz>
 *
 * Purpose      :
 *     Long description of what the file is for.
 */


#include "scenemodule.h"
#include <wx/filename.h>
#include "objparser.h"

using namespace VRUT;


OBJParser::OBJParser(wxInputStream * _inputStream, SCENE_ID _sceneID, const wxString _scenePath, Module * _module)
: inputStream(_inputStream), sceneID(_sceneID), module(_module)
{
	fileName = wxFileName(_scenePath).GetName();
	isMaterialLib = false;
}


OBJParser::~OBJParser()
{
	///Is called by wxWidgets itself on wxApp end but it doesn't work in dynamic library
}

bool OBJParser::parseIndices(TextParser &textParser, GeometryTriangles *geoTri, GeometryTriangles::PRIMITIVE_TYPE primType, std::vector<Vector3> &v,
			std::vector<GeometryTriangles::TexCoord> &vt, std::vector<Vector3> &vn)
{
	std::vector<Vector3> * vertices = &(geoTri->vertices);
	std::vector<Vector3> * normals = &(geoTri->normals);
	std::vector<GeometryTriangles::TexCoord> * texCoords = &(geoTri->texCoords);
	std::vector<GeometryTriangles::Index> * indices = &(geoTri->indices);

	std::pair<GeometryTriangles::Index, GeometryTriangles::PRIMITIVE_TYPE> indType((GeometryTriangles::Index)indices->size(), primType);
	geoTri->triDescList.push_back(indType);
	size_t maxV=v.size()+1, maxVT=vt.size()+1, maxVN=vn.size()+1;
	//read indices
	while (!textParser.NextCharIs('\n'))
	{
		long i;
		if (!textParser.ParseLong(&i, NULL, false))
			break;
		if ((i == 0) || (labs(i) > long(maxV)))
		{
			LOGERROR(wxString::Format(wxT("<OBJParser>Error parseIndices, v=%li, maxV=%li "), i, (unsigned long)maxV)+textParser.GetWxString("\r\n"));
			return false;
		}
		vertices->push_back(v[(i>0)?i-1:maxV+i-1]);
		indices->push_back((GeometryTriangles::Index)(vertices->size()-1));
		i=0;
		if (textParser.NextCharIs('/', NULL, false))
		{
			textParser.GetChar(NULL, false);
			if (!textParser.NextCharIs('/', NULL, false))
			{
				textParser.ParseLong(&i, NULL, false);//read texture index
				if ((i == 0) || (labs(i) > long(maxVT)))
				{
					LOGERROR(wxString::Format(wxT("<OBJParser>Error parseIndices, vt=%li, maxVT=%li "), i, (unsigned long)maxVT)+textParser.GetWxString("\r\n"));
					return false;
				}
				texCoords->push_back(vt[(i>0)?i-1:maxVT+i-1]);
			}
		}
		//if (i==0)
		//	texCoords->push_back(GeometryTriangles::TexCoord(0,0));
		i=0;
		if (textParser.NextCharIs('/', NULL, false))
		{
			textParser.GetChar(NULL, false);
			if (!textParser.NextCharIs('/', NULL, false))
			{
				textParser.ParseLong(&i, NULL, false);//read normal index
				if ((i == 0) || (labs(i) > long(maxVN)))
				{
					LOGERROR(wxString::Format(wxT("<OBJParser>Error parseIndices, vn=%li, maxVN=%li "), i, (unsigned long)maxVN)+textParser.GetWxString("\r\n"));
					return false;
				}
				normals->push_back(vn[(i>0)?i-1:maxVN+i-1]);
			}
		}
		//if (i==0)
		//	normals->push_back(Vector3(0,0,0));
		if (textParser.NextCharIs('\\'))
			textParser.GetWxLine();
	}
	return true;
}

void OBJParser::createNodeGeometryMaterial(Scene *scene, GeometryTriangles *geoTri, NODE_ID rootID, const wxString &nodeName, const wxString &materialName, const bool optimize)
{
	//prepare material
	if (materialSet.insert(materialName).second) {
		Material * material = new Material(materialName);
		//create new "default" material with adequate name
		material->diffuse = Vector4(0.5, 0.5, 0.5, 1);
		material->specular = Vector4(0.5, 0.5, 0.5, 1);
		material->flags = Material::DOUBLE_SIDED;
		module->PostToKernel(GET_EVT_SCENE_MATERIAL_ADD(sceneID, material));
	}

	GeometryNode * node = new GeometryNode(nodeName);
	NODE_ID nodeId = scene->GetNewNodeID();
	node->SetID(nodeId);
	module->PostToKernel(GET_EVT_SCENE_NODE_INSERT(sceneID, node, rootID));
	module->PostToKernel(GET_EVT_SCENE_NODE_MATERIAL_SET(sceneID, nodeId, materialName));

	///Assign mesh
	GEOMETRY_ID gid=geoTri->GetID();
	if (optimize)
	{
		IOObjJob *job = new IOObjJob(scene, geoTri);
		KERNEL->GetMessageSink()->PostJob(job);
	}
	else
	{
		//geoTri->UnifyVertices();
		module->PostToKernel(GET_EVT_SCENE_GEOMETRY_ADD(sceneID, geoTri));
	}
	module->PostToKernel(GET_EVT_SCENE_NODE_GEOMETRY_SET(sceneID, nodeId, gid));
}

bool OBJParser::parseFile(Scene *scene, NODE_ID rootID, const bool optimize)
{
	TextParser textParser(inputStream, 100000, 100);
//	newName = textParser.GetWxString("\r\n");
	///prepare

	std::vector<Vector3> v;
	std::vector<GeometryTriangles::TexCoord> vt;
	std::vector<Vector3> vn;
	///Create new mesh
	GeometryTriangles *geoTri = new GeometryTriangles(scene->GetNewGeometryID());

	///parse
	const char *keywords[]={"v", "vn", "vt", "f", "l", "p", "g", "s", "mg", "o", "lod", "usemtl", "mtllib", "shadow_obj", 
		"vp", "cstype", "deg", "bmat", "step", "curv", "curv2", "surf", "parm", "trim", "hole", "scrv", "sp", "end", "con", "bevel", "c_interp", "d_interp", "trace_obj", "ctech", "stech", ""};
/*
o       parameter space vertices (vp)
	   Free-form curve/surface attributes
o       rational or non-rational forms of curve or surface type:
	   basis matrix, Bezier, B-spline, Cardinal, Taylor (cstype)
o       degree (deg)
o       basis matrix (bmat)
o       step size (step)

Elements

o       curve (curv)
o       2D curve (curv2)
o       surface (surf)

Free-form curve/surface body statements

o       parameter values (parm)
o       outer trimming loop (trim)
o       inner trimming loop (hole)
o       special curve (scrv)
o       special point (sp)
o       end statement (end)

Connectivity between free-form surfaces

o       connect (con)

Display/render attributes

o       bevel interpolation (bevel)
o       color interpolation (c_interp)
o       dissolve interpolation (d_interp)
o       ray tracing (trace_obj)
o       curve approximation technique (ctech)
o       surface approximation technique (stech)
*/
	while (!textParser.IsEmpty())
	{
		switch (textParser.KeywordsSwitch(keywords))
		{
		case 0://v - geometric vertices
			{
				Vector3 vert;
				if (textParser.ParseVector(vert._v, 3)<3)
				{
					LOGERROR(wxT("<OBJParser>Error parsing 'v'."));
					return false;
				}
				v.push_back(Vector3(vert._v));
				break;
			}
		case 1://vn - vertex normals
			{
				Vector3 norm;
				if (textParser.ParseVector(norm._v, 3) != 3)
				{
					LOGERROR(wxT("<OBJParser>Error parsing 'vn'."));
					return false;
				}
				vn.push_back(norm);
				break;
			}
		case 2://vt - texture vertices
			{
				Vector3 texCoord;
				if (textParser.ParseVector(texCoord._v, 3) < 2)
				{
					LOGERROR(wxT("<OBJParser>Error parsing 'vt'."));
					return false;
				}
				vt.push_back(GeometryTriangles::TexCoord(texCoord.x, texCoord.y));
				break;
			}
		case 3://f - face
			if (!parseIndices(textParser, geoTri, GeometryTriangles::POLYGON, v, vt, vn))
			{
				LOGERROR(wxT("<OBJParser>Error parsing 'f'."));
				return false;
			}
			break;
		case 4://l - line
			if (!parseIndices(textParser, geoTri, GeometryTriangles::LINES, v, vt, vn))
			{
				LOGERROR(wxT("<OBJParser>Error parsing 'l'."));
				return false;
			}
			break;
		case 5://p - point
			if (!parseIndices(textParser, geoTri, GeometryTriangles::POINTS, v, vt, vn))
			{
				LOGERROR(wxT("<OBJParser>Error parsing 'p'."));
				return false;
			}
			break;
		case 6://g - group name
			if (!geoTri->triDescList.empty())
			{
				createNodeGeometryMaterial(scene, geoTri, rootID, nodeName, materialName, optimize);
				geoTri = new GeometryTriangles(scene->GetNewGeometryID());
			}
			textParser.SkipChars(" \t");
			nodeName=textParser.GetWxLine();
			break;
		case 7://s - smoothing group
			textParser.GetWxLine();
			break;
		case 8://mg - merging group
			break;
		case 9://o - object name
			if (!geoTri->triDescList.empty())
			{
				createNodeGeometryMaterial(scene, geoTri, rootID, nodeName, materialName, optimize);
				geoTri = new GeometryTriangles(scene->GetNewGeometryID());
			}
			textParser.SkipChars(" \t");
			nodeName=textParser.GetWxLine();
			break;
		case 10://lod - level of detail
			break;
		case 11://usemtl - material name
			if (!geoTri->triDescList.empty())
			{
				createNodeGeometryMaterial(scene, geoTri, rootID, nodeName, materialName, optimize);
				geoTri = new GeometryTriangles(scene->GetNewGeometryID());
			}
			textParser.SkipChars(" \t");
			materialName=textParser.GetWxLine();
			break;
		case 12://mtllib - material library
			textParser.SkipChars(" \t");
			materialLib=textParser.GetWxLine();
			isMaterialLib = true;
			LOG(wxString::Format(wxT("<OBJParser>material lib, "+materialLib)));
			break;
		case 13://shadow_obj - shadow casting
			break;
		default:
			LOGERROR(wxT("<OBJParser>Unexpected token: '" + textParser.GetWxLine() + wxT("'")));
			return false;
		}
	}

	//compute normals
	/*if(geoTri->normals.size() != geoTri->vertices.size())
		geoTri->ComputeNormals(45.0f/180.0f*3.14159f);*/

	if (!geoTri->triDescList.empty())
		createNodeGeometryMaterial(scene, geoTri, rootID, nodeName, materialName, optimize);
		
	return true;
}

bool OBJParser::Parse(const NODE_ID &rootID, const bool optimize)
{
	smart_pointer_v2<Scene> scene;
	KERNEL->sceneManager->GetScene(scene, sceneID);
	if (!scene)
		return false;

	materialName = wxT("OBJDefault");
	nodeName = fileName + wxT("_node");

	//parse
	if (!parseFile(scene.getPtr(), rootID, optimize))
	{
		LOGERROR(wxT("<OBJParser>Scene not loaded properly"));
		return false;
	}
	return true;
}

bool OBJParser::isMatLib() 
{
	return isMaterialLib;
}

wxString OBJParser::getMatLibName() 
{
	return materialLib;
}
