// Copyright (C) 2002-2006 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CXFileReader.h"
#include "os.h"
#include "fast_atof.h"
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


namespace irr
{
namespace scene
{

CXFileReader::CXFileReader(io::IReadFile* file)
: ErrorHappened(false), Buffer(0), Size(0), P(0), End(0), binary(false), m_pgCurFrame(0)
{
	if (!file)
	{
		ErrorHappened = true;
		return;
	}

	if (!readFileIntoMemory(file))
	{
		ErrorHappened = true;
		return;
	}

	if (!parseFile())
	{
		ErrorHappened = true;
		return;
	}

#ifdef _XREADER_DEBUG
	for( unsigned int i = 0; i < RootFrames.size(); i++ )
		validateMesh(&RootFrames[i]);
#endif

	for( unsigned int i = 0; i < RootFrames.size(); i++ )
	{
		computeGlobalFrameMatrices(RootFrames[i], 0 );
	}
}


CXFileReader::~CXFileReader()
{
	if (Buffer)
		delete [] Buffer;

	for (u32 i=0; i<AnimationSets.size(); ++i)
		for (u32 j=0; j<AnimationSets[i].Animations.size(); ++j)
			for (u32 k=0; k<AnimationSets[i].Animations[j].Keys.size(); ++k)
				AnimationSets[i].Animations[j].Keys[k].del();
}


//! Returns an error occured during reading the file
bool CXFileReader::errorHappened() const
{
	return ErrorHappened;
}


//! checks if there was an error loading the mesh
bool CXFileReader::validateMesh(SXFrame* frame)
{
	bool error = false;
	char tmp[1024];

	// validate all meshes in this frame

	for (u32 m=0; m<frame->Meshes.size(); ++m)
	{
		s32 vcnt = frame->Meshes[m].Vertices.size();
		s32 icnt = frame->Meshes[m].Indices.size();
		s32 ncnt = frame->Meshes[m].Normals.size();
		s32 nicnt = frame->Meshes[m].NormalIndices.size();

		// validate indices in mesh
		s32 i;

		for (i=0; i<icnt; ++i)
		{
			if (frame->Meshes[m].Indices[i] < 0)
			{
				sprintf(tmp, "XLoader error: index %d smaller than 0 in mesh %u: %d, frame '%s'",
					i, m, frame->Meshes[m].Indices[i], frame->Name.c_str());
				os::Printer::log(tmp, ELL_ERROR);
				error = true;
			}

			if (frame->Meshes[m].Indices[i] > vcnt-1)
			{
				sprintf(tmp, "XLoader error: invalid index %d in mesh %u: %d, frame '%s'",
					i, m, frame->Meshes[m].Indices[i], frame->Name.c_str());
				os::Printer::log(tmp, ELL_ERROR);
				error = true;
			}
		}

		// validate normal indices

		for (i=0; i<nicnt; ++i)
		{
			if (frame->Meshes[m].NormalIndices[i] < 0)
			{
				sprintf(tmp, "XLoader error: normal index %d smaller than 0 in mesh %u: %d, frame '%s'",
					i, m, frame->Meshes[m].NormalIndices[i], frame->Name.c_str());
				os::Printer::log(tmp, ELL_ERROR);
				error = true;
			}

			if (frame->Meshes[m].NormalIndices[i] > ncnt-1)
			{
				sprintf(tmp, "XLoader error: invalid normal index %d in mesh %u: %d, frame '%s'",
					i, m, frame->Meshes[m].NormalIndices[i], frame->Name.c_str());
				os::Printer::log(tmp, ELL_ERROR);
				error = true;
			}
		}
	}


	// validate child frames
	for (u32 i=0; i<frame->ChildFrames.size(); ++i)
		if (!validateMesh(&frame->ChildFrames[i]))
			error = true;

	return error;
}


//! Reads file into memory
bool CXFileReader::readFileIntoMemory(io::IReadFile* file)
{
	s32 Size = file->getSize();
	if (Size < 12)
	{
		os::Printer::log("X File is too small.", ELL_WARNING);
		return false;
	}

	Buffer = new c8[Size];

	//! read all into memory
	file->seek(0); // apparently sometimes files have been read already, so reset it
	if (file->read(Buffer, Size) != Size)
	{
		os::Printer::log("Could not read from x file.", ELL_WARNING);
		return false;
	}

	End = Buffer + Size;

	//! check header "xof "
	if (strncmp(Buffer, "xof ", 4)!=0)
	{
		os::Printer::log("Not an x file, wrong header.", ELL_WARNING);
		return false;
	}

	//! read minor and major version, e.g. 0302 or 0303
	c8 tmp[3];
	tmp[2] = 0x0;
	tmp[0] = Buffer[4];
	tmp[1] = Buffer[5];
	MajorVersion = strtol(tmp, (char**) &P, 10);

	tmp[0] = Buffer[6];
	tmp[1] = Buffer[7];
	MinorVersion = strtol(tmp, (char**) &P, 10);

	//! read format
	if (strncmp(&Buffer[8], "txt ", 4) ==0)
		binary = false;
	else if (strncmp(&Buffer[8], "bin ", 4) ==0)
		binary = true;
	else
	{
		os::Printer::log("Only uncompressed x files currently supported.", ELL_WARNING);
		return false;
	}
	binaryNumCount=0;

	//! read float size
	if (strncmp(&Buffer[12], "0032", 4) ==0)
		FloatSize = 4;
	else if (strncmp(&Buffer[12], "0064", 4) ==0)
		FloatSize = 8;
	else
	{
		os::Printer::log("Float size not supported.", ELL_WARNING);
		return false;
	}

	P = &Buffer[16];

	readUntilEndOfLine();

	return true;
}

//! Parses the file
bool CXFileReader::parseFile()
{
	u32 u32Idx;
	while(parseDataObject())
	{
		// loop
	}
	// loop through hiearchy and combine frames that have no mesh
	// and no name into its parent
	
	m_bFrameRemoved = false;
	for( u32Idx = 0; u32Idx < RootFrames.size(); u32Idx++ )
	{
		optimizeFrames( &RootFrames[ u32Idx ], 0 );
	}
	while( m_bFrameRemoved )
	{
		m_bFrameRemoved = false;
		for( u32Idx = 0; u32Idx < RootFrames.size(); u32Idx++ )
		{
			optimizeFrames( &RootFrames[ u32Idx ], 0 );
		}
	}
	return true;
}

//! loop through hiearchy and combine frames that have no mesh or name into parent frame
void CXFileReader::optimizeFrames( SXFrame * pgFrame,  SXFrame * pgParent )
{
	if( pgParent )
	{
		if( (0 == pgParent->Meshes.size()) &&
			(0 == strlen( pgFrame->Name.c_str() )) &&
			strlen( pgParent->Name.c_str() ) )
		{
			// combine this frame with parent
			// add child frames to parent
			pgParent->LocalMatrix = pgParent->LocalMatrix * pgFrame->LocalMatrix;

			u32 c;

			for( c=0; c<pgFrame->ChildFrames.size(); ++c )
			{
				// add child frames to parent
				for (u32 c=0; c<pgFrame->ChildFrames.size(); ++c)
				{
					pgParent->ChildFrames.push_back(pgFrame->ChildFrames[c]);
				}
			}

			// add meshes to parent

			for( c=0; c<pgFrame->Meshes.size(); ++c )
			{
				// add meshes frames to parent
				pgParent->Meshes.push_back( pgFrame->Meshes[c] );
			}

			// remove child fames in our list
			pgFrame->ChildFrames.clear();

			// remove meshes
			pgFrame->Meshes.clear();

			// find ourselve and remove from parent frame
			for( c=0; c< pgParent->ChildFrames.size(); ++c )
			{
				if( &pgParent->ChildFrames[c] == pgFrame )
				{
					//found ourself
					pgParent->ChildFrames.erase( c, 1 );
					m_bFrameRemoved = true;
					return;
				}
			}
		}
	}
	for (u32 c=0; c<pgFrame->ChildFrames.size(); ++c)
		optimizeFrames( &pgFrame->ChildFrames[c], pgFrame );
}


//! Parses the next Data object in the file
bool CXFileReader::parseDataObject()
{
	core::stringc objectName = getNextToken();

	if (objectName.size() == 0)
		return false;

	// parse specific object

	if (objectName == "template")
		return parseDataObjectTemplate();
	else
	if (objectName == "Frame")
	{
		RootFrames.push_back(SXFrame());
		m_pgCurFrame = &RootFrames[ RootFrames.size() - 1 ];
		return parseDataObjectFrame( * m_pgCurFrame );
	}
	else
	if (objectName == "Mesh")
	{
		// some meshes have no frames at all
		if (!m_pgCurFrame)
		{
			RootFrames.push_back(SXFrame());
			m_pgCurFrame = &RootFrames[ RootFrames.size() - 1 ];
		}
		m_pgCurFrame->Meshes.push_back(SXMesh());
		return parseDataObjectMesh(m_pgCurFrame->Meshes[m_pgCurFrame->Meshes.size()-1]);
	}
	else
	if (objectName == "AnimationSet")
	{
		AnimationSets.push_back(SXAnimationSet());
		return parseDataObjectAnimationSet(AnimationSets[AnimationSets.size()-1]);
	}
	else
	if (objectName == "Material")
	{
		// template materials now available thanks to joeWright
		TemplateMaterials.push_back(SXTemplateMaterial());
		TemplateMaterials.getLast().Name = getNextToken();
		return parseDataObjectMaterial(TemplateMaterials.getLast().Material);
	}

	os::Printer::log("Unknown data object in x file", objectName.c_str());

	return parseUnknownDataObject();
}


bool CXFileReader::parseDataObjectFrame(SXFrame& frame)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading frame");
#endif

	// A coordinate frame, or "frame of reference." The Frame template
	// is open and can contain any object. The Direct3D extensions (D3DX)
	// mesh-loading functions recognize Mesh, FrameTransformMatrix, and
	// Frame template instances as child objects when loading a Frame
	// instance.

	if (!readHeadOfDataObject(&frame.Name))
	{
		os::Printer::log("No opening brace in Frame found in x file", ELL_WARNING);
		return false;
	}

	// Now inside a frame.
	// read tokens until closing brace is reached.

	while(true)
	{
		core::stringc objectName = getNextToken();

		if (objectName.size() == 0)
		{
			os::Printer::log("Unexpected ending found in Frame in x file.", ELL_WARNING);
			return false;
		}
		else
		if (objectName == "}")
		{
			break; // frame finished
		}
		else
		if (objectName == "Frame")
		{
			frame.ChildFrames.push_back(SXFrame());
			if (!parseDataObjectFrame(frame.ChildFrames[frame.ChildFrames.size()-1]))
				return false;
		}
		else
		if (objectName == "FrameTransformMatrix")
		{
			if (!parseDataObjectTransformationMatrix(frame.LocalMatrix))
				return false;
		}
		else
		if (objectName == "Mesh")
		{
			frame.Meshes.push_back(SXMesh());
			if (!parseDataObjectMesh(frame.Meshes[frame.Meshes.size()-1]))
				return false;
		}
		else
		{
			os::Printer::log("Unknown data object in frame in x file", objectName.c_str());
			if (!parseUnknownDataObject())
				return false;
		}

	}

	return true;
}


bool CXFileReader::parseDataObjectTransformationMatrix(core::matrix4 &mat)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading Transformation Matrix");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Transformation Matrix found in x file", ELL_WARNING);
		return false;
	}

	if (binary)
	{
		// read matrix in binary format
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: Mesh: Expecting float list (for matrix)", ELL_WARNING);
			return false;
		}

		if (readBinDWord() != 0x10)
		{
			os::Printer::log("Binary X: Mesh: Should be 16 floats in matrix", ELL_WARNING);
			return false;
		}
	}

	for (s32 i=0; i<4; ++i)
		for (s32 j=0; j<4; ++j)
			mat(i,j)=readFloat();

	if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Transformation Matrix found in x file", ELL_WARNING);
		return false;
	}

	if (getNextToken() != "}")
	{
		os::Printer::log("No closing brace in Transformation Matrix found in x file", ELL_WARNING);
		return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectTemplate()
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading template");
#endif

	// parse a template data object. Currently not stored.
	core::stringc name = getNextToken();

	// ignore left delimiter
	if (getNextToken() != "{")
	{
		os::Printer::log("Left delimiter in template data object missing.",
			name.c_str(), ELL_ERROR);
		return false;
	}

	// read GUID
	core::stringc guid = getNextToken();

	// read and ignore data members
	while(true)
	{
		core::stringc s = getNextToken();

		if (s == "}")
			break;

		if (s.size() == 0)
			return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectMesh(SXMesh &mesh)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading mesh");
#endif

	if (!readHeadOfDataObject(&mesh.Name))
	{
		os::Printer::log("No opening brace in Mesh found in x file", ELL_WARNING);
		return false;
	}

	// read vertex count
	s32 nVertices = readInt();

	// read vertices
	mesh.Vertices.set_used(nVertices);

	s32 count=0;
	if (binary)
	{
		// read vertices in binary format
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: Mesh: Expecting float list (for vertices)", ELL_WARNING);
			return false;
		}
		count = readBinDWord();
		if (count != (nVertices * 3))
		{
			os::Printer::log("Binary X: Mesh: Value count not matching vertices count", ELL_WARNING);
			return false;
		}
	}

	for (s32 n=0; n<nVertices; ++n)
		readVector3(mesh.Vertices[n]);

	if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Mesh Vertex Array found in x file", ELL_WARNING);
		return false;
	}

	// read faces
	s32 nFaces = readInt();

	mesh.Indices.set_used(nFaces * 3);
	mesh.IndexCountPerFace.set_used(nFaces);

	core::array<s32> polygonfaces;
	s32 currentIndex = 0;

	for (s32 k=0; k<nFaces; ++k)
	{
		s32 fcnt = readInt();

		if (fcnt != 3)
		{
			if (fcnt < 3)
			{
				os::Printer::log("Invalid face count (<3) found in Mesh x file reader.", ELL_WARNING);
				return false;
			}

			// read face indices
			polygonfaces.set_used(fcnt);
			s32 triangles = (fcnt-2);
			mesh.Indices.set_used(mesh.Indices.size() + ((triangles*3)-3));
			mesh.IndexCountPerFace[k] = triangles * 3;

			for (int f=0; f<fcnt; ++f)
				polygonfaces[f] = readInt();

			for (s32 jk=0; jk<triangles; ++jk)
			{
				mesh.Indices[currentIndex++] = polygonfaces[0];
				mesh.Indices[currentIndex++] = polygonfaces[jk+1];
				mesh.Indices[currentIndex++] = polygonfaces[jk+2];
			}

			// TODO: change face indices in material list
		}
		else
		{
			mesh.Indices[currentIndex++] = readInt();
			mesh.Indices[currentIndex++] = readInt();
			mesh.Indices[currentIndex++] = readInt();
			mesh.IndexCountPerFace[k] = 3;
		}
	}

	if (binary && binaryNumCount)
	{
		os::Printer::log("Binary X: Mesh: Integer count mismatch", ELL_WARNING);
		return false;
	}
	else if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Mesh Face Array found in x file", ELL_WARNING);
		return false;
	}

	// here, other data objects may follow

	while(true)
	{
		core::stringc objectName = getNextToken();

		if (objectName.size() == 0)
		{
			os::Printer::log("Unexpected ending found in Mesh in x file.", ELL_WARNING);
			return false;
		}
		else
		if (objectName == "}")
		{
			break; // mesh finished
		}
		else
		if (objectName == "MeshNormals")
		{
			if (!parseDataObjectMeshNormals(mesh.Normals, mesh.NormalIndices,
				mesh.Indices.size(), mesh.IndexCountPerFace))
				return false;
		}
		else
		if (objectName == "MeshTextureCoords")
		{
			if (!parseDataObjectMeshTextureCoords(mesh.TextureCoords))
				return false;
		}
		else
		if (objectName == "MeshVertexColors")
		{
			if (!parseDataObjectMeshVertexColors(mesh.VertexColors))
				return false;
		}
		else
		if (objectName == "MeshMaterialList")
		{
			if (!parseDataObjectMeshMaterialList(mesh.MaterialList,
				mesh.Indices.size(), mesh.IndexCountPerFace))
					return false;
		}
		else
		if (objectName == "VertexDuplicationIndices")
		{
			// we'll ignore vertex duplication indices
			// TODO: read them
			if (!parseUnknownDataObject())
				return false;
		}
		else
		if (objectName == "XSkinMeshHeader")
		{
			if (!parseDataObjectSkinMeshHeader(mesh.SkinMeshHeader))
				return false;
		}
		else
		if (objectName == "SkinWeights")
		{
			mesh.SkinWeights.push_back(SXSkinWeight());
			if (!parseDataObjectSkinWeights(mesh.SkinWeights[mesh.SkinWeights.size()-1]))
				return false;
		}
		else
		{
			os::Printer::log("Unknown data object in mesh in x file", objectName.c_str());
			if (!parseUnknownDataObject())
				return false;
		}
	}

	return true;
}


bool CXFileReader::parseDataObjectSkinWeights(SXSkinWeight& weights)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading mesh skin weights");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Skin Weights found in x file", ELL_WARNING);
		return false;
	}

	if (!getNextTokenAsString(weights.TransformNodeName))
	{
		os::Printer::log("Unknown syntax while reading transfrom node name string in x file", ELL_WARNING);
		return false;
	}

	// read vertex weights

	s32 nWeights = readInt();
	weights.Weights.set_used(nWeights);

	// read vertex indices

	s32 i;

	for (i=0; i<nWeights; ++i)
		weights.Weights[i].VertexIndex = readInt();

	// read vertex weights

	if (binary)
	{
		// read float list in binary format
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: Mesh: Expecting float list (for SkinWeights)", ELL_WARNING);
			return false;
		}

		if (readBinDWord() != (u32)(nWeights+16))
		{
			os::Printer::log("Binary X: Mesh: Wrong number of floats", ELL_WARNING);
			return false;
		}
	}

	for (i=0; i<nWeights; ++i)
		weights.Weights[i].Weight = readFloat();

	// sort weights

	weights.Weights.set_sorted(false);
	weights.Weights.sort();

	// read matrix offset

	for (i=0; i<4; ++i)
		for (u32 j=0; j<4; ++j)
			weights.MatrixOffset(i,j) = readFloat();

	if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Skin Weights found in x file", ELL_WARNING);
		return false;
	}

	if (getNextToken() != "}")
	{
		os::Printer::log("No closing brace in Skin Weights found in x file", ELL_WARNING);
		return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectSkinMeshHeader(SXSkinMeshHeader& header)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading skin mesh header");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Skin Mesh header found in x file", ELL_WARNING);
		return false;
	}

	header.MaxSkinWeightsPerVertex = readInt();
	header.MaxSkinWeightsPerFace = readInt();
	header.BoneCount = readInt();
	if (!binary)
		getNextToken(); // skip semicolon
	core::stringc objectName = getNextToken();

	if (objectName != "}")
	{
		os::Printer::log("No closing brace in skin mesh header in x file", objectName.c_str());
		return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectMeshMaterialList(SXMeshMaterialList& mlist,
												   s32 triangulatedIndexCount,
												   core::array< s32 >& indexCountPerFace)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading mesh material list");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Mesh Material List found in x file", ELL_WARNING);
		return false;
	}

	// read material count
	s32 nMaterials = readInt();

	// read non triangulated face material index count
	s32 nFaceIndices = readInt();

	// read non triangulated face indices

	core::array<s32> nonTriFaceIndices;
	nonTriFaceIndices.set_used(nFaceIndices);

	for (s32 i=0; i<nFaceIndices; ++i)
		nonTriFaceIndices[i] = readInt();

	// create triangulated face indices

	if (nFaceIndices != (s32)indexCountPerFace.size())
	{
		os::Printer::log("Index count per face not equal to face material index count in x file.", ELL_WARNING);
		return false;
	}

	mlist.FaceIndices.set_used(triangulatedIndexCount / 3);
	s32 triangulatedindex = 0;
	for (s32 tfi=0; tfi<nFaceIndices; ++tfi)
		for (s32 k=0; k<indexCountPerFace[tfi]/3; ++k)
			mlist.FaceIndices[triangulatedindex++] = nonTriFaceIndices[tfi];

	// in version 03.02, the face indices end with two semicolons.
	// commented out version check, as version 03.03 exported from blender also has 2 semicolons
	if (!binary) // && MajorVersion == 3 && MinorVersion <= 2)
	{
		if (P[0] == ';')
			++P;
	}

	// read following data objects

	while(true)
	{
		core::stringc objectName = getNextToken();

		if (objectName.size() == 0)
		{
			os::Printer::log("Unexpected ending found in Mesh Material list in x file.", ELL_WARNING);
			return false;
		}
		else
		if (objectName == "}")
		{
			break; // material list finished
		}
		else
		if (objectName == "{")
		{
			// template materials now available thanks to joeWright
			objectName = getNextToken();
			for (u32 i=0; i<TemplateMaterials.size(); ++i)
				if (TemplateMaterials[i].Name == objectName)
					mlist.Materials.push_back(TemplateMaterials[i].Material);
			getNextToken(); // skip }
		}
		else
		if (objectName == "Material")
		{
			mlist.Materials.push_back(SXMaterial());
			if (!parseDataObjectMaterial(mlist.Materials[mlist.Materials.size()-1]))
				return false;
		}
		else
		if (objectName == ";")
		{
			// ignore
		}
		else
		{
			os::Printer::log("Unknown data object in material list in x file", objectName.c_str());
			if (!parseUnknownDataObject())
				return false;
		}

	}

	return true;
}


bool CXFileReader::parseDataObjectMaterial(SXMaterial& material)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading mesh material");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Mesh Material found in x file", ELL_WARNING);
		return false;
	}

	u32 count = 0;
	if (binary)
	{
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: Material: Expecting float list", ELL_WARNING);
			return false;
		}
		count = readBinDWord();
		if (count != 11)
		{
			os::Printer::log("Binary X: Material: Float list length not equal to 11", ELL_WARNING);
			return false;
		}
	}

	// read RGBA
	readRGBA(material.FaceColor);

	// read power
	material.Power = readFloat();

	// read specular
	readRGB(material.Specular);

	// read emissive
	readRGB(material.Emissive);

	// read other data objects
	while(true)
	{
		core::stringc objectName = getNextToken();

		if (objectName.size() == 0)
		{
			os::Printer::log("Unexpected ending found in Mesh Material in x file.", ELL_WARNING);
			return false;
		}
		else
		if (objectName == "}")
		{
			break; // material finished
		}
		else
		if (objectName.equals_ignore_case("TextureFilename"))
		{
			// some exporters write "TextureFileName" instead.

			if (!parseDataObjectTextureFilename(material.TextureFileName))
				return false;
		}
		else
		{
			os::Printer::log("Unknown data object in material in x file", objectName.c_str());
			if (!parseUnknownDataObject())
				return false;
		}

	}

	return true;
}


//! reads a x file style string
bool CXFileReader::getNextTokenAsString(core::stringc& out)
{
	if (binary)
	{
		out=getNextToken();
		return true;
	}
	findNextNoneWhiteSpace();

	if (P >= End)
		return false;

	if (P[0] != '"')
		return false;
	++P;

	while(P < End && P[0]!='"')
	{
		out.append(P[0]);
		++P;
	}

	if ( P[1] != ';' || P[0] != '"')
		return false;
	P+=2;

	return true;
}


bool CXFileReader::parseDataObjectAnimationSet(SXAnimationSet& set)
{
	#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: Reading animation set");
	#endif

	if (!readHeadOfDataObject(&set.AnimationName))
	{
		os::Printer::log("No opening brace in Animation Set found in x file", ELL_WARNING);
		return false;
	}

	while(true)
	{
		core::stringc objectName = getNextToken();

		if (objectName.size() == 0)
		{
			os::Printer::log("Unexpected ending found in Animation set in x file.", ELL_WARNING);
			return false;
		}
		else
		if (objectName == "}")
		{
			break; // animation set finished
		}
		else
		if (objectName == "Animation")
		{
			set.Animations.push_back(SXAnimation());
			if (!parseDataObjectAnimation(set.Animations[set.Animations.size()-1]))
				return false;
		}
		else
		{
			os::Printer::log("Unknown data object in animation set in x file", objectName.c_str());
			if (!parseUnknownDataObject())
				return false;
		}

	}

	return true;
}


bool CXFileReader::parseDataObjectAnimation(SXAnimation& anim)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: reading animation");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Animation found in x file", ELL_WARNING);
		return false;
	}

	anim.closed = true;
	anim.linearPositionQuality = true;

	while(true)
	{
		core::stringc objectName = getNextToken();

		if (objectName.size() == 0)
		{
			os::Printer::log("Unexpected ending found in Animation in x file.", ELL_WARNING);
			return false;
		}
		else
		if (objectName == "}")
		{
			break; // animation finished
		}
		else
		if (objectName == "AnimationKey")
		{
			anim.Keys.push_back(SXAnimationKey());
			if (!parseDataObjectAnimationKey(anim.Keys[anim.Keys.size()-1]))
				return false;
		}
		else
		if (objectName == "AnimationOptions")
		{
			//TODO: parse options.
			if (!parseUnknownDataObject())
				return false;
		}
		else
		if (objectName == "{")
		{
			// read frame name
			anim.FrameName = getNextToken();
			core::stringc end = getNextToken();
			if (end.size() == 0 || end != "}")
			{
				os::Printer::log("Unexpected ending found in Animation in x file.", ELL_WARNING);
				return false;
			}
		}
		else
		{
			if (objectName.size()>2 && objectName[0] == '{' &&
				objectName[objectName.size()-1] == '}')
			{
				anim.FrameName = objectName.subString(1,objectName.size()-2);
			}
			else
			{
				os::Printer::log("Unknown data object in animation in x file", objectName.c_str());
				if (!parseUnknownDataObject())
					return false;
			}
		}

	}

	return true;
}


bool CXFileReader::parseDataObjectAnimationKey(SXAnimationKey& animkey)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: reading animation key");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Animation Key found in x file", ELL_WARNING);
		return false;
	}

	// read key type
	animkey.keyType = readInt();

	if ((animkey.keyType < 0) || (animkey.keyType > 4))
	{
		os::Printer::log("Unknown key type found in Animation Key in x file", ELL_WARNING);
		return false;
	}

	// read number of keys
	animkey.numberOfKeys = readInt();

	animkey.init();

	// read keys
	switch(animkey.keyType)
	{
	case 0:
		{
			//read quaternions
			for (s32 i=0; i<animkey.numberOfKeys; ++i)
			{
				// read time
				animkey.time[i] = readInt();

				// read count
				if (readInt() != 4)
				{
					os::Printer::log("Expected 4 numbers in animation key in x file", ELL_WARNING);
					return false;
				}

				if (binary)
				{
					if (readBinWord() != 7)
					{
						os::Printer::log("Binary X: Animation Key: Expecting float list", ELL_WARNING);
						return false;
					}

					if (readBinDWord() != 4)
					{
						os::Printer::log("Binary X: Animation Key : Value count not correct", ELL_WARNING);
						return false;
					}
				}
				animkey.getQuaternion(i).W = -readFloat();
				animkey.getQuaternion(i).X = -readFloat();
				animkey.getQuaternion(i).Y = -readFloat();
				animkey.getQuaternion(i).Z = -readFloat();

				if (!checkForTwoFollowingSemicolons())
				{
					os::Printer::log("No finishing semicolon after quaternion animation key in x file", ELL_WARNING);
					return false;
				}
			}
		}
		break;
	case 1:
	case 2:
		{
			// read vectors
			for (s32 i=0; i<animkey.numberOfKeys; ++i)
			{
				// read time
				animkey.time[i] = readInt();

				// read count
				if (readInt() != 3)
				{
					os::Printer::log("Expected 3 numbers in animation key in x file", ELL_WARNING);
					return false;
				}

				if (binary)
				{
					if (readBinWord() != 7)
					{
						os::Printer::log("Binary X: Animation Key: Expecting float list", ELL_WARNING);
						return false;
					}

					if (readBinDWord() != 3)
					{
						os::Printer::log("Binary X: Animation Key : Value count not correct", ELL_WARNING);
						return false;
					}
				}
				readVector3(animkey.getVector(i));

				if (!checkForTwoFollowingSemicolons())
				{
					os::Printer::log("No finishing semicolon after vector animation key in x file", ELL_WARNING);
					return false;
				}
			}
		}
		break;
	case 3:
	case 4:
		{
			// read matrix
			for (s32 i=0; i<animkey.numberOfKeys; ++i)
			{
				// read time
				animkey.time[i] = readInt();

				// read count
				if (readInt() != 16)
				{
					os::Printer::log("Expected 16 numbers in animation key in x file", ELL_WARNING);
					return false;
				}

				// read matrix
				if (binary)
				{
					if (readBinWord() != 7)
					{
						os::Printer::log("Binary X: Animation Key: Expecting float list", ELL_WARNING);
						return false;
					}

					if (readBinDWord() != 16)
					{
						os::Printer::log("Binary X: Animation Key : Value count not correct", ELL_WARNING);
						return false;
					}
				}
				for (s32 m=0; m<4; ++m)
					for (s32 n=0; n<4; ++n)
						animkey.getMatrix(i)(m,n) = readFloat();

				if (!checkForTwoFollowingSemicolons())
				{
					os::Printer::log("No finishing semicolon after matrix animation key in x file", ELL_WARNING);
					return false;
				}
			}
		}
		break;
	} // end switch

	if (!binary)
		getNextToken(); // skip another semicolon
	core::stringc objectName = getNextToken();

	if (objectName != "}")
	{
		os::Printer::log("No closing brace in animation key in x file", objectName.c_str());
		return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectTextureFilename(core::stringc& texturename)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: reading texture filename");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Texture filename found in x file", ELL_WARNING);
		return false;
	}

	if (!getNextTokenAsString(texturename))
	{
		os::Printer::log("Unknown syntax while reading texture filename string in x file", ELL_WARNING);
		return false;
	}

	if (getNextToken() != "}")
	{
		os::Printer::log("No closing brace in Texture filename found in x file", ELL_WARNING);
		return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectMeshNormals(core::array<core::vector3df>& normals,
											  core::array< s32 >& normalIndices,
											  s32 triangulatedIndexCount,
											  core::array< s32 >& indexCountPerFace)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: reading mesh normals");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Mesh Normals found in x file", ELL_WARNING);
		return false;
	}

	// read count
	s32 nNormals;
	s32 count;
	nNormals = readInt();
	normals.set_used(nNormals);

	// read normals
	if (binary)
	{
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: MeshNormals: Expecting float list", ELL_WARNING);
			return false;
		}
		count = readBinDWord();
		if (count != nNormals * 3)
		{
			os::Printer::log("Binary X: MeshNormals: Value count not equal to normal count", ELL_WARNING);
			return false;
		}
	}
	for (s32 i=0; i<nNormals; ++i)
		readVector3(normals[i]);

	if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Mesh Normals Array found in x file", ELL_WARNING);
		return false;
	}

	// read face normal indices
	s32 nFNormals = readInt();
	normalIndices.set_used(triangulatedIndexCount);

	s32 normalidx = 0;
	core::array<s32> polygonfaces;
	for (s32 k=0; k<nFNormals; ++k)
	{
		s32 fcnt = readInt();
		s32 triangles = fcnt - 2;
		s32 indexcount = triangles * 3;

		if (indexcount != indexCountPerFace[k])
		{
			os::Printer::log("Not matching normal and face index count found in x file", ELL_WARNING);
			return false;
		}

		if (indexcount == 3)
		{
			// default, only one triangle in this face
			for (s32 h=0; h<3; ++h)
				normalIndices[normalidx++] = readInt();
		}
		else
		{
			// multiple triangles in this face
			polygonfaces.set_used(fcnt);

			for (s32 h=0; h<fcnt; ++h)
				polygonfaces[h] = readInt();

			for (s32 jk=0; jk<triangles; ++jk)
			{
				normalIndices[normalidx++] = polygonfaces[0];
				normalIndices[normalidx++] = polygonfaces[jk+1];
				normalIndices[normalidx++] = polygonfaces[jk+2];
			}
		}
	}

	if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Mesh Face Normals Array found in x file", ELL_WARNING);
		return false;
	}
	if (getNextToken() != "}")
	{
		os::Printer::log("No closing brace in Mesh Normals found in x file", ELL_WARNING);
		return false;
	}

	return true;
}


bool CXFileReader::parseDataObjectMeshTextureCoords(
	core::array<core::vector2df>& textureCoords)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: reading mesh texture coordinates");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace in Mesh Texture Coordinates found in x file", ELL_WARNING);
		return false;
	}

	s32 nCoords;
	u32 count;
	nCoords = readInt();
	if (binary)
	{
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: MeshTextureCoords: Expecting float list", ELL_WARNING);
			return false;
		}
		count = readBinDWord();
	}
	textureCoords.set_used(nCoords);

	for (s32 i=0; i<nCoords; ++i)
		readVector2(textureCoords[i]);

	if (!checkForTwoFollowingSemicolons())
	{
		os::Printer::log("No finishing semicolon in Mesh Texture Coordinates Array found in x file", ELL_WARNING);
		return false;
	}

	if (getNextToken() != "}")
	{
		os::Printer::log("No closing brace in Mesh Texture Coordinates Array found in x file", ELL_WARNING);
		return false;
	}

	return true;
}



bool CXFileReader::parseDataObjectMeshVertexColors(
	core::array<SXIndexedColor>& vertexColors)
{
#ifdef _XREADER_DEBUG
	os::Printer::log("CXFileReader: reading mesh vertex colors");
#endif

	if (!readHeadOfDataObject())
	{
		os::Printer::log("No opening brace for Mesh Vertex Colors found in x file", ELL_WARNING);
		return false;
	}

	s32 nColors;
	u32 count;
	nColors = readInt();
	if (binary)
	{
		if (readBinWord() != 7)
		{
			os::Printer::log("Binary X: MeshVertexColors: Expecting float list", ELL_WARNING);
			return false;
		}
		count = readBinDWord();
	}
	vertexColors.set_used(nColors);

	for (s32 i=0; i<nColors; ++i)
	{
		vertexColors[i].Index=readInt();
		readRGBA(vertexColors[i].Color);
	}

	if (getNextToken() != ";")
	{
		os::Printer::log("No finishing semicolon in Mesh Vertex Colors Array found in x file", ELL_WARNING);
		return false;
	}

	if (getNextToken() != "}")
	{
		os::Printer::log("No closing brace in Mesh Texture Coordinates Array found in x file", ELL_WARNING);
		return false;
	}

	return true;
}



bool CXFileReader::parseUnknownDataObject()
{
	// find opening delimiter
	while(true)
	{
		core::stringc t = getNextToken();

		if (t.size() == 0)
			return false;

		if (t == "{")
			break;
	}

	s32 counter = 1;

	// parse until closing delimiter

	while(counter)
	{
		core::stringc t = getNextToken();

		if (t.size() == 0)
			return false;

		if (t == "{")
			++counter;
		else
		if (t == "}")
			--counter;
	}

	return true;
}



//! checks for two following semicolons, returns false if they are not there
bool CXFileReader::checkForTwoFollowingSemicolons()
{
	if (binary)
		return true;

	for (s32 k=0; k<2; ++k)
	{
		findNextNoneWhiteSpace();
		if (P[0] != ';')
			return false;
		++P;
	}

	return true;
}


//! reads header of dataobject including the opening brace.
//! returns false if error happened, and writes name of object
//! if there is one
bool CXFileReader::readHeadOfDataObject(core::stringc* outname)
{
	core::stringc nameOrBrace = getNextToken();
	if (nameOrBrace != "{")
	{
		if (outname)
			(*outname) = nameOrBrace;

		if (nameOrBrace.size() != 0 &&
			nameOrBrace[nameOrBrace.size()-1] == '{')
		{
			(*outname) = nameOrBrace.subString(0, nameOrBrace.size()-1);
			return true;
		}


		if (getNextToken() != "{")
			return false;
	}

	return true;
}


//! returns next parseable token. Returns empty string if no token there
core::stringc CXFileReader::getNextToken()
{
	core::stringc s;

	// process binary-formatted file
	if (binary)
	{
		// in binary mode it will only return NAME and STRING token
		// and (correctly) skip over other tokens.

		s16 tok = readBinWord();
		s32 len;

		// standalone tokens
		switch (tok) {
			case 1:
				// name token
				len = readBinDWord();
				s = core::stringc(P, len);
				P += len;
				return s;
			case 2:
				// string token
				len = readBinDWord();
				s = core::stringc(P, len);
				P += (len + 2);
				return s;
			case 3:
				// integer token
				P += 4;
				return "<integer>";
			case 5:
				// GUID token
				P += 16;
				return "<guid>";
			case 6:
				len = readBinDWord();
				P += (len * 4);
				return "<int_list>";
			case 7:
				len = readBinDWord();
				P += (len * FloatSize);
				return "<flt_list>";
			case 0x0a:
				return "{";
			case 0x0b:
				return "}";
			case 0x0c:
				return "(";
			case 0x0d:
				return ")";
			case 0x0e:
				return "[";
			case 0x0f:
				return "]";
			case 0x10:
				return "<";
			case 0x11:
				return ">";
			case 0x12:
				return ".";
			case 0x13:
				return ",";
			case 0x14:
				return ";";
			case 0x1f:
				return "template";
			case 0x28:
				return "WORD";
			case 0x29:
				return "DWORD";
			case 0x2a:
				return "FLOAT";
			case 0x2b:
				return "DOUBLE";
			case 0x2c:
				return "CHAR";
			case 0x2d:
				return "UCHAR";
			case 0x2e:
				return "SWORD";
			case 0x2f:
				return "SDWORD";
			case 0x30:
				return "void";
			case 0x31:
				return "string";
			case 0x32:
				return "unicode";
			case 0x34:
				return "array";
		}
	}
	// process text-formatted file
	else
	{
		findNextNoneWhiteSpace();

		if (P >= End)
			return s;

		while(P < End && !isspace(P[0]))
		{
			s.append(P[0]);
			++P;
		}
	}
	return s;
}


//! places pointer to next begin of a token, which must be a number,
// and ignores comments
void CXFileReader::findNextNoneWhiteSpaceNumber()
{
	if (binary)
		return;

	while(true)
	{
		while((P < End) && (P[0] != '-') && (P[0] != '.') &&
				!(isdigit(P[0])))
			++P;

		if (P >= End)
			return;

		// check if this is a comment
		if ((P[0] == '/' && P[1] == '/') || P[0] == '#')
			readUntilEndOfLine();
		else
			break;
	}

}

// places pointer to next begin of a token, and ignores comments
void CXFileReader::findNextNoneWhiteSpace()
{
	if (binary)
		return;

	while(true)
	{
		while(P < End && (P[0]==' ' || P[0]=='\n' || P[0]=='\r' || P[0]=='\t'))
			++P;

		if (P >= End)
			return;

		// check if this is a comment
		if ((P[0] == '/' && P[1] == '/') ||
			P[0] == '#')
			readUntilEndOfLine();
		else
			break;
	}
}


void CXFileReader::readUntilEndOfLine()
{
	if (binary)
		return;

	while(P < End)
	{
		if (P[0] == '\n')
		{
			++P;
			return;
		}

		++P;
	}
}


//! Returns if the loaded mesh is static
bool CXFileReader::isStaticMesh() const
{
	return AnimationSets.empty();
}

//! returns count of animations
s32 CXFileReader::getAnimationSetCount() const
{
	return AnimationSets.size();
}

//! returns a specific animation set
CXFileReader::SXAnimationSet& CXFileReader::getAnimationSet(s32 i)
{
	return AnimationSets[i];
}

//! returns the root frame of the mesh
core::array<CXFileReader::SXFrame> & CXFileReader::getRootFrames()
{
	return RootFrames;
}


void CXFileReader::computeGlobalFrameMatrices(SXFrame& frame, const SXFrame* const parent)
{
	if (!parent)
		frame.GlobalMatrix = frame.LocalMatrix;
	else
		frame.GlobalMatrix = parent->GlobalMatrix * frame.LocalMatrix;

#ifdef _XREADER_DEBUG
	char tmp[255];
	sprintf(tmp, "CXFileReader: Frame %s ", frame.Name.c_str());
	os::Printer::log(tmp);
	for (int i=0; i<4; ++i)
	{
		sprintf(tmp, "    %f, %f, %f, %f",
			frame.LocalMatrix(i,0),
			frame.LocalMatrix(i,1),
			frame.LocalMatrix(i,2),
			frame.LocalMatrix(i,3));
		os::Printer::log(tmp);
	}
#endif
	for (u32 c=0; c<frame.ChildFrames.size(); ++c)
		computeGlobalFrameMatrices(frame.ChildFrames[c], &frame);
}

inline u16 CXFileReader::readBinWord()
{
	u8 *Q = (u8 *)P;
	u16 tmp = 0;
	tmp = Q[0] + (Q[1] << 8);
	P += 2;
	return tmp;
}

inline u32 CXFileReader::readBinDWord()
{
	u8 *Q = (u8 *)P;
	u32 tmp = 0;
	tmp = Q[0] + (Q[1] << 8) + (Q[2] << 16) + (Q[3] << 24);
	P += 4;
	return tmp;
}

inline s32 CXFileReader::readInt()
{
	if (binary)
	{
		if (!binaryNumCount)
		{
			readBinWord(); // 0x06
			binaryNumCount=readBinDWord(); // 0x0001
		}
		--binaryNumCount;
		return readBinDWord();
	}
	else
	{
		f32 ftmp;
		findNextNoneWhiteSpaceNumber();
		P = core::fast_atof_move(P, ftmp);
		return (s32)ftmp;
	}
}

inline f32 CXFileReader::readFloat()
{
	if (binary)
	{
		if (FloatSize == 8)
		{
			char tmp[8];
			memcpy(tmp, P, 8);
			P += 8;
			return (f32)(*(f64 *)tmp);
		}
		else
		{
			char tmp[4];
			memcpy(tmp, P, 4);
			P += 4;
			return *(f32 *)tmp;
		}
	}
	findNextNoneWhiteSpaceNumber();
	f32 ftmp;
	P = core::fast_atof_move(P, ftmp);
	return ftmp;
}

// read 2-dimensional vector. Stops at semicolon after second value for text file format
inline bool CXFileReader::readVector2(core::vector2df& vec)
{
	vec.X = readFloat();
	vec.Y = readFloat();
	return true;
}

// read 3-dimensional vector. Stops at semicolon after third value for text file format
inline bool CXFileReader::readVector3(core::vector3df& vec)
{
	vec.X = readFloat();
	vec.Y = readFloat();
	vec.Z = readFloat();
	return true;
}

// read color without alpha value. Stops after second semicolon after blue value
inline bool CXFileReader::readRGB(video::SColorf& color)
{
	color.r = readFloat();
	color.g = readFloat();
	color.b = readFloat();
	color.a = 1.0f;
	return checkForTwoFollowingSemicolons();
}

// read color with alpha value. Stops after second semicolon after blue value
inline bool CXFileReader::readRGBA(video::SColorf& color)
{
	color.r = readFloat();
	color.g = readFloat();
	color.b = readFloat();
	color.a = readFloat();
	return checkForTwoFollowingSemicolons();
}

} // end namespace scene
} // end namespace irr

