// 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 "exmodel_local.h"

//////////////////////////////////////////////////////////////////////////
// LOCALS

#define clearArray(array,length) memset( array, 0, sizeof(array[0])*(length)); 



const char *EXModel_lastError(EXModelPTR model)
{
	return model->errorstring;
}

const char *EXAnim_lastError(EXAnimPTR model)
{
	return model->errorstring;
}

//////////////////////////////////////////////////////////////////////////
// Animation

void	EXAnim_rebuldIDs(EXAnimPTR imodel)
{
	// TRACK
	for (size_t i = 0; i < imodel->tracks.size(); i++){
		imodel->tracks[i]->param.id = i + 1;
	}
}

bool EXPRSKey_compare (EXPRSKey &a, EXPRSKey &b) {
	return a.time < b.time;
}

void EXTrack_sortAndPackKeys(EXTrack &imesh)
{

	if (!imesh.keys.size())
		return;

	std::vector<EXPRSKey> oldkeys = imesh.keys;
	std::vector<EXPRSKey> &newkeys = imesh.keys;
	std::sort(oldkeys.begin(),oldkeys.end(),EXPRSKey_compare);

	newkeys.clear();
	newkeys.push_back(oldkeys[0]);

	
	for (size_t i = 1; i < oldkeys.size(); i++)
	{
		EXPRSKey &iprev = newkeys[i-1];
		EXPRSKey &ikey = oldkeys[i];

		if (fabsf(iprev.time-ikey.time)<0.1f){
			// pack
			enum32 diff = iprev.usedFlags ^ ikey.usedFlags;
			if (diff&EXTRANS_POS){
				Vector3Copy(iprev.pos,ikey.pos);
			}
			if (diff&EXTRANS_ROT){
				Vector4Copy(iprev.rot,ikey.rot);
			}
			if (diff&EXTRANS_SCALE){
				Vector3Copy(iprev.scale,ikey.scale);
			}
			iprev.usedFlags |= ikey.usedFlags;
		}
		else{
			// add
			newkeys.push_back(oldkeys[i]);
		}
	}
}

void	EXAnim_validate(EXAnimPTR imodel)
{
	for (size_t i = 0; i < imodel->tracks.size(); i++){
		EXTrack_sortAndPackKeys(*imodel->tracks[i]);
	}
}

int	EXTrack_findDir(EXTrack &itrack, int idx, booln next, enum32 what)
{
	int dir = next ? 1 : -1;
	int finish = next ? itrack.keys.size()+1 : -1;
	for (int i = idx; idx != finish; i += dir){
		if (itrack.keys[idx].usedFlags & what)
			return idx;
	}

	return -1;
}

int EXTrack_fixKey(EXTrack &itrack, int idx, enum32 what, booln spline, booln loop)
{
	EXPRSKey key = itrack.keys[idx];
	int nextidx = EXTrack_findDir(itrack,idx,TRUE, what);
	int previdx = EXTrack_findDir(itrack,idx,FALSE,what);

	if (nextidx < 0 && previdx < 0){
		// found no key at all
		return what;
	}
	else if (nextidx >= 0 && previdx >= 0){
		EXPRSKey knext = itrack.keys[nextidx];
		EXPRSKey kprev = itrack.keys[previdx];
		// find interpolation timefrac value
		float timefrac = (key.time - kprev.time) / (knext.time - kprev.time);

		// in case we screwed it up
		timefrac = CLAMP(timefrac,0.0f,1.0f);

		if (what == EXTRANS_ROT){
			QuatSlerp(key.rot,timefrac,kprev.rot,knext.rot);
		}
		else if (what == EXTRANS_SCALE){
			Vector3Lerp(key.scale,timefrac,kprev.scale,knext.scale);
		}
		else if (what == EXTRANS_POS){
			Vector3Lerp(key.pos,timefrac,kprev.pos,knext.pos);
		}

		return 0;
	}
	else{
		// in case we got only one match, no need to interpolate
		// else interpolate
		EXPRSKey &knext = nextidx >= 0 ? itrack.keys[nextidx] : itrack.keys[previdx];

		switch (what){
		case EXTRANS_SCALE:
		case EXTRANS_POS: 
			Vector3Copy(key.pos,knext.pos);
			break;
		case EXTRANS_ROT:
			Vector4Copy(key.rot,knext.rot);
		}
		return 0;
	}

	

}

void EXTrack_fixKeys(EXTrack &itrack,booln spline, booln loop)
{
	int ignore = 0;
	for (size_t i = 0; i < itrack.keys.size(); i++){
		EXPRSKey &key = itrack.keys[i];
		int lacks = (~key.usedFlags)&EXTRANS_MASK;
		lacks &= ~ignore;
		if(key.usedFlags && lacks){
			if (lacks & EXTRANS_POS){
				ignore=EXTrack_fixKey(itrack,i,EXTRANS_POS,spline,loop);
			}
			if (lacks & EXTRANS_SCALE){
				ignore=EXTrack_fixKey(itrack,i,EXTRANS_SCALE,spline,loop);
			}
			if (lacks & EXTRANS_ROT){
				ignore=EXTrack_fixKey(itrack,i,EXTRANS_ROT,spline,loop);
			}
		}
	}
}



void EXAnim_allStatesPerKey		(EXAnimPTR imodel, booln spline, booln loop)
{
	// generates keys with all PRS states
	for (size_t i = 0; i < imodel->tracks.size(); i++){
		EXTrack_fixKeys(*imodel->tracks[i],spline,loop);
	}

}

//////////////////////////////////////////////////////////////////////////
// Model

bool	EXMeshPTR_compare (EXMeshPTR a, EXMeshPTR b) 
{
	return a->param.id < b->param.id ;
}
void	EXModel_rebuildIDs(EXModel *imodel)
{
	uint	counter[EXMESHS];
	uint	offset[EXMESHS];

	clearArray(counter,EXMESHS);
	clearArray(offset,EXMESHS);


	// MESH
	// first prepare offsets
	for (size_t i = 0; i < imodel->meshes.size(); i++){
		counter[imodel->meshes[i]->meshtype]++;
	}
	for (uint i = 1; i < EXMESHS; i++){
		offset[i] = counter[i-1];
		offset[i] += offset[i-1];
	}
	for (uint i = 0; i < EXMESHS; i++)
		counter[i] = 1;

	// now rebuild offsets
	for (size_t i = 0; i < imodel->meshes.size(); i++){
		EXMeshPTR imesh = imodel->meshes[i];
		imesh->param.id = counter[imesh->meshtype] + offset[imesh->meshtype];
		counter[imesh->meshtype]++;
	}

	// sort
	std::sort(imodel->meshes.begin(),imodel->meshes.end(),EXMeshPTR_compare);


	// MATERIAL
	for (size_t i = 0; i < imodel->materials.size(); i++){
		imodel->materials[i]->param.id = i + 1;
	}

	// SKIN
	for (size_t i = 0; i < imodel->skins.size(); i++){
		imodel->skins[i]->param.id = i + 1;
	}
}


booln EXSkin_fix(EXSkin &iskin)
{
	EXMeshPTR mesh = iskin.mesh;
	if (!mesh){
		ERRORSTRING(iskin.host.model,"Skin without mesh");
		return FALSE;
	}

	
	// make unique
	if (iskin.skinVerts.size() != mesh->vertices.size()){
		std::vector<EXVertex> &meshverts = mesh->vertices;
		std::vector<EXSkinVertex> oldverts = iskin.skinVerts;
		iskin.skinVerts.clear();
		iskin.skinVerts.resize(mesh->vertices.size());

		for (size_t v = 0; v < oldverts.size(); v++){
			EXSkinVertex &svert = oldverts[v];
			booln found = FALSE;
			for (size_t m = 0; m < meshverts.size(); m++){
				if (meshverts[m].vertexID == svert.vertexID){
					iskin.skinVerts[m] = svert;
					found = TRUE;
				}
			}

			if (!found){
				ERRORSTRING(iskin.host.model,"Skin vertex mesh vertex not found");
				return FALSE;
			}
		}
	}

	// create parent list if needed
	booln checkparent = iskin.skinParents.size() > 0;
	for (size_t v = 0; v < iskin.skinVerts.size(); v++){
		EXSkinVertex &svert = iskin.skinVerts[v];
		svert.vertexIDX = (int)v;

		for (uint w = 0; w < svert.numWeights; w++){
			EXSkinWeight &wt = svert.weights[w];
			if (checkparent)
				wt.parent = EXSkin_getParentMesh(&iskin,wt.parentIDX);

			if (!wt.parent){
				ERRORSTRING(iskin.host.model,"Skin vertex weight parent not found");
				return FALSE;
			}

			if (!checkparent)
				wt.parentIDX = FindOrAdd<EXMeshPTR>(iskin.skinParents,wt.parent);
		}
	}

	return TRUE;
}


	// assumes Skin to be pervertex
void	EXMesh_buildOrder(EXMesh &imesh)
{

	uint idx = 0;
	imesh.vertexOrder.clear();
	for (size_t i = 0; i <  imesh.vertices.size(); i++){
		EXVertex &vert =  imesh.vertices[i];
		if (vert.clone >= 0){
			vert.orderIDX = imesh.vertices[vert.clone].orderIDX;
		}
		else if (vert.orderIDX >= 0)
		{
			vert.orderIDX = idx;
			imesh.vertexOrder.push_back(i);
			idx++;
		}
	}

	imesh.validVertexIndex = TRUE;
}

booln	EXMesh_linkIndices(EXMesh &imesh)
{
	EXMeshPTR vertmesh = imesh.instance ? imesh.instance : &imesh;
	for (size_t g = 0; g < imesh.primitivegroups.size(); g++){
		EXPrimitiveGroup &iprimgrp = imesh.primitivegroups[g];
		// and set vertex order indices
		for (size_t n = 0; n < iprimgrp.indices.size(); n++){
			EXIndex &index = iprimgrp.indices[n];
			if (index.vertexIDX < 0){
				ERRORSTRING(imesh.host.model,"Mesh index without vertexIDX");
				return FALSE;
			}
			EXVertexPTR vert = EXMesh_getVertex(vertmesh,index.vertexIDX);
			if (!vert){
				ERRORSTRING(imesh.host.model,"Mesh index vertex not found");
				return FALSE;
			}
			index.vertex = vert;
			vert->orderIDX = index.vertexIDX;
		}
	}

	return TRUE;
}

booln	EXModel_validate(EXModelPTR imodel)
{
	// link everything
	// start with meshes, check whether all indices are valid
	// and set material sub pointers
	for (size_t i = 0; i < imodel->meshes.size(); i++){
		EXMesh &imesh = *imodel->meshes[i];

		if ((imesh.meshtype == EXMESH_INST_SHRD_VERT_TRIS) && !imesh.instance)
		{
			ERRORSTRING(imodel,"Mesh with instance meshtype but without instance mesh");
			return FALSE;
		}

		if ((imesh.meshtype != EXMESH_INST_SHRD_VERT_TRIS) && imesh.instance)
		{
			ERRORSTRING(imodel,"Mesh non instanced, has instance mesh");
			return FALSE;
		}

		// sanity checks and values
		switch(imesh.meshtype){
		case EXMESH_MESH:
			if (imesh.skin)
				imesh.skin->mesh = imodel->meshes[i];

			if (!EXMesh_linkIndices(imesh))
				return FALSE;

			EXModelMesh_splitMultiMaterialToMeshes(imodel,imesh);
			break;
		case EXMESH_INST_SHRD_VERT_TRIS:
			if (imesh.primitivegroups.size() || imesh.vertices.size()){
				ERRORSTRING(imodel,"Mesh with instanced verts and tris, has primpgrps or verts");
				return FALSE;
			}
			if (imesh.material && EXMaterial_getSubCount(imesh.material)){
				ERRORSTRING(imodel,"Mesh with instanced verts and tris, has multi-material");
				return FALSE;
			}
			break;
		case EXMESH_HELPER:
			if (imesh.primitivegroups.size() || imesh.vertices.size()){
				ERRORSTRING(imodel,"Mesh with helper type has geometry");
				return FALSE;
			}
			imesh.vertexOrder.clear();
			break;
		default:
			break;
		}
	}



	// check integrity of skin (parent list and vertexIDX)
	// make unique per vertex
	for (size_t i = 0; i < imodel->skins.size(); i++){
		EXSkin &iskin = *imodel->skins[i];
		if (iskin.mesh)
			iskin.mesh->skin = imodel->skins[i];
		if (!EXSkin_fix(iskin)){
			ERRORSTRING(imodel,"could not fix skin");
			return FALSE;
		}
	}


	// time for tidy up
	for (size_t i = 0; i < imodel->meshes.size(); i++){
		EXMesh &imesh = *imodel->meshes[i];
		EXMesh_buildOrder(imesh);
	}
	for (size_t i = 0; i < imodel->skins.size(); i++){
		EXSkin &iskin = *imodel->skins[i];
		if (iskin.mesh)
			iskin.mesh->skin = imodel->skins[i];
		if (!EXSkin_hasValidVertexOrder(&iskin)){
			ERRORSTRING(imodel,"skin without valid vertex order");
			return FALSE;
		}
	}

	
	return TRUE;
}



int		EXVertex_areIdentical(EXVertex &a, EXVertex &b, int uvchannels)
{
	// base parameters
	float	*aptr = a.position;
	float	*bptr = b.position;

	while (aptr < a.uvws[0]){
		if (*aptr != *bptr)
			return FALSE;

		aptr++;
		bptr++;
	}

	// uvws
	aptr = a.uvws[0];
	bptr = b.uvws[0];
	while (aptr < a.uvws[uvchannels]){
		if (*aptr != *bptr)
			return FALSE;

		aptr++;
		bptr++;
	}

	return TRUE;
}

// assumes correct vertex->index
void	EXMesh_markCloneVertices(EXMesh &imesh)
{
	for (size_t i = 0; i <  imesh.vertices.size(); i++){
		EXVertex &vert =  imesh.vertices[i];
		if (vert.clone < 0 && vert.orderIDX >= 0)
		{
			// check if identical
			for (size_t n = i+1; n < imesh.vertices.size(); n++){
				EXVertex &vertb =  imesh.vertices[n];
				if (EXVertex_areIdentical(vert,vertb,imesh.maxUVcount)){
					vertb.clone = i;
					break;
				}
			}
		}
	}

	EXMesh_buildOrder(imesh);
}

void	EXModel_removeIdenticalVertices(EXModelPTR imodel)
{
	for (size_t i = 0; i < imodel->meshes.size(); i++){
		if (imodel->meshes[i]->meshtype == EXMESH_MESH)
			EXMesh_markCloneVertices(*imodel->meshes[i]);
	}
}

static uint	EXModel_newMeshID(EXModelPTR imodel)
{
	uint maxid = 0;
	for (size_t i = 0; i < imodel->meshes.size(); i++){
		if (imodel->meshes[i]->param.id != EXMODEL_NOID)
			maxid = MAX(imodel->meshes[i]->param.id,maxid);
	}

	return maxid+1;
}
static uint	EXModel_newSkinID(EXModelPTR imodel)
{
	uint maxid = 0;
	for (size_t i = 0; i < imodel->skins.size(); i++){
		if (imodel->skins[i]->param.id != EXMODEL_NOID)
			maxid = MAX(imodel->skins[i]->param.id,maxid);
	}

	return maxid+1;
}

struct EXMeshSplit{
	EXMeshPTR				mesh;
	EXSkinPTR				skin;
	EXPrimitiveGroupPTR		primgrp;
	std::vector<int>		old2new;
};

static void	EXModelMesh_splitMultiMaterialToMeshes(EXModelPTR imodel,EXMesh &imesh)
{ 
	uint subcnt;
	if (imesh.meshtype != EXMESH_MESH || !imesh.material || (subcnt = EXMaterial_getSubCount(imesh.material)) < 2 )
		return;

	
	size_t oldsize = imesh.primitivegroups.size();
	std::vector<uint>	curids;

	// find out how many differnt ids are used
	for (size_t g = 0; g < oldsize; g++){
		EXPrimitiveGroup &grp = imesh.primitivegroups[g];
		
		for (size_t i = 0; i < grp.indices.size(); i++){
			FindOrAdd<uint>(curids,grp.indices[i].primID % subcnt);
		}
	}
	// simple, just one used
	if (curids.size() == 1){
		imesh.material = EXMaterial_getSubMaterial(imesh.material,curids[0]);
		return;
	}

	// more complex as we must split
	// we generate additional meshes that are childs
	// of this mesh to keep animation/hierarchy same.
	// we also must duplicate skin data!

	std::vector<EXMeshSplit> splits;
	splits.resize(subcnt);

	splits[curids[0]].mesh = &imesh;
	splits[curids[0]].skin = imesh.skin;

	for (size_t i = 1; i < curids.size(); i++ ){
		uint curid = curids[i];
		char buffer[8] = {0};
		itoa(curid, buffer, 10);

		std::string newname = imesh.param.name + "_mat_" + buffer;
		EXMeshPTR newmesh = EXModel_provideMesh(imodel,newname.c_str(),EXModel_newMeshID(imodel));
		splits[curid].mesh = newmesh;
		
		EXMesh_setMatrix(newmesh,Matrix44GetIdentity());
		EXMesh_setScale(newmesh,1,1,1);
		EXMesh_setPosition(newmesh,0,0,0);
		EXMesh_setRotation(newmesh,0,0,1,0);

		EXMesh_setParent(newmesh,&imesh);
		EXMesh_setMaterial(newmesh,EXMaterial_getSubMaterial(imesh.material,curid));
		EXMesh_setType(newmesh,EXMESH_MESH);

		EXMesh_setPrimitiveType(newmesh,imesh.primitivetype);
		EXMesh_setUsedUVWCount(newmesh,imesh.maxUVcount);

		if (imesh.skin){
			std::string newskinname = imesh.skin->param.name + "_mat_" + buffer;
			EXSkinPTR newskin = EXModel_provideSkin(imodel,newskinname.c_str(),EXModel_newSkinID(imodel));
			EXSkin_setMesh(newskin,newmesh);
			EXSkin_setUsedWeightCount(newskin,EXSkin_getUsedWeightCount(imesh.skin));
			splits[curid].skin = newskin;
		}
	}
	EXMesh_setMaterial(&imesh,EXMaterial_getSubMaterial(imesh.material,curids[0]));
	// spread old indices on new grps
	// and copy vertices along

	std::vector<EXVertex>	oldverts = imesh.vertices;
	imesh.vertices.clear();

	for (size_t i = 0; i < splits.size(); i++ ){
		splits[i].old2new.resize(oldverts.size(),-1);
	}

	std::vector<EXPrimitiveGroup> oldprimgrps = imesh.primitivegroups;
	imesh.primitivegroups.clear();

	for (size_t g = 0; g < oldsize; g++){
		EXPrimitiveGroup &grp = oldprimgrps[g];

		// must split off others
		// create new meshes/groups
		for (size_t s = 0; s < splits.size(); s++){
			splits[s].primgrp = EXMesh_addPrimitiveGroup(splits[s].mesh);;
		}

		
		std::vector<EXIndex>	oldindices = grp.indices;
		for (size_t i = 0; i < oldindices.size(); i++){
			EXIndex		&ind	= oldindices[i];
			uint		idx		= ind.primID % subcnt;
			EXMeshSplit &split  = splits[idx];


			// add to verts (if not already)
			if (split.old2new[ind.vertexIDX] < 0){
				split.mesh->vertices.push_back(oldverts[ind.vertexIDX]);
				split.old2new[ind.vertexIDX] = ((int)split.mesh->vertices.size())-1;
			}

			// create new idx
			EXIndex newidx;
			newidx.primID = idx;
			newidx.vertexIDX = split.old2new[ind.vertexIDX];
			split.primgrp->indices.push_back(newidx);
		}
	}

	if (imesh.skin){
		// convert skin
		// verts either use IDS (good for us), doesnt require a change
		// or match (ugly)

		EXSkin origskin = *imesh.skin;

		booln matchverts = origskin.skinVerts.size() == oldverts.size();
		booln parents    = origskin.skinParents.size() > 0;

		for (size_t i = 0; i < splits.size(); i++ ){
			EXMeshSplit &split  = splits[i];
			EXSkinPTR	newskin = split.skin;
			newskin->maxWeights = origskin.maxWeights;
			newskin->skinVerts.clear();
			newskin->skinParents.clear();

			if (matchverts){
				newskin->skinVerts.resize(split.mesh->vertices.size());
				// copy from old 2 new
				for (size_t v = 0; v < split.old2new.size(); v++){
					if (split.old2new[v] < 0) continue;
					newskin->skinVerts[split.old2new[v]] = origskin.skinVerts[v];
				}
			}
			else{
				std::vector<int> usedIDS;
				for (size_t v = 0; v < split.old2new.size(); v++){
					int id = split.old2new[v];
					if (id < 0) continue;
					FindOrAdd<int>(usedIDS,split.mesh->vertices[id].vertexID);
				}


				for (size_t v = 0; v < origskin.skinVerts.size(); v++){
					for (size_t n = 0; n < usedIDS.size(); n++){
						if (usedIDS[n] == origskin.skinVerts[v].vertexID){
							newskin->skinVerts.push_back(origskin.skinVerts[v]);
						}
					}
				}
			}

			if (parents){
				// let linking rebuild the parent list
				// just remove
				for (size_t v = 0; v < newskin->skinVerts.size(); v++){
					EXSkinVertex &svert = newskin->skinVerts[v];

					for (uint w = 0; w < svert.numWeights; w++){
						EXSkinWeight &wt = svert.weights[w];
						wt.parent = EXSkin_getParentMesh(&origskin,wt.parentIDX);
					}
				}
			}
		}
	}


	// at the end relink all meshes again
	for (size_t i = 0; i < splits.size(); i++ ){
		EXMesh_linkIndices(*splits[i].mesh);
	}
}

static void	EXModel_splitMultiMaterialsToMeshes(EXModelPTR imodel)
{
	size_t origsize = imodel->meshes.size();
	for (size_t i = 0; i < origsize; i++){
		if (imodel->meshes[i]->meshtype != EXMESH_HELPER)
			EXModelMesh_splitMultiMaterialToMeshes(imodel,*imodel->meshes[i]);
	}
}

void EXModel_swizzle(EXModelPTR imodel,uint axis[3],float dir[4], booln reversetris, booln flipnormals)
{
	Vector3 normaldirs;
	
	if (flipnormals)
		Vector3Negate(normaldirs,dir);
	else
		Vector3Copy(normaldirs,dir);

	// scale is encoded into vertextransforms
	Vector3 scaledirs;
	Vector3Set(scaledirs,1.0f,1.0f,1.0f);
	
	for (size_t m = 0; m < imodel->meshes.size(); m++){
		EXMesh &meshbrowse = *imodel->meshes[m];

		Matrix44Swizzle1(meshbrowse.location.matrix,axis,dir);
		Vector3Swizzle(meshbrowse.location.pos,axis,dir);
		QuatSwizzle(meshbrowse.location.rot,axis,dir);
		Vector3Swizzle(meshbrowse.location.scale,axis,scaledirs);

		if (meshbrowse.meshtype != EXMESH_HELPER){
			// transform vertices
			for (size_t i = 0; i < meshbrowse.vertices.size(); i++){
				EXVertex &vertex = meshbrowse.vertices[i];
				Vector3Swizzle(vertex.position,axis,dir);
				Vector3Swizzle(vertex.normal,axis,normaldirs);
				Vector3Swizzle(vertex.tangent,axis,normaldirs);
				Vector3Swizzle(vertex.binormal,axis,normaldirs);
			}
			
			
			if (reversetris){
				for (size_t i = 0; i < meshbrowse.primitivegroups.size(); i++){
					std::vector<EXIndex> &indices = meshbrowse.primitivegroups[i].indices;
					std::reverse(indices.begin(),indices.end());
				}
			}
			
		}
	}
}

void EXAnim_swizzle(EXAnimPTR imodel,uint axis[3],float dir[4], booln reversetris, booln flipnormals)
{
	Vector3 scaledirs;
	Vector3Set(scaledirs,1.0f,1.0f,1.0f);

	for (size_t i = 0; i < imodel->tracks.size(); i++){
		EXTrack &track = *imodel->tracks[i];
		for (size_t i = 0; i < track.keys.size(); i++){
			EXPRSKey &keybrowse = track.keys[i];
			Vector3Swizzle(keybrowse.pos,axis,dir);
			QuatSwizzle(keybrowse.rot,axis,dir);
			Vector3Swizzle(keybrowse.scale,axis,scaledirs);
		}
	}
}

