/*
---------------------------------------------------------------------------
Open Asset Import Library (ASSIMP)
---------------------------------------------------------------------------

Copyright (c) 2006-2010, ASSIMP Development Team

All rights reserved.

Redistribution and use of this software in source and binary forms, 
with or without modification, are permitted provided that the following 
conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other
  materials provided with the distribution.

* Neither the name of the ASSIMP team, nor the names of its
  contributors may be used to endorse or promote products
  derived from this software without specific prior
  written permission of the ASSIMP Development Team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/

/** @file Implementation of the XFile parser helper class */

#include "AssimpPCH.h"
#ifndef ASSIMP_BUILD_NO_X_IMPORTER

#include "XFileParser.h"
#include "XFileHelper.h"
#include "fast_atof.h"

using namespace Assimp;
using namespace Assimp::XFile;

#ifndef ASSIMP_BUILD_NO_COMPRESSED_X

#	ifdef ASSIMP_BUILD_NO_OWN_ZLIB
#		include <zlib.h>
#	else
#		include "../contrib/zlib/zlib.h"
#	endif

// Magic identifier for MSZIP compressed data
#define MSZIP_MAGIC 0x4B43
#define MSZIP_BLOCK 32786

// ------------------------------------------------------------------------------------------------
// Dummy memory wrappers for use with zlib
static void* dummy_alloc (void* /*opaque*/, unsigned int items, unsigned int size)	{
	return ::operator new(items*size);
}

static void  dummy_free  (void* /*opaque*/, void* address)	{
	return ::operator delete(address);
}

#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X

// ------------------------------------------------------------------------------------------------
// Constructor. Creates a data structure out of the XFile given in the memory block. 
XFileParser::XFileParser( const std::vector<char>& pBuffer)
{
	mMajorVersion = mMinorVersion = 0;
	mIsBinaryFormat = false;
	mBinaryNumCount = 0;
	P = End = NULL;
	mLineNumber = 0;
	mScene = NULL;

	// vector to store uncompressed file for INFLATE'd X files
	std::vector<char> uncompressed;

	// set up memory pointers
	P = &pBuffer.front();
	End = P + pBuffer.size() - 1;

	// check header
	if( strncmp( P, "xof ", 4) != 0)
		throw DeadlyImportError( "Header mismatch, file is not an XFile.");

	// read version. It comes in a four byte format such as "0302"
	mMajorVersion = (unsigned int)(P[4] - 48) * 10 + (unsigned int)(P[5] - 48);
	mMinorVersion = (unsigned int)(P[6] - 48) * 10 + (unsigned int)(P[7] - 48);

	bool compressed = false;

	// txt - pure ASCII text format
	if( strncmp( P + 8, "txt ", 4) == 0)
		mIsBinaryFormat = false;

	// bin - Binary format
	else if( strncmp( P + 8, "bin ", 4) == 0)
		mIsBinaryFormat = true;

	// tzip - Inflate compressed text format
	else if( strncmp( P + 8, "tzip", 4) == 0)
	{
		mIsBinaryFormat = false;
		compressed = true;
	}
	// bzip - Inflate compressed binary format
	else if( strncmp( P + 8, "bzip", 4) == 0)
	{
		mIsBinaryFormat = true;
		compressed = true;
	}
	else ThrowException( boost::str(boost::format("Unsupported xfile format '%c%c%c%c'") 
		% P[8] % P[9] % P[10] % P[11]));

	// float size
	mBinaryFloatSize = (unsigned int)(P[12] - 48) * 1000
		+ (unsigned int)(P[13] - 48) * 100
		+ (unsigned int)(P[14] - 48) * 10
		+ (unsigned int)(P[15] - 48);

	if( mBinaryFloatSize != 32 && mBinaryFloatSize != 64)
		ThrowException( boost::str( boost::format( "Unknown float size %1% specified in xfile header.")
			% mBinaryFloatSize));

	P += 16;

	// If this is a compressed X file, apply the inflate algorithm to it
	if (compressed)
	{
#ifdef ASSIMP_BUILD_NO_COMPRESSED_X
		throw DeadlyImportError("Assimp was built without compressed X support");
#else
		/* ///////////////////////////////////////////////////////////////////////  
		 * COMPRESSED X FILE FORMAT
		 * ///////////////////////////////////////////////////////////////////////
		 *    [xhead]
		 *    2 major
		 *    2 minor
		 *    4 type    // bzip,tzip
		 *    [mszip_master_head]
		 *    4 unkn    // checksum?
		 *    2 unkn    // flags? (seems to be constant)
		 *    [mszip_head]
		 *    2 ofs     // offset to next section
		 *    2 magic   // 'CK'
		 *    ... ofs bytes of data
		 *    ... next mszip_head
		 *
		 *  http://www.kdedevelopers.org/node/3181 has been very helpful.
		 * ///////////////////////////////////////////////////////////////////////
		 */

		// build a zlib stream
		z_stream stream;
		stream.opaque = NULL;
		stream.zalloc = &dummy_alloc;
		stream.zfree  = &dummy_free;
		stream.data_type = (mIsBinaryFormat ? Z_BINARY : Z_ASCII);

		// initialize the inflation algorithm
		::inflateInit2(&stream, -MAX_WBITS);

		// skip unknown data (checksum, flags?)
		P += 6;

		// First find out how much storage we'll need. Count sections.
		const char* P1       = P;
		unsigned int est_out = 0;

		while (P1 + 3 < End)
		{
			// read next offset
			uint16_t ofs = *((uint16_t*)P1);
			AI_SWAP2(ofs); P1 += 2;

			if (ofs >= MSZIP_BLOCK)
				throw DeadlyImportError("X: Invalid offset to next MSZIP compressed block");

			// check magic word
			uint16_t magic = *((uint16_t*)P1);
			AI_SWAP2(magic); P1 += 2;

			if (magic != MSZIP_MAGIC)
				throw DeadlyImportError("X: Unsupported compressed format, expected MSZIP header");

			// and advance to the next offset
			P1 += ofs;
			est_out += MSZIP_BLOCK; // one decompressed block is 32786 in size
		}
		
		// Allocate storage and terminating zero and do the actual uncompressing
		uncompressed.resize(est_out + 1);
		char* out = &uncompressed.front();
		while (P + 3 < End)
		{
			uint16_t ofs = *((uint16_t*)P);
			AI_SWAP2(ofs); 
			P += 4;

			// push data to the stream
			stream.next_in   = (Bytef*)P;
			stream.avail_in  = ofs;
			stream.next_out  = (Bytef*)out;
			stream.avail_out = MSZIP_BLOCK;

			// and decompress the data ....
			int ret = ::inflate( &stream, Z_SYNC_FLUSH );
			if (ret != Z_OK && ret != Z_STREAM_END)
				throw DeadlyImportError("X: Failed to decompress MSZIP-compressed data");

			::inflateReset( &stream );
			::inflateSetDictionary( &stream, (const Bytef*)out , MSZIP_BLOCK - stream.avail_out );

			// and advance to the next offset
			out +=  MSZIP_BLOCK - stream.avail_out;
			P   += ofs;
		}

		// terminate zlib
		::inflateEnd(&stream);
		
		// ok, update pointers to point to the uncompressed file data
		P = &uncompressed[0];
		End = out;

		// FIXME: we don't need the compressed data anymore, could release
		// it already for better memory usage. Consider breaking const-co.
		DefaultLogger::get()->info("Successfully decompressed MSZIP-compressed file");
#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X
	}
	else
	{
		// start reading here
		ReadUntilEndOfLine();
	}

	mScene = new Scene;
	ParseFile();

	// filter the imported hierarchy for some degenerated cases
	if( mScene->mRootNode) {
		FilterHierarchy( mScene->mRootNode);
	}
}

// ------------------------------------------------------------------------------------------------
// Destructor. Destroys all imported data along with it 
XFileParser::~XFileParser()
{
	// kill everything we created
	delete mScene;
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseFile()
{
	bool running = true;
	while( running )
	{
		// read name of next object
		std::string objectName = GetNextToken();
		if (objectName.length() == 0)
			break;

		// parse specific object
		if( objectName == "template")
			ParseDataObjectTemplate();
		else
		if( objectName == "Frame")
			ParseDataObjectFrame( NULL);
		else
		if( objectName == "Mesh")
		{
			// some meshes have no frames at all
			Mesh* mesh = new Mesh;
			ParseDataObjectMesh( mesh);
			mScene->mGlobalMeshes.push_back( mesh);
		} else
		if( objectName == "AnimTicksPerSecond")
			ParseDataObjectAnimTicksPerSecond();
		else
		if( objectName == "AnimationSet")
			ParseDataObjectAnimationSet();
		else
		if( objectName == "Material")
		{
			// Material outside of a mesh or node
			Material material; 
			ParseDataObjectMaterial( &material);
			mScene->mGlobalMaterials.push_back( material);
		} else
		if( objectName == "}")
		{
			// whatever?
			DefaultLogger::get()->warn("} found in dataObject");
		} else
		{
			// unknown format
			DefaultLogger::get()->warn("Unknown data object in animation of .x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectTemplate()
{
	// parse a template data object. Currently not stored.
	std::string name;
	readHeadOfDataObject( &name);

	// read GUID
	std::string guid = GetNextToken();

	// read and ignore data members
	bool running = true;
	while ( running )
	{
		std::string s = GetNextToken();

		if( s == "}")
			break;

		if( s.length() == 0)
			ThrowException( "Unexpected end of file reached while parsing template definition");
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectFrame( Node* pParent)
{
	// 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.
	std::string name;
	readHeadOfDataObject(&name);

	// create a named node and place it at its parent, if given
	Node* node = new Node( pParent);
	node->mName = name;
	if( pParent)
	{
		pParent->mChildren.push_back( node);
	} else
	{
		// there might be multiple root nodes
		if( mScene->mRootNode != NULL)
		{
			// place a dummy root if not there
			if( mScene->mRootNode->mName != "$dummy_root")
			{
				Node* exroot = mScene->mRootNode;
				mScene->mRootNode = new Node( NULL);
				mScene->mRootNode->mName = "$dummy_root";
				mScene->mRootNode->mChildren.push_back( exroot);
				exroot->mParent = mScene->mRootNode;
			}
			// put the new node as its child instead
			mScene->mRootNode->mChildren.push_back( node);
			node->mParent = mScene->mRootNode;
		} else
		{
			// it's the first node imported. place it as root
			mScene->mRootNode = node;
		}
	}

	// Now inside a frame.
	// read tokens until closing brace is reached.
	bool running = true;
	while ( running )
	{
		std::string objectName = GetNextToken();
		if (objectName.size() == 0)
			ThrowException( "Unexpected end of file reached while parsing frame");

		if( objectName == "}")
			break; // frame finished
		else
		if( objectName == "Frame")
			ParseDataObjectFrame( node); // child frame
		else
		if( objectName == "FrameTransformMatrix")
			ParseDataObjectTransformationMatrix( node->mTrafoMatrix);
		else
		if( objectName == "Mesh")
		{
			Mesh* mesh = new Mesh;
			node->mMeshes.push_back( mesh);
			ParseDataObjectMesh( mesh);
		} else
		{
			DefaultLogger::get()->warn("Unknown data object in frame in x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectTransformationMatrix( aiMatrix4x4& pMatrix)
{
	// read header, we're not interested if it has a name
	readHeadOfDataObject();

	// read its components
	pMatrix.a1 = ReadFloat(); pMatrix.b1 = ReadFloat();
	pMatrix.c1 = ReadFloat(); pMatrix.d1 = ReadFloat();
	pMatrix.a2 = ReadFloat(); pMatrix.b2 = ReadFloat();
	pMatrix.c2 = ReadFloat(); pMatrix.d2 = ReadFloat();
	pMatrix.a3 = ReadFloat(); pMatrix.b3 = ReadFloat();
	pMatrix.c3 = ReadFloat(); pMatrix.d3 = ReadFloat();
	pMatrix.a4 = ReadFloat(); pMatrix.b4 = ReadFloat();
	pMatrix.c4 = ReadFloat(); pMatrix.d4 = ReadFloat();

	// trailing symbols
	CheckForSemicolon();
	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMesh( Mesh* pMesh)
{
	std::string name;
	readHeadOfDataObject( &name);

	// read vertex count
	unsigned int numVertices = ReadInt();
	pMesh->mPositions.resize( numVertices);

	// read vertices
	for( unsigned int a = 0; a < numVertices; a++)
		pMesh->mPositions[a] = ReadVector3();

	// read position faces
	unsigned int numPosFaces = ReadInt();
	pMesh->mPosFaces.resize( numPosFaces);
	for( unsigned int a = 0; a < numPosFaces; a++)
	{
		unsigned int numIndices = ReadInt();
		if( numIndices < 3)
			ThrowException( boost::str( boost::format( "Invalid index count %1% for face %2%.") % numIndices % a));

		// read indices
		Face& face = pMesh->mPosFaces[a];
		for( unsigned int b = 0; b < numIndices; b++)
			face.mIndices.push_back( ReadInt());
		CheckForSeparator();
	}

	// here, other data objects may follow
	bool running = true;
	while ( running )
	{
		std::string objectName = GetNextToken();

		if( objectName.size() == 0)
			ThrowException( "Unexpected end of file while parsing mesh structure");
		else
		if( objectName == "}")
			break; // mesh finished
		else
		if( objectName == "MeshNormals")
			ParseDataObjectMeshNormals( pMesh);
		else
		if( objectName == "MeshTextureCoords")
			ParseDataObjectMeshTextureCoords( pMesh);
		else
		if( objectName == "MeshVertexColors")
			ParseDataObjectMeshVertexColors( pMesh);
		else
		if( objectName == "MeshMaterialList")
			ParseDataObjectMeshMaterialList( pMesh);
		else
		if( objectName == "VertexDuplicationIndices")
			ParseUnknownDataObject(); // we'll ignore vertex duplication indices
		else
		if( objectName == "XSkinMeshHeader")
			ParseDataObjectSkinMeshHeader( pMesh);
		else
		if( objectName == "SkinWeights")
			ParseDataObjectSkinWeights( pMesh);
		else
		{
			DefaultLogger::get()->warn("Unknown data object in mesh in x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectSkinWeights( Mesh *pMesh)
{
	readHeadOfDataObject();

	std::string transformNodeName;
	GetNextTokenAsString( transformNodeName);

	pMesh->mBones.push_back( Bone());
	Bone& bone = pMesh->mBones.back();
	bone.mName = transformNodeName;

	// read vertex weights
	unsigned int numWeights = ReadInt();
	bone.mWeights.reserve( numWeights);

	for( unsigned int a = 0; a < numWeights; a++)
	{
		BoneWeight weight;
		weight.mVertex = ReadInt();
		bone.mWeights.push_back( weight);
	}

	// read vertex weights
	for( unsigned int a = 0; a < numWeights; a++)
		bone.mWeights[a].mWeight = ReadFloat();

	// read matrix offset
	bone.mOffsetMatrix.a1 = ReadFloat(); bone.mOffsetMatrix.b1 = ReadFloat();
	bone.mOffsetMatrix.c1 = ReadFloat(); bone.mOffsetMatrix.d1 = ReadFloat();
	bone.mOffsetMatrix.a2 = ReadFloat(); bone.mOffsetMatrix.b2 = ReadFloat();
	bone.mOffsetMatrix.c2 = ReadFloat(); bone.mOffsetMatrix.d2 = ReadFloat();
	bone.mOffsetMatrix.a3 = ReadFloat(); bone.mOffsetMatrix.b3 = ReadFloat();
	bone.mOffsetMatrix.c3 = ReadFloat(); bone.mOffsetMatrix.d3 = ReadFloat();
	bone.mOffsetMatrix.a4 = ReadFloat(); bone.mOffsetMatrix.b4 = ReadFloat();
	bone.mOffsetMatrix.c4 = ReadFloat(); bone.mOffsetMatrix.d4 = ReadFloat();

	CheckForSemicolon();
	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectSkinMeshHeader( Mesh* /*pMesh*/ )
{
	readHeadOfDataObject();

	/*unsigned int maxSkinWeightsPerVertex =*/ ReadInt();
	/*unsigned int maxSkinWeightsPerFace =*/ ReadInt();
	/*unsigned int numBonesInMesh = */ReadInt();

	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshNormals( Mesh* pMesh)
{
	readHeadOfDataObject();

	// read count
	unsigned int numNormals = ReadInt();
	pMesh->mNormals.resize( numNormals);

	// read normal vectors
	for( unsigned int a = 0; a < numNormals; a++)
		pMesh->mNormals[a] = ReadVector3();

	// read normal indices
	unsigned int numFaces = ReadInt();
	if( numFaces != pMesh->mPosFaces.size())
		ThrowException( "Normal face count does not match vertex face count.");

	for( unsigned int a = 0; a < numFaces; a++)
	{
		unsigned int numIndices = ReadInt();
		pMesh->mNormFaces.push_back( Face());
		Face& face = pMesh->mNormFaces.back();

		for( unsigned int b = 0; b < numIndices; b++)
			face.mIndices.push_back( ReadInt());

		CheckForSeparator();
	}

	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshTextureCoords( Mesh* pMesh)
{
	readHeadOfDataObject();
	if( pMesh->mNumTextures + 1 > AI_MAX_NUMBER_OF_TEXTURECOORDS)
		ThrowException( "Too many sets of texture coordinates");

	std::vector<aiVector2D>& coords = pMesh->mTexCoords[pMesh->mNumTextures++];

	unsigned int numCoords = ReadInt();
	if( numCoords != pMesh->mPositions.size())
		ThrowException( "Texture coord count does not match vertex count");

	coords.resize( numCoords);
	for( unsigned int a = 0; a < numCoords; a++)
		coords[a] = ReadVector2();

	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshVertexColors( Mesh* pMesh)
{
	readHeadOfDataObject();
	if( pMesh->mNumColorSets + 1 > AI_MAX_NUMBER_OF_COLOR_SETS)
		ThrowException( "Too many colorsets");
	std::vector<aiColor4D>& colors = pMesh->mColors[pMesh->mNumColorSets++];

	unsigned int numColors = ReadInt();
	if( numColors != pMesh->mPositions.size())
		ThrowException( "Vertex color count does not match vertex count");

	colors.resize( numColors, aiColor4D( 0, 0, 0, 1));
	for( unsigned int a = 0; a < numColors; a++)
	{
		unsigned int index = ReadInt();
		if( index >= pMesh->mPositions.size())
			ThrowException( "Vertex color index out of bounds");

		colors[index] = ReadRGBA();
		// HACK: (thom) Maxon Cinema XPort plugin puts a third separator here, kwxPort puts a comma.
		// Ignore gracefully.
		if( !mIsBinaryFormat)
		{
			FindNextNoneWhiteSpace();
			if( *P == ';' || *P == ',')
				P++;
		}
	}

	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMeshMaterialList( Mesh* pMesh)
{
	readHeadOfDataObject();

	// read material count
	/*unsigned int numMaterials =*/ ReadInt();
	// read non triangulated face material index count
	unsigned int numMatIndices = ReadInt();

	// some models have a material index count of 1... to be able to read them we
	// replicate this single material index on every face
	if( numMatIndices != pMesh->mPosFaces.size() && numMatIndices != 1)
		ThrowException( "Per-Face material index count does not match face count.");

	// read per-face material indices
	for( unsigned int a = 0; a < numMatIndices; a++)
		pMesh->mFaceMaterials.push_back( ReadInt());

	// 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( !mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2)
	{
		if(P < End && *P == ';')
			++P;
	}

	// if there was only a single material index, replicate it on all faces
	while( pMesh->mFaceMaterials.size() < pMesh->mPosFaces.size())
		pMesh->mFaceMaterials.push_back( pMesh->mFaceMaterials.front());

	// read following data objects
	bool running = true;
	while ( running )
	{
		std::string objectName = GetNextToken();
		if( objectName.size() == 0)
			ThrowException( "Unexpected end of file while parsing mesh material list.");
		else
		if( objectName == "}")
			break; // material list finished
		else
		if( objectName == "{")
		{
			// template materials 
			std::string matName = GetNextToken();
			Material material;
			material.mIsReference = true;
			material.mName = matName;
			pMesh->mMaterials.push_back( material);

			CheckForClosingBrace(); // skip }
		} else
		if( objectName == "Material")
		{
			pMesh->mMaterials.push_back( Material());
			ParseDataObjectMaterial( &pMesh->mMaterials.back());
		} else
		if( objectName == ";")
		{
			// ignore
		} else
		{
			DefaultLogger::get()->warn("Unknown data object in material list in x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectMaterial( Material* pMaterial)
{
	std::string matName;
	readHeadOfDataObject( &matName);
	if( matName.empty())
		matName = std::string( "material") + boost::lexical_cast<std::string>( mLineNumber);
	pMaterial->mName = matName;
	pMaterial->mIsReference = false;

	// read material values
	pMaterial->mDiffuse = ReadRGBA(); 
	pMaterial->mSpecularExponent = ReadFloat();
	pMaterial->mSpecular = ReadRGB(); 
	pMaterial->mEmissive = ReadRGB(); 

	// read other data objects
	bool running = true;
	while ( running )
	{
		std::string objectName = GetNextToken();
		if( objectName.size() == 0)
			ThrowException( "Unexpected end of file while parsing mesh material");
		else
		if( objectName == "}")
			break; // material finished
		else
		if( objectName == "TextureFilename" || objectName == "TextureFileName")
		{
			// some exporters write "TextureFileName" instead.
			std::string texname;
			ParseDataObjectTextureFilename( texname);
			pMaterial->mTextures.push_back( TexEntry( texname));
		} else
		if( objectName == "NormalmapFilename" || objectName == "NormalmapFileName")
		{
			// one exporter writes out the normal map in a separate filename tag
			std::string texname;
			ParseDataObjectTextureFilename( texname);
			pMaterial->mTextures.push_back( TexEntry( texname, true));
		} else
		{
			DefaultLogger::get()->warn("Unknown data object in material in x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectAnimTicksPerSecond()
{
	readHeadOfDataObject();
	mScene->mAnimTicksPerSecond = ReadInt();
	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectAnimationSet()
{
	std::string animName;
	readHeadOfDataObject( &animName);

	Animation* anim = new Animation;
	mScene->mAnims.push_back( anim);
	anim->mName = animName;

	bool running = true;
	while ( running )
	{
		std::string objectName = GetNextToken();
		if( objectName.length() == 0)
			ThrowException( "Unexpected end of file while parsing animation set.");
		else
		if( objectName == "}")
			break; // animation set finished
		else
		if( objectName == "Animation")
			ParseDataObjectAnimation( anim);
		else
		{
			DefaultLogger::get()->warn("Unknown data object in animation set in x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectAnimation( Animation* pAnim)
{
	readHeadOfDataObject();
	AnimBone* banim = new AnimBone;
	pAnim->mAnims.push_back( banim);

	bool running = true;
	while( running )
	{
		std::string objectName = GetNextToken();

		if( objectName.length() == 0)
			ThrowException( "Unexpected end of file while parsing animation.");
		else
		if( objectName == "}")
			break; // animation finished
		else
		if( objectName == "AnimationKey")
			ParseDataObjectAnimationKey( banim);
		else
		if( objectName == "AnimationOptions")
			ParseUnknownDataObject(); // not interested
		else
		if( objectName == "{")
		{
			// read frame name
			banim->mBoneName = GetNextToken();
			CheckForClosingBrace();
		} else
		{
			DefaultLogger::get()->warn("Unknown data object in animation in x file");
			ParseUnknownDataObject();
		}
	}
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectAnimationKey( AnimBone* pAnimBone)
{
	readHeadOfDataObject();

	// read key type
	unsigned int keyType = ReadInt();

	// read number of keys
	unsigned int numKeys = ReadInt();

	for( unsigned int a = 0; a < numKeys; a++)
	{
		// read time
		unsigned int time = ReadInt();

		// read keys
		switch( keyType)
		{
			case 0: // rotation quaternion
			{
				// read count
				if( ReadInt() != 4)
					ThrowException( "Invalid number of arguments for quaternion key in animation");

				aiQuatKey key;
				key.mTime = double( time);
				key.mValue.w = ReadFloat();
				key.mValue.x = ReadFloat();
				key.mValue.y = ReadFloat();
				key.mValue.z = ReadFloat();
				pAnimBone->mRotKeys.push_back( key);

				CheckForSemicolon();
				break;
			}

			case 1: // scale vector
			case 2: // position vector
			{
				// read count
				if( ReadInt() != 3)
					ThrowException( "Invalid number of arguments for vector key in animation");

				aiVectorKey key;
				key.mTime = double( time);
				key.mValue = ReadVector3();

				if( keyType == 2)
					pAnimBone->mPosKeys.push_back( key);
				else
					pAnimBone->mScaleKeys.push_back( key);

				break;
			}

			case 3: // combined transformation matrix
			case 4: // denoted both as 3 or as 4
			{
				// read count
				if( ReadInt() != 16)
					ThrowException( "Invalid number of arguments for matrix key in animation");

				// read matrix
				MatrixKey key;
				key.mTime = double( time);
				key.mMatrix.a1 = ReadFloat(); key.mMatrix.b1 = ReadFloat();
				key.mMatrix.c1 = ReadFloat(); key.mMatrix.d1 = ReadFloat();
				key.mMatrix.a2 = ReadFloat(); key.mMatrix.b2 = ReadFloat();
				key.mMatrix.c2 = ReadFloat(); key.mMatrix.d2 = ReadFloat();
				key.mMatrix.a3 = ReadFloat(); key.mMatrix.b3 = ReadFloat();
				key.mMatrix.c3 = ReadFloat(); key.mMatrix.d3 = ReadFloat();
				key.mMatrix.a4 = ReadFloat(); key.mMatrix.b4 = ReadFloat();
				key.mMatrix.c4 = ReadFloat(); key.mMatrix.d4 = ReadFloat();
				pAnimBone->mTrafoKeys.push_back( key);

				CheckForSemicolon();
				break;
			}

			default:
				ThrowException( boost::str( boost::format( "Unknown key type %1% in animation.") % keyType));
				break;
		} // end switch

		// key separator
		CheckForSeparator();
	}

	CheckForClosingBrace();
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseDataObjectTextureFilename( std::string& pName)
{
	readHeadOfDataObject();
	GetNextTokenAsString( pName);
	CheckForClosingBrace();

	// FIX: some files (e.g. AnimationTest.x) have "" as texture file name
	if (!pName.length())
	{
		DefaultLogger::get()->warn("Length of texture file name is zero. Skipping this texture.");
	}

	// some exporters write double backslash paths out. We simply replace them if we find them
	while( pName.find( "\\\\") != std::string::npos)
		pName.replace( pName.find( "\\\\"), 2, "\\");
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ParseUnknownDataObject()
{
	// find opening delimiter
	bool running = true;
	while( running )
	{
		std::string t = GetNextToken();
		if( t.length() == 0)
			ThrowException( "Unexpected end of file while parsing unknown segment.");

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

	unsigned int counter = 1;

	// parse until closing delimiter
	while( counter > 0)
	{
		std::string t = GetNextToken();

		if( t.length() == 0)
			ThrowException( "Unexpected end of file while parsing unknown segment.");

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

// ------------------------------------------------------------------------------------------------
//! checks for closing curly brace
void XFileParser::CheckForClosingBrace()
{
	if( GetNextToken() != "}")
		ThrowException( "Closing brace expected.");
}

// ------------------------------------------------------------------------------------------------
//! checks for one following semicolon
void XFileParser::CheckForSemicolon()
{
	if( mIsBinaryFormat)
		return;

	if( GetNextToken() != ";")
		ThrowException( "Semicolon expected.");
}

// ------------------------------------------------------------------------------------------------
//! checks for a separator char, either a ',' or a ';'
void XFileParser::CheckForSeparator()
{
	if( mIsBinaryFormat)
		return;

	std::string token = GetNextToken();
	if( token != "," && token != ";")
		ThrowException( "Separator character (';' or ',') expected.");
}

// ------------------------------------------------------------------------------------------------
// tests and possibly consumes a separator char, but does nothing if there was no separator
void XFileParser::TestForSeparator()
{
  if( mIsBinaryFormat)
    return;

  FindNextNoneWhiteSpace();
  if( P >= End)
    return;

  // test and skip
  if( *P == ';' || *P == ',')
    P++;
}

// ------------------------------------------------------------------------------------------------
void XFileParser::readHeadOfDataObject( std::string* poName)
{
	std::string nameOrBrace = GetNextToken();
	if( nameOrBrace != "{")
	{
		if( poName)
			*poName = nameOrBrace;

		if( GetNextToken() != "{")
			ThrowException( "Opening brace expected.");
	}
}

// ------------------------------------------------------------------------------------------------
std::string XFileParser::GetNextToken()
{
	std::string s;

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

		if( End - P < 2) return s;
		unsigned int tok = ReadBinWord();
		unsigned int len;

		// standalone tokens
		switch( tok) 
		{
			case 1:
				// name token
				if( End - P < 4) return s;
				len = ReadBinDWord();
				if( End - P < int(len)) return s;
				s = std::string(P, len);
				P += len;
				return s;
			case 2:
				// string token
				if( End - P < 4) return s;
				len = ReadBinDWord();
				if( End - P < int(len)) return s;
				s = std::string(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:
				if( End - P < 4) return s;
				len = ReadBinDWord();
				P += (len * 4);
				return "<int_list>";
			case 7:
				if( End - P < 4) return s;
				len = ReadBinDWord();
				P += (len * mBinaryFloatSize);
				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 0x33:
				return "cstring";
			case 0x34:
				return "array";
		}
	}
	// process text-formatted file
	else
	{
		FindNextNoneWhiteSpace();
		if( P >= End)
			return s;

		while( (P < End) && !isspace( (unsigned char) *P))
		{
			// either keep token delimiters when already holding a token, or return if first valid char
			if( *P == ';' || *P == '}' || *P == '{' || *P == ',')
			{
				if( !s.size())
					s.append( P++, 1);
				break; // stop for delimiter
			}
			s.append( P++, 1);
		}
	}
	return s;
}

// ------------------------------------------------------------------------------------------------
void XFileParser::FindNextNoneWhiteSpace()
{
	if( mIsBinaryFormat)
		return;

	bool running = true;
	while( running )
	{
		while( P < End && isspace( (unsigned char) *P))
		{
			if( *P == '\n')
				mLineNumber++;
			++P;
		}

		if( P >= End)
			return;

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

// ------------------------------------------------------------------------------------------------
void XFileParser::GetNextTokenAsString( std::string& poString)
{
	if( mIsBinaryFormat)
	{
		poString = GetNextToken();
		return;
	}

	FindNextNoneWhiteSpace();
	if( P >= End)
		ThrowException( "Unexpected end of file while parsing string");

	if( *P != '"')
		ThrowException( "Expected quotation mark.");
	++P;

	while( P < End && *P != '"')
		poString.append( P++, 1);

	if( P >= End-1)
		ThrowException( "Unexpected end of file while parsing string");

	if( P[1] != ';' || P[0] != '"')
		ThrowException( "Expected quotation mark and semicolon at the end of a string.");
	P+=2;
}

// ------------------------------------------------------------------------------------------------
void XFileParser::ReadUntilEndOfLine()
{
	if( mIsBinaryFormat)
		return;

	while( P < End)
	{
		if( *P == '\n' || *P == '\r')
		{
			++P; mLineNumber++;
			return;
		}

		++P;
	}
}

// ------------------------------------------------------------------------------------------------
unsigned short XFileParser::ReadBinWord()
{
	assert(End - P >= 2);
	const unsigned char* q = (const unsigned char*) P;
	unsigned short tmp = q[0] | (q[1] << 8);
	P += 2;
	return tmp;
}

// ------------------------------------------------------------------------------------------------
unsigned int XFileParser::ReadBinDWord()
{
	assert(End - P >= 4);
	const unsigned char* q = (const unsigned char*) P;
	unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24);
	P += 4;
	return tmp;
}

// ------------------------------------------------------------------------------------------------
unsigned int XFileParser::ReadInt()
{
	if( mIsBinaryFormat)
	{
		if( mBinaryNumCount == 0 && End - P >= 2)
		{
			unsigned short tmp = ReadBinWord(); // 0x06 or 0x03
			if( tmp == 0x06 && End - P >= 4) // array of ints follows
				mBinaryNumCount = ReadBinDWord();
			else // single int follows
				mBinaryNumCount = 1; 
		}

		--mBinaryNumCount;
		if ( End - P >= 4) {
			return ReadBinDWord();
		} else {
			P = End;
			return 0;
		}
	} else
	{
		FindNextNoneWhiteSpace();

		// TODO: consider using strtol10 instead???

		// check preceeding minus sign
		bool isNegative = false;
		if( *P == '-')
		{
			isNegative = true;
			P++;
		}

		// at least one digit expected
		if( !isdigit( *P))
			ThrowException( "Number expected.");

		// read digits
		unsigned int number = 0;
		while( P < End)
		{
			if( !isdigit( *P))
				break;
			number = number * 10 + (*P - 48);
			P++;
		}
		
		CheckForSeparator();
		return isNegative ? ((unsigned int) -int( number)) : number;
	}
}

// ------------------------------------------------------------------------------------------------
float XFileParser::ReadFloat()
{
	if( mIsBinaryFormat)
	{
		if( mBinaryNumCount == 0 && End - P >= 2)
		{
			unsigned short tmp = ReadBinWord(); // 0x07 or 0x42
			if( tmp == 0x07 && End - P >= 4) // array of floats following
				mBinaryNumCount = ReadBinDWord();
			else // single float following
				mBinaryNumCount = 1; 
		}

		--mBinaryNumCount;
		if( mBinaryFloatSize == 8)
		{
			if( End - P >= 8) {
				float result = (float) (*(double*) P);
				P += 8;
				return result;
			} else {
				P = End;
				return 0;
			}
		} else
		{
			if( End - P >= 4) {
				float result = *(float*) P;
				P += 4;
				return result;
			} else {
				P = End;
				return 0;
			}
		}
	}

	// text version
	FindNextNoneWhiteSpace();
	// check for various special strings to allow reading files from faulty exporters
	// I mean you, Blender!
	// Reading is safe because of the terminating zero
	if( strncmp( P, "-1.#IND00", 9) == 0 || strncmp( P, "1.#IND00", 8) == 0)
	{ 
		P += 9;
		CheckForSeparator();
		return 0.0f;
	} else
	if( strncmp( P, "1.#QNAN0", 8) == 0)
	{
		P += 8;
		CheckForSeparator();
		return 0.0f;
	}

	float result = 0.0f;
	P = fast_atof_move( P, result);

	CheckForSeparator();

	return result;
}

// ------------------------------------------------------------------------------------------------
aiVector2D XFileParser::ReadVector2()
{
	aiVector2D vector;
	vector.x = ReadFloat();
	vector.y = ReadFloat();
	TestForSeparator();

	return vector;
}

// ------------------------------------------------------------------------------------------------
aiVector3D XFileParser::ReadVector3()
{
	aiVector3D vector;
	vector.x = ReadFloat();
	vector.y = ReadFloat();
	vector.z = ReadFloat();
	TestForSeparator();

	return vector;
}

// ------------------------------------------------------------------------------------------------
aiColor4D XFileParser::ReadRGBA()
{
	aiColor4D color;
	color.r = ReadFloat();
	color.g = ReadFloat();
	color.b = ReadFloat();
	color.a = ReadFloat();
	TestForSeparator();

	return color;
}

// ------------------------------------------------------------------------------------------------
aiColor3D XFileParser::ReadRGB()
{
	aiColor3D color;
	color.r = ReadFloat();
	color.g = ReadFloat();
	color.b = ReadFloat();
	TestForSeparator();

	return color;
}

// ------------------------------------------------------------------------------------------------
// Throws an exception with a line number and the given text.
void XFileParser::ThrowException( const std::string& pText)
{
	if( mIsBinaryFormat)
		throw DeadlyImportError( pText);
	else
		throw DeadlyImportError( boost::str( boost::format( "Line %d: %s") % mLineNumber % pText));
}


// ------------------------------------------------------------------------------------------------
// Filters the imported hierarchy for some degenerated cases that some exporters produce.
void XFileParser::FilterHierarchy( XFile::Node* pNode)
{
	// if the node has just a single unnamed child containing a mesh, remove
	// the anonymous node inbetween. The 3DSMax kwXport plugin seems to produce this
	// mess in some cases
	if( pNode->mChildren.size() == 1 && pNode->mMeshes.empty() )
	{
		XFile::Node* child = pNode->mChildren.front();
		if( child->mName.length() == 0 && child->mMeshes.size() > 0)
		{
			// transfer its meshes to us
			for( unsigned int a = 0; a < child->mMeshes.size(); a++)
				pNode->mMeshes.push_back( child->mMeshes[a]);
			child->mMeshes.clear();

			// transfer the transform as well
			pNode->mTrafoMatrix = pNode->mTrafoMatrix * child->mTrafoMatrix;

			// then kill it
			delete child;
			pNode->mChildren.clear();
		}
	}

	// recurse
	for( unsigned int a = 0; a < pNode->mChildren.size(); a++)
		FilterHierarchy( pNode->mChildren[a]);
}

#endif // !! ASSIMP_BUILD_NO_X_IMPORTER
