// Copyright (C) 2004-2009 Christoph Kubisch & Eike Decker
// This file is part of the "Luxinia Engine".
// For conditions of distribution and use, see copyright notice in luxiniapublicsdk.txt

#include "f3d.h"
#include "luxmath/luxmath.h"

EXModelOutputF3D	g_EXOutF3D;

const EXMeshType EXModelOutputF3D::m_typeorder[F3D_MESHES] = {	
		EXMESH_MESH,
		EXMESH_DEPRECATED, //EXMESH_INST_SHRD_VERT,
		EXMESH_DEPRECATED, //EXMESH_INST_SHRD_VERT_SPATIAL,
		EXMESH_INST_SHRD_VERT_TRIS,
		EXMESH_HELPER	
};


booln EXModelOutputF3D::Export(EXModelPTR model, std::ostream &outstream)
{
	EXModel_rebuildIDs(model);

	if (!WriteHeader(outstream,model))
		return FALSE;

	ushort	cnt;

	// export meshes in order
	uint num;
	num = EXModel_getMeshCount(model);
	if (num > SHORT_UNSIGNEDMAX)
		return FALSE;

	for (uint t = 0; t < F3D_MESHES; t++){
		EXMeshType type = m_typeorder[t];
		cnt = (ushort)EXModel_getMeshTypeCount(model,type);
		outstream.write((const char*)&cnt,sizeof(ushort));
	}

	for (uint t = 0; t < F3D_MESHES; t++){
		EXMeshType type = m_typeorder[t];

		for (size_t i = 0; i < num; i++){
			EXMeshPTR mesh = EXModel_getMesh(model,i);
			if (mesh && type == EXMesh_getType(mesh)){
				if (!WriteMesh(outstream,mesh))
					return FALSE;
			}
		}
		
	}

	// export material
	num = EXModel_getMaterialCount(model);
	if (num > SHORT_UNSIGNEDMAX)
		return FALSE;
	cnt = (ushort)num;
	outstream.write((const char*)&cnt,sizeof(ushort));

	for (size_t i = 0; i < num; i++){
		EXMaterialPTR mat = EXModel_getMaterial(model,i);
		if (mat){
			if (!WriteMaterial(outstream,mat))
				return FALSE;
		}
	}

	// export skin
	num = EXModel_getSkinCount(model);
	if (num > SHORT_UNSIGNEDMAX)
		return FALSE;
	cnt = (ushort)num;
	outstream.write((const char*)&cnt,sizeof(ushort));

	for (size_t i = 0; i < num; i++){
		EXSkinPTR skin = EXModel_getSkin(model,i);
		if (skin){
			if (!WriteSkin(outstream,skin))
				return FALSE;
		}
	}

	return TRUE;
}

booln	EXModelOutputF3D::WriteHeader(std::ostream &outstream,EXModelPTR model)
{
	// Write Header
	vfwrite(&m_header,sizeof(ushort),1,outstream);
	// Version number
	vfwrite(&m_version,sizeof(ushort),1,outstream);

	return TRUE;
}
booln	EXModelOutputF3D::WriteMeshIndices(std::ostream &file,EXMeshPTR imesh, booln asushort)
{
/*
		<[4] Total Indices>
		<[2] Primitivelistencount>
		[
		<[2] Material ID>		## ignored
		<[4] Anzahl der Indices>  ## z.b Triangle List : indices = tris * 3, Triangle Strip indices = tris - 2
		[<[2]/[4] Vertex ID>]
		]
*/

	// total indices
	uint indexcount = EXMesh_getIndexTotalCount(imesh);
	vfwrite(&indexcount,sizeof(uint),1,file);

	// group count
	uint primgrpcount = EXMesh_getPrimitiveGroupCount(imesh);
	if (primgrpcount > 0xFFFF)
		return FALSE;

	ushort val = (ushort)primgrpcount;
	vfwrite(&val,sizeof(ushort),1,file);

	for (uint g = 0; g < primgrpcount; g++){
		EXPrimitiveGroupPTR igrp = EXMesh_getPrimitiveGroup(imesh,g);

		// no longer support material ids per primgroup
		val = 0;
		vfwrite(&val,sizeof(ushort),1,file);

		indexcount = EXPrimitiveGroup_getIndicesCount(igrp);
		vfwrite(&indexcount,sizeof(uint),1,file);

		for (uint i = 0; i < indexcount; i++){
			EXIndexPTR iindex = EXPrimitiveGroup_getIndex(igrp,i);
			int overt = EXIndex_getVertexOrderIDX(iindex);
			if (overt < 0)
				return FALSE;

			if (asushort){
				ushort uindex = (ushort)overt;
				vfwrite(&uindex,sizeof(ushort),1,file);
			}
			else{
				uint uindex = (uint)overt;
				vfwrite(&uindex,sizeof(uint),1,file);
			}
		}		
	}

	return TRUE;
}
booln	EXModelOutputF3D::WriteMesh(std::ostream &file,EXMeshPTR imesh)
{

	EXMeshType type = EXMesh_getType(imesh);
	// basic stuff
	{
		EXMeshPTR parent = EXMesh_getParent(imesh);
		ushort val = parent ? EXMesh_getID(parent) : 0;
		vfwrite(&val,sizeof(ushort),1,file);
		// name
		size_t len = strlen(EXMesh_getName(imesh));
		val = (ushort)len;
		vfwrite(&val,sizeof(ushort),1,file);
		vfwrite(EXMesh_getName(imesh),sizeof(char),val,file);
		// matrix
		vfwrite(EXMesh_getMatrix(imesh),sizeof(float),16,file);
		// Ref Pos, Rot, Scale
		vfwrite(EXMesh_getPosition(imesh),sizeof(float),3,file);
		vfwrite(EXMesh_getRotation(imesh),sizeof(float),4,file);
		vfwrite(EXMesh_getScale(imesh),sizeof(float),3,file);
	}

	switch (type){
	case EXMESH_HELPER:
		break;
	case EXMESH_MESH:
		/*  <[2] Material ID>
		<[4] Anzahl der Punkte>
		[ <[16] tx,ty,tz,tw> <[8] (u,v)> <[4] (r,g,b,a)> <[12] (nx,ny,nz)> <[12] (x,y,z)> ] 
		<1> Primitve Type
		{
		0: Points
		1: Triangle List
		2: Triangle Strip
		3: Quad List
		4: Quad Strip
		}

		*/
	{

		// material
		EXMaterialPTR imat = EXMesh_getMaterial(imesh);
		ushort	val = imat ? EXMaterial_getID(imat) : 0;
		vfwrite(&val,sizeof(ushort),1,file);

		// number of Vertices
		uint vcount = EXMesh_getVertexOrderCount(imesh);
		vfwrite(&vcount,sizeof(uint),1,file);

		// vertices
		uint uvcnt = EXMesh_getUsedUVWCount(imesh);
		for (uint i = 0; i < vcount; i++){
			EXVertexPTR ivertex = EXMesh_getVertexOrdered(imesh,i);
			// write 4 uv channels
			for (uint uv = 0; uv < 4; uv++){
				if (uv < uvcnt){
					vfwrite(EXVertex_getUVW(ivertex,uv),sizeof(float),2,file);
				}
				else if (uv == 1 && uvcnt == 1){
					vfwrite(EXVertex_getUVW(ivertex,0),sizeof(float),2,file);
				}
				else{
					static float zeros[] = {0.0f,0.0f};
					vfwrite(&zeros,sizeof(float),2,file);
				}
			}

			short	shrt4[4];
			// normal
			Vector3short_FROM_float(shrt4,EXVertex_getNormal(ivertex));
			shrt4[3] = 0;
			vfwrite(shrt4,sizeof(short),4,file);

			// tangent
			Vector4short_FROM_float(shrt4,EXVertex_getTangent(ivertex));
			vfwrite(shrt4,sizeof(short),4,file);

			// pos
			vfwrite(EXVertex_getPosition(ivertex),sizeof(float),3,file);

			// color
			uchar color[4];
			Vector4ubyte_FROM_float(color,EXVertex_getColor(ivertex));
			vfwrite(&color,sizeof(uchar),4,file);
		}

		// primitive type
		uchar val8 = EXMesh_getPrimitiveType(imesh);
		vfwrite(&val8,sizeof(uchar),1,file);

		// indices
		if (!WriteMeshIndices(file,imesh,vcount <= 0xFFFF))
			return FALSE;
	}
		break;
#if 0
	case EXMESH_INST_SHRD_VERT:
	case EXMESH_INST_SHRD_VERT_SPATIAL:
	{

		/*	<[2] Objekt ID als instanz>
		<[2] Material ID>
		<[4] Total Indices>
		<[2] Polygonlistencount>
		[
		<[2] Material ID>
		<[4] Anzahl der Indices>
		[<[2/4] Vertex ID>]*/

		// instance
		ushort val = EXMesh_getID(EXMesh_getInstance(imesh));
		vfwrite(&val,sizeof(ushort),1,file);

		// material
		EXMaterialPTR imat = EXMesh_getMaterial(imesh);
		val = imat ? EXMaterial_getID(imat) : 0;
		vfwrite(&val,sizeof(ushort),1,file);

		// indices
		uint vcount = EXMesh_getVertexCount(EXMesh_getInstance(imesh));
		if (!WriteMeshIndices(file,imesh,vcount > 0xFFFF))
			return FALSE;
	}
		break;
#endif
	case EXMESH_INST_SHRD_VERT_TRIS:
	{
		// instance
		ushort val = EXMesh_getID(EXMesh_getInstance(imesh));
		vfwrite(&val,sizeof(ushort),1,file);

		// material
		EXMaterialPTR imat = EXMesh_getMaterial(imesh);
		val = imat ? EXMaterial_getID(imat) : 0;
		vfwrite(&val,sizeof(ushort),1,file);
	}
		break;
	}

	return TRUE;

}
booln	EXModelOutputF3D::WriteMaterial(std::ostream &file,EXMaterialPTR imat)
{
	const char* sname = EXMaterial_getShaderName(imat);
	// Shader name
	if (sname){
		size_t len = strlen(sname);
		ushort val = (ushort) len;
		vfwrite(&val,sizeof(ushort),1,file);
		vfwrite(EXMaterial_getShaderName(imat),sizeof(char),val,file);
	}
	else{
		ushort val = 0;
		vfwrite(&val,sizeof(ushort),1,file);
	}

	return TRUE;
}
booln	EXModelOutputF3D::WriteSkin(std::ostream &file,EXSkinPTR iskin)
{
	/*	<[2] ID des deformierten Meshes> 
	<[2] Maximal Anzahl der Gewichtungen pro Punkt
	<[4] Anzahl der Punkte>
	[	 
	<[2] Anzahl Weights>                         ## Gewichte in Absteigender Reihenfolge
	[<[2] Parent ID> <[2] Weight>] 
	]*/
	// deform mesh
	ushort val = EXMesh_getID(EXSkin_getMesh(iskin));
	vfwrite(&val,sizeof(ushort),1,file);
	// max count
	val = (ushort)EXSkin_getUsedWeightCount(iskin);
	vfwrite(&val,sizeof(ushort),1,file);

	// vertices
	uint val32 = EXSkin_getVertexOrderCount(iskin);
	vfwrite(&val32,sizeof(uint),1,file);

	uint vcnt = val32;
	for (size_t i = 0; i < vcnt; i++){
		EXSkinVertexPTR	isvert = EXSkin_getVertexOrdered(iskin,i);
		ushort wcnt = (ushort)EXSkinVertex_getWeightsCount(isvert);
		vfwrite(&wcnt,sizeof(ushort),1,file);
		for (uint n = 0; n < wcnt; n++){
			EXSkinWeightPTR isweight = EXSkinVertex_getWeight(isvert,n);
			val = EXMesh_getID(EXSkinWeight_getParent(isweight));
			vfwrite(&val,sizeof(ushort),1,file);
			val = (ushort)(EXSkinWeight_getWeight(isweight) * M_USHORT);
			vfwrite(&val,sizeof(ushort),1,file);
		}
	}

	return TRUE;
}
