// Copyright (C) 2002-2004 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 <stdlib.h>
#include <stdio.h>

namespace irr
{
namespace scene
{

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

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

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

	#ifdef _XREADER_DEBUG
		validateMesh(&RootFrame);
	#endif

	computeGlobalFrameMatrizes(RootFrame, 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()
{
	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();

		s32 i;

		// validate indices in mesh

		for (i=0; i<icnt; ++i)
		{
			if (frame->Meshes[m].Indices[i] < 0)
			{
				sprintf(tmp, "XLoader error: index %d smaller than 0 in mesh %d: %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 %d: %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 %d: %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 %d: %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
	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 0302
	if (Buffer[0] != 'x' || Buffer[1] != 'o' ||
		Buffer[2] != 'f' || Buffer[3] != ' ')
	{
		os::Printer::log("Not an x file, wrong header.", ELL_WARNING);
		return false;
	}

	//! read minor and major version
	c8 tmp[3];
	tmp[2] = 0x0;
	tmp[0] = Buffer[4];
	tmp[1] = Buffer[5];
	MajorVersion = strtol(tmp, &P, 10);

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

	//! read format
	if (Buffer[8] != 't' || Buffer[9] != 'x' ||
		Buffer[10] != 't' || Buffer[11] != ' ')
	{
		os::Printer::log("Only text encoded x files supported currently.", ELL_WARNING);
		return false;
	}

	//! read float size
	if (Buffer[12] == '0' || Buffer[13] != '0' ||
		Buffer[14] != '3' || Buffer[15] != '2')
		FloatSize = 32;
	else
		FloatSize = 64;
	
	P = &Buffer[16];

	readUntilEndOfLine();

	return true;
}

//! Parses the file
bool CXFileReader::parseFile()
{
	while(parseDataObject())
	{
		// loop
	}

    return true;
}


//! 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")
		return parseDataObjectFrame(RootFrame);
	else
	if (objectName == "Mesh")
	{
		RootFrame.Meshes.push_back(SXMesh());
		return parseDataObjectMesh(RootFrame.Meshes[RootFrame.Meshes.size()-1]);
	}
	else
	if (objectName == "AnimationSet")
	{
		AnimationSets.push_back(SXAnimationSet());
		return parseDataObjectAnimationSet(AnimationSets[AnimationSets.size()-1]);
	}

	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;
	}

	// jetzt sind wir im frame.
	// solange tokens lesen, bis closing brace erreicht

	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;
	}

	for (s32 i=0; i<4; ++i)
		for (s32 j=0; j<4; ++j)
		{
			findNextNoneWhiteSpaceNumber();
			P = core::fast_atof_move(P, mat(j,i));
		}

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

	findNextNoneWhiteSpace();
	if (P[0] != '}')
	{
		os::Printer::log("No closing brace in Transformation Matrix found in x file", ELL_WARNING);
		return false;
	}
	++P;

	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 UUID
	core::stringc uuid = 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

	findNextNoneWhiteSpaceNumber();
	s32 nVertices = readInt();

	// read vertices

	mesh.Vertices.set_used(nVertices);

	for (s32 n=0; n<nVertices; ++n)
	{
		findNextNoneWhiteSpaceNumber();
		mesh.Vertices[n].X = readFloat();
		++P; //findNextNoneWhiteSpaceNumber();		
		mesh.Vertices[n].Y = readFloat();
		++P; //findNextNoneWhiteSpaceNumber();		
		mesh.Vertices[n].Z = readFloat();
	}

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

	// read faces

	findNextNoneWhiteSpaceNumber();
	s32 nFaces = strtol(P, &P, 10);

	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)
	{
		findNextNoneWhiteSpaceNumber();
		s32 fcnt = strtol(P, &P, 10);

		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)
			{
				++P;
				polygonfaces[f] = strtol(P, &P, 10);
			}

			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
		{
			++P;
			mesh.Indices[currentIndex++] = strtol(P, &P, 10);
			++P;
			mesh.Indices[currentIndex++] = strtol(P, &P, 10);
			++P;
			mesh.Indices[currentIndex++] = strtol(P, &P, 10);

			mesh.IndexCountPerFace[k] = 3;
		}
	}


	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 == "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

	findNextNoneWhiteSpaceNumber();
	s32 nWeights = strtol(P, &P, 10);

	weights.Weights.set_used(nWeights);

	// read vertex indices

	s32 i;
	for (i=0; i<nWeights; ++i)
	{
		findNextNoneWhiteSpaceNumber();
		weights.Weights[i].VertexIndex = strtol(P, &P, 10);
	}

	// read vertex weights

	for (i=0; i<nWeights; ++i)
	{
		findNextNoneWhiteSpaceNumber();
		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 (s32 j=0; j<4; ++j)
		{
			findNextNoneWhiteSpaceNumber();
			weights.MatrixOffset(j,i) = readFloat();
		}

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

	findNextNoneWhiteSpace();
	if (P[0] != '}')
	{
		os::Printer::log("No closing brace in Skin Weights found in x file", ELL_WARNING);
		return false;
	}
	++P;

    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;
	}

	findNextNoneWhiteSpaceNumber();
	header.MaxSkinWeightsPerVertex = strtol(P, &P, 10);
	findNextNoneWhiteSpaceNumber();
	header.MaxSkinWeightsPerFace = strtol(P, &P, 10);
	findNextNoneWhiteSpaceNumber();
	header.BoneCount = strtol(P, &P, 10);
	++P;

	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

	findNextNoneWhiteSpaceNumber();
	s32 nMaterials = strtol(P, &P, 10);

	// read non triangulated face material index count

	findNextNoneWhiteSpaceNumber();
	s32 nFaceIndices = strtol(P, &P, 10);

	// read non triangulated face indices

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

	for (s32 i=0; i<nFaceIndices; ++i)
	{
		findNextNoneWhiteSpaceNumber();
		nonTriFaceIndices[i] = strtol(P, &P, 10);
		++P;
	}

	// 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.
	if (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 == "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;
	}

	// read RGBA

	findNextNoneWhiteSpaceNumber();
	material.FaceColor.a = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.FaceColor.r = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.FaceColor.g = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.FaceColor.b = readFloat();

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

	// read specular
	findNextNoneWhiteSpaceNumber();		
	material.Specular.r = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.Specular.g = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.Specular.b = readFloat();

	// read emissive
	findNextNoneWhiteSpaceNumber();		
	material.Emissive.r = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.Emissive.g = readFloat();
	findNextNoneWhiteSpaceNumber();		
	material.Emissive.b = readFloat();

	// read two closing semicolons

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

	// 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)
{
	core::stringc s = getNextToken();
	s32 sl = s.size();
	
	if (sl < 3)
		return false;

	if (s[0] != '"' || s[sl-1] != ';' || s[sl-2] != '"')
		return false;

	out = s.subString(1, sl-3);	    
	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
	findNextNoneWhiteSpaceNumber();
	animkey.keyType = strtol(P, &P, 10);

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

	// read number of keys
	findNextNoneWhiteSpaceNumber();
	animkey.numberOfKeys = strtol(P, &P, 10);

	animkey.init();	

	// read keys
	switch(animkey.keyType)
	{
	case 0:
		{
			//read quaternions
			for (s32 i=0; i<animkey.numberOfKeys; ++i)
			{
				// read time
				findNextNoneWhiteSpaceNumber();
				animkey.time[i] = strtol(P, &P, 10);

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

				++P; 
				animkey.getQuaternion(i).W = -readFloat();
				++P;
				animkey.getQuaternion(i).X = -readFloat();
				++P;
				animkey.getQuaternion(i).Y = -readFloat();
				++P;
				animkey.getQuaternion(i).Z = -readFloat();

				
				if (P[0] != ';' || P[1] != ';')
				{
					os::Printer::log("No following two semicolons missing after quaternion animation key in x file", ELL_WARNING);
					return false;
				}

				P+=3;
			}
		}
		break;
	case 1:
	case 2:
		{
			// read vectors
			for (s32 i=0; i<animkey.numberOfKeys; ++i)
			{
				// read time
				findNextNoneWhiteSpaceNumber();
				animkey.time[i] = strtol(P, &P, 10);

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

				++P; 
				animkey.getVector(i).X = readFloat();
				++P;
				animkey.getVector(i).Y = readFloat();
				++P;
				animkey.getVector(i).Z = readFloat();

				if (P[0] != ';' || P[1] != ';')
				{
					os::Printer::log("No following two semicolons missing after vector animation key in x file", ELL_WARNING);
					return false;
				}

				P+=3;
			}
		}
		break;
	case 3:
	case 4:
		{
			// read matrix

			for (s32 i=0; i<animkey.numberOfKeys; ++i)
			{
				// read time
				findNextNoneWhiteSpaceNumber();
				animkey.time[i] = strtol(P, &P, 10);

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

				// read matrix
				for (s32 n=0; n<4; ++n)
					for (s32 m=0; m<4; ++m)
					{
						findNextNoneWhiteSpaceNumber();
						animkey.getMatrix(i)(m,n) = readFloat();
					}

				if (P[0] != ';' || P[1] != ';')
				{
					os::Printer::log("No following two semicolons missing after matrix animation key in x file", ELL_WARNING);
					return false;
				}

				P+=3;
			}
		}
		break;
	} // end switch


	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;
	}

	findNextNoneWhiteSpace();
	if (P[0] != '}')
	{
		os::Printer::log("No closing brace in Texture filename found in x file", ELL_WARNING);
		return false;
	}
	++P;

	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

	findNextNoneWhiteSpaceNumber();
	s32 nNormals = strtol(P, &P, 10);
	normals.set_used(nNormals);

	// read normals

	for (s32 i=0; i<nNormals; ++i)
	{
		findNextNoneWhiteSpaceNumber();
		normals[i].X = readFloat();
		++P;
		normals[i].Y = readFloat();
		++P;
		normals[i].Z = readFloat();
	}

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

	// read face normal indices

	findNextNoneWhiteSpaceNumber();
	s32 nFNormals = strtol(P, &P, 10);
	normalIndices.set_used(triangulatedIndexCount);
	s32 normalidx = 0;
	core::array<s32> polygonfaces;

	for (s32 k=0; k<nFNormals; ++k)
	{
		findNextNoneWhiteSpaceNumber();
		s32 fcnt = strtol(P, &P, 10);
		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)
			{
				++P;
				normalIndices[normalidx++] = strtol(P, &P, 10);
			}
		}
		else
		{
			// multiple triangles in this face
			polygonfaces.set_used(fcnt);

			for (s32 h=0; h<fcnt; ++h)
			{
				++P;
				polygonfaces[h] = strtol(P, &P, 10);
			}

			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;
	}

	findNextNoneWhiteSpace();
	if (P[0] != '}')
	{
		os::Printer::log("No closing brace in Mesh Normals found in x file", ELL_WARNING);
		return false;
	}
	++P;

	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;
	}

	findNextNoneWhiteSpaceNumber();
	s32 nCoords = strtol(P, &P, 10);
	textureCoords.set_used(nCoords);

	for (s32 i=0; i<nCoords; ++i)
	{
		findNextNoneWhiteSpaceNumber();
		textureCoords[i].X = readFloat();
		++P; 
		textureCoords[i].Y = readFloat();
	}

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

	findNextNoneWhiteSpace();
	if (P[0] != '}')
	{
		os::Printer::log("No closing brace in Mesh Texture Coordinates Array found in x file", ELL_WARNING);
		return false;
	}
	++P;

	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()
{
	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;
		}

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

	return true;
}


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

	if (P >= End)
		return s;

	while(P < End && P[0]!=' ' && P[0]!='\n' && P[0]!='\r' && P[0]!='\t')
	{
		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()
{
	while(1)
	{
		while(P < End && P[0]!='-' &&
				((P[0]==' ' || P[0]=='\n' || P[0]=='\r' || P[0]=='\t') ||
				  (P[0] < '0' || P[0] > '9')))
			++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()
{
	while(1)
	{
		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()
{
	while(P < End)
	{
		if (P[0] == '\n')
		{
			++P;
			return;
		}

		++P;
	}
}


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

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

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

//! returns the root frame of the mesh
CXFileReader::SXFrame& CXFileReader::getRootFrame()
{
	return RootFrame;
}


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

#ifdef _XREADER_DEBUG
	if (frame.Name != "")
	{
		char tmp[255];
		sprintf(tmp, "CXFileReader: Frame %s ", frame.Name);
		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

	/*if (frame.Name == "")
	{
		// frames without name or with mesh data in them (not sure exactly)
		// seem to have to be ignored. TODO: check this out

		for (u32 c=0; c<frame.ChildFrames.size(); ++c)
			computeGlobalFrameMatrizes(frame.ChildFrames[c], parent);
	}
	else*/
	for (u32 c=0; c<frame.ChildFrames.size(); ++c)
		computeGlobalFrameMatrizes(frame.ChildFrames[c], &frame);
}

inline s32 CXFileReader::readInt()
{
	f32 ftmp;
	P = core::fast_atof_move(P, ftmp);
	return (s32)ftmp;
}

inline f32 CXFileReader::readFloat()
{
	f32 ftmp;
	P = core::fast_atof_move(P, ftmp);
	return ftmp;
}

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

