/////////////////////////////////////////////////////////////////////////////////////////////////////
//
//	Crytek Character Animation source code
//	
//	History:
//	Created by Sergiy Migdalskiy
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "StringUtils.h"
#include "CgfUtils.h"
 
using namespace CryStringUtils;

//////////////////////////////////////////////////////////////////////////
// given the chunk, parses it and returns the array of pointers to the strings with the bone names 
// in the supplied array. Returns true when successful
// PARAMETERS:
//  chunkHeader - the header of the chunk in the chunk table
//  pChunk      - the raw chunk data in the file
//  nChunkSize  - the size of the raw data piece in the file
//  arrNames    - the array that will be resized according to the number of entries, and each element of this array
//                will be the pointer to the corresponding 0-terminated name string.
bool LoadBoneNameList (const CHUNK_HEADER& chunkHeader, const void* pChunk, unsigned nChunkSize, std::vector<const char*>& arrNames)
{
	switch (chunkHeader.ChunkVersion)
	{
		case BONENAMELIST_CHUNK_DESC_0744::VERSION:
		{
			// the old chunk version - fixed-size name list
			const BONENAMELIST_CHUNK_DESC_0744* pNameChunk = (const BONENAMELIST_CHUNK_DESC_0744*)(pChunk);

			int nGeomBones = pNameChunk->nEntities;

			// just fill in the pointers to the fixed-size string buffers
			const NAME_ENTITY* pGeomBoneNameTable = (const NAME_ENTITY*)(pNameChunk+1);
			if(nGeomBones < 0 || nGeomBones > 0x800)
				return false;
			arrNames.resize(nGeomBones);
			for (int i = 0; i < nGeomBones; ++i)
				arrNames[i] = pGeomBoneNameTable[i].name;
		}
		break;

		case BONENAMELIST_CHUNK_DESC_0745::VERSION:
		{
			// the new memory-economizing chunk with variable-length packed strings following tightly each other
			const BONENAMELIST_CHUNK_DESC_0745* pNameChunk = (const BONENAMELIST_CHUNK_DESC_0745*)(pChunk);
			int nGeomBones = pNameChunk->numEntities;

			// we know how many strings there are there; the whole bunch of strings may 
			// be followed by pad zeros, to enable alignment
			arrNames.resize(nGeomBones, "");
			
			// scan through all the strings, each string following immediately the 0 terminator of the previous one
			const char* pNameListEnd = ((const char*)pNameChunk) + nChunkSize;
			const char* pName = (const char*)(pNameChunk+1);
			int nName = 0;
			while (*pName && pName < pNameListEnd && nName < nGeomBones)
			{
				arrNames[nName] = pName;
				pName += strnlen(pName, pNameListEnd) + 1;
				++nName;
			}
			if (nName < nGeomBones)
			{
				// the chunk is truncated
#ifdef _CRY_ANIMATION_BASE_HEADER_
				// if this is in the engine, log the error
				g_GetLog()->LogWarning ("\003inconsistent bone name list chunk: only %d out of %d bone names have been read.", nName, nGeomBones);
#endif
				return false;
			}
		}
		break;
	}

	return true;
}



// Attempts to load the material from the given material chunk to the "me" structure
MatChunkLoadErrorEnum LoadMatEntity (const CHUNK_HEADER& chunkHeader, const void* pChunkData, unsigned nChunkSize, MAT_ENTITY& me)
{
	memset(&me, 0, sizeof(MAT_ENTITY));

	switch (chunkHeader.ChunkVersion) 
	{
	case MTL_CHUNK_DESC_0746::VERSION:
	{
    const MTL_CHUNK_DESC_0746* pMatChunk = (const MTL_CHUNK_DESC_0746*)pChunkData;
    
    me.m_New = 2;
    strcpy(me.name, pMatChunk->name);
    switch (pMatChunk->MtlType)
    {
      case MTL_STANDARD:
				{
        me.IsStdMat = true;
        me.col_d = pMatChunk->std.col_d;
        me.col_a = pMatChunk->std.col_a;
        //me.col_a.g=0;
        //me.col_a.b=0;
        me.col_s = pMatChunk->std.col_s;
        
        me.specLevel = pMatChunk->std.specLevel;
        me.specShininess = pMatChunk->std.specShininess*100;
        me.opacity = pMatChunk->std.opacity;
        me.selfIllum = pMatChunk->std.selfIllum;
        me.flags = pMatChunk->std.flags;
        
        me.Dyn_Bounce = pMatChunk->std.Dyn_Bounce;
        me.Dyn_StaticFriction = pMatChunk->std.Dyn_StaticFriction;
        me.Dyn_SlidingFriction = pMatChunk->std.Dyn_SlidingFriction;
        /* //Timur[10/24/2001] 
        strcpy(me.map_a, pMatChunk->std.tex_a.name);
        strcpy(me.map_d, pMatChunk->std.tex_d.name);
        strcpy(me.map_o, pMatChunk->std.tex_o.name);
        strcpy(me.map_b, pMatChunk->std.tex_b.name);
        strcpy(me.map_s, pMatChunk->std.tex_s.name);
        strcpy(me.map_g, pMatChunk->std.tex_g.name);
        strcpy(me.map_c, pMatChunk->std.tex_c.name);
        strcpy(me.map_e, pMatChunk->std.tex_rl.name);
        strcpy(me.map_rr, pMatChunk->std.tex_rr.name);
        strcpy(me.map_det, pMatChunk->std.tex_det.name);
        */
        me.map_a = pMatChunk->std.tex_a;
        me.map_d = pMatChunk->std.tex_d;
        me.map_o = pMatChunk->std.tex_o;
        me.map_b = pMatChunk->std.tex_b;
        me.map_s = pMatChunk->std.tex_s;
        me.map_g = pMatChunk->std.tex_g;
        me.map_detail = pMatChunk->std.tex_fl;
        me.map_e = pMatChunk->std.tex_rl;
        me.map_subsurf = pMatChunk->std.tex_subsurf;
        me.map_displ = pMatChunk->std.tex_det;
        
        me.nChildren = pMatChunk->multi.nChildren;

				me.alpharef = pMatChunk->alphaTest;
				}        
        return MCLE_Success;
        
			default:
				return MCLE_IgnoredType;
    }
	}
	break;
	case MTL_CHUNK_DESC_0745::VERSION:
  {
    const MTL_CHUNK_DESC_0745* pMatChunk = (const MTL_CHUNK_DESC_0745*)pChunkData;
    
    me.m_New = 1;
    strcpy(me.name, pMatChunk->name);
    switch (pMatChunk->MtlType)
    {
      case MTL_STANDARD:
        me.IsStdMat = true;
        me.col_d = pMatChunk->std.col_d;
        me.col_a = pMatChunk->std.col_a;
				//me.col_a.g=0;
				//me.col_a.b=0;
        me.col_s = pMatChunk->std.col_s;
        
        me.specLevel = pMatChunk->std.specLevel;
        me.specShininess = pMatChunk->std.specShininess*100;
        me.opacity = pMatChunk->std.opacity;
        me.selfIllum = pMatChunk->std.selfIllum;
        me.flags = pMatChunk->std.flags;
        
        me.Dyn_Bounce = pMatChunk->std.Dyn_Bounce;
        me.Dyn_StaticFriction = pMatChunk->std.Dyn_StaticFriction;
        me.Dyn_SlidingFriction = pMatChunk->std.Dyn_SlidingFriction;
				me.map_a = pMatChunk->std.tex_a;
        me.map_d = pMatChunk->std.tex_d;
        me.map_o = pMatChunk->std.tex_o;
        me.map_b = pMatChunk->std.tex_b;
        me.map_s = pMatChunk->std.tex_s;
        me.map_g = pMatChunk->std.tex_g;
        me.map_detail = pMatChunk->std.tex_c;
        me.map_e = pMatChunk->std.tex_rl;
        me.map_subsurf = pMatChunk->std.tex_subsurf;
        me.map_displ = pMatChunk->std.tex_det;

        me.nChildren = pMatChunk->multi.nChildren;

        return MCLE_Success;
        
			default:
				return MCLE_IgnoredType;
    }
  }
	break;
	default:
		return MCLE_UnknownVersion;
  }
}


// if the given string is null, assigns the "" to the pointer
void chkNullString (const char*&pszString)
{
	if (!pszString)
		pszString = "";
}

static void rtrim (char* szString)
{
	for (char* p = szString + strlen (szString) - 1; p >= szString && isspace(*p); --p)
		*p = '\0';
}

static void ltrim (const char*& szString)
{
	while (*szString && isspace(*szString))
		++szString;
}


CMatEntityNameTokenizer::CMatEntityNameTokenizer ():
	m_szMtlName (NULL),
	szName (""),
	szTemplate (""),
	szPhysMtl (""),
	nSortValue (0),
	bInvert (false)
	{
	}


void CMatEntityNameTokenizer::tokenize (const char* szMtlFullName)
{
	if (m_szMtlName)
	{
		free (m_szMtlName);
		m_szMtlName = NULL;
	}	
	if (!szMtlFullName)
		return;

	int nLen = (int)strlen(szMtlFullName);
	m_szMtlName = (char*)malloc (nLen+1);
	memcpy (m_szMtlName, szMtlFullName, nLen + 1);

	szName = NULL;
	szTemplate = NULL;
	szPhysMtl = NULL;
	nSortValue = 0;
	bInvert = false;

	// the state machine will parse the whole string
	enum StateEnum
	{
		kUnknown,
		kName,
		kTemplate,
		kPhysMtl,
		kIndex
	};

	StateEnum nState = kName;  // by default, the string begins with name
	this->szName = m_szMtlName;

	for (char* p = m_szMtlName; *p; ++p)
	{
		switch (*p)
		{
			case '(': // template name begins
				this->szTemplate = p+1;
				nState = kTemplate;
				*p = '\0';
				break;

			case '[': // render priority number begins
				*p = '\0';
				nState = kIndex;
				break;

			case '/':
				this->szPhysMtl = p+1;
				*p = '\0';
				nState = kPhysMtl;
				break;

		default:
		switch (nState)
		{
		case kName:
			switch (*p)
			{
			/*case ' ': // there are no spaces in the name
				*p = '\0';
				break;*/

			case ')':
			case ']':
#ifdef _CRY_ANIMATION_BASE_HEADER_
					g_GetLog()->LogError ("Invalid material name (unexpected closing bracket) \"%s\"", szMtlFullName);
#endif
				break;
			};
		break;

		case kTemplate:
			switch (*p)
			{
			case ')':
				nState = kUnknown;
				*p = '\0';
				break;
			}
		break;

		case kIndex:
			{
				switch (*p)
				{
				case ']':
					nState = kUnknown;
					break;
				
				case ' ':
					break;

				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					this->nSortValue *= 10;
					this->nSortValue += *p - '0';
					break;

				default:
					nState = kUnknown;
#ifdef _CRY_ANIMATION_BASE_HEADER_
					g_GetLog()->LogError ("Invalid material name (unexpected symbol in index field) \"%s\"", szMtlFullName);
#endif
					break;
				}
			}
		break;

		};
		break;
		}
	}

	// take into account hte old form $s_... of setting the template name
	// if there was no () template, then use this one (after _)
	if ((!this->szTemplate || !this->szTemplate[0]) &&
		this->szName[0] == '$' && tolower(this->szName[1]) == 's' && this->szName[2] == '_')
	{
		this->szTemplate = this->szName + 3;
	}

	// make sure all the strings get their pointers - if there's no name, then it will be an empty name
	chkNullString(this->szName);
	chkNullString(this->szTemplate);
	chkNullString(this->szPhysMtl);

	// a special case (one more) - template name preceded by # means (or meant) the inverted template
	if (this->szTemplate[0] == '#')
	{
		this->szTemplate++;
		this->bInvert = true;
	}

	// trim unneeded left and right leading spaces
	rtrim ((char*)this->szName);
	rtrim ((char*)this->szTemplate);
	rtrim ((char*)this->szPhysMtl);

	ltrim (this->szName);
	ltrim (this->szTemplate);
	ltrim (this->szPhysMtl);
}

CMatEntityNameTokenizer::~CMatEntityNameTokenizer ()
{
	if (m_szMtlName)
		free (m_szMtlName);
}

// operator that sorts the materials for rendering
bool CMatEntityNameTokenizer::operator < (const CMatEntityNameTokenizer& right)const
{
	if (this->nSortValue < right.nSortValue)
		return true;

	if (this->nSortValue > right.nSortValue)
		return false;

	int nComp = stricmp (this->szTemplate, right.szTemplate);

	if (nComp < 0)
		return true;
	if (nComp > 0)
		return false;

	nComp = stricmp (this->szName, right.szName);

	if (nComp < 0)
		return true;
	if (nComp > 0)
		return false;
	
	return false; // they're equal
}

// given the in-permutation, constructs the inverse out-permutation, so that:
// pOut[pIn[i]] == i
// pIn[pOut[i]] == i
void ConstructReversePermutation (const unsigned* pIn, unsigned* pOut, unsigned num)
{
	unsigned i;
#ifdef _DEBUG
	// we'll check the correctness of permutation by checking if there are duplicate entries in pIn
	// if there are no duplicate entries, according to Dirichle principle, there are no missed entries in pOut
	for (i = 0; i < num; ++i)
		pOut[i] = (unsigned)-1;
#endif

	for (i = 0; i < num; ++i)
	{
		assert (pIn[i] < num);
		assert ((int)pOut[pIn[i]] ==(unsigned)-1);
		pOut[pIn[i]] = i;
	}

}

// Remaps the materials according to the given permutation
// the permutation is perm[new index] == old index
void RemapMatEntities (MAT_ENTITY* pMtls, unsigned numMtls, unsigned* pPerm)
{
	MAT_ENTITY* pOldMtls = new MAT_ENTITY[numMtls];
	memcpy (pOldMtls, pMtls, sizeof(MAT_ENTITY)*numMtls);

	for (unsigned nNewMtl = 0; nNewMtl < numMtls; ++nNewMtl)
		memcpy (pMtls + nNewMtl, pOldMtls + pPerm[nNewMtl], sizeof(MAT_ENTITY));

	delete[]pOldMtls;	
}

// copies the matrix from  SBoneInitPosMatrix to Matrix
void copyMatrix (Matrix44& matDst, const SBoneInitPosMatrix& matSrc)
{
	for (unsigned i = 0; i < 4; ++i)
	{
		matDst(i,0) = matSrc[i][0];
		matDst(i,1) = matSrc[i][1];
		matDst(i,2) = matSrc[i][2];
	}
	matDst(0,3) = 0;
	matDst(1,3) = 0;
	matDst(2,3) = 0;
	matDst(3,3) = 1;
}

const char* getMtlType (unsigned nMtlType)
{
	switch (nMtlType)
	{
	case MTL_UNKNOWN:
		return "UNKNOWN";
	case MTL_STANDARD:
		return "STANDARD";
	case MTL_MULTI:
		return "MULTI";
	case MTL_2SIDED:
		return "2SIDED";
	default:
		return "#Unknown#";
	}
}

const char* getTexType (unsigned char nTexType)
{
	switch (nTexType)
	{
	case TEXMAP_AUTOCUBIC:
		return "TEXMAP_AUTOCUBIC";
	case TEXMAP_CUBIC:
		return "TEXMAP_CUBIC";
	case TEXMAP_ENVIRONMENT:
		return "TEXMAP_ENVIRONMENT";
	case TEXMAP_SCREENENVIRONMENT:
		return "!TEXMAP_SCREENENVIRONMENT(unsupported)!";
	default:
		return "#Unknown#";
	}
}

string getLightType (LightTypes nType)
{
	switch (nType)
	{
	case LT_OMNI:
		return "Omni";
	case LT_SPOT:
		return "Spot";
	case LT_DIRECT:
		return "Direct";
	case LT_AMBIENT:
		return "Ambient";
	default:
		{
			char szBuffer[32];
			printf (szBuffer, "Unknown(%d)", nType);
			return szBuffer;
		}
	}
}

string getMtlFlags (int nFlags)
{
	string strResult;
	if (nFlags & MTLFLAG_WIRE)
		strResult += "MTLFLAG_WIRE|";
	if (nFlags & MTLFLAG_2SIDED)
		strResult += "MTLFLAG_2SIDED|";
	if (nFlags & MTLFLAG_FACEMAP)
		strResult += "MTLFLAG_FACEMAP|";
	if (nFlags & MTLFLAG_FACETED)
		strResult += "MTLFLAG_FACETED|";
	if (nFlags & MTLFLAG_ADDITIVE)
		strResult += "MTLFLAG_ADDITIVE|";
	if (nFlags & MTLFLAG_SUBTRACTIVE)
		strResult += "MTLFLAG_SUBTRACTIVE|";

	if (!strResult.empty ())
		strResult.resize (strResult.length ()-1);

	return strResult;
}
