#include "3DSLoader.h"
#include "GFDebugHandler.h"

CLoad3DS::CLoad3DS()
{
	m_CurrentChunk = new tChunk;				
	m_TempChunk = new tChunk;					
}

bool CLoad3DS::Import3DS(MODEL3DS *pModel, char *strFileName)
{
	char strMessage[255] = {0};

	m_FilePointer = fopen(strFileName, "rb");

	if(!m_FilePointer) 
	{
		return 0;
	}

	ReadChunk(m_CurrentChunk);

	if (m_CurrentChunk->ID != PRIMARY)
	{
		return 0;
	}

	ProcessNextChunk(pModel, m_CurrentChunk);
	ComputeNormals(pModel);
	CleanUp();

	return 1;
}

void CLoad3DS::CleanUp()
{

	fclose(m_FilePointer);					
	delete m_CurrentChunk;						
	delete m_TempChunk;							
}

void CLoad3DS::ProcessNextChunk(MODEL3DS *pModel, tChunk *pPreviousChunk)
{
	OBJECT3DS newObject;
	ZeroMemory(&newObject, sizeof(OBJECT3DS));
	OBJMATERIALINFO newTexture;
	ZeroMemory(&newTexture, sizeof(OBJMATERIALINFO));
	unsigned int version = 0;
	int buffer[50000] = {0};

	m_CurrentChunk = new tChunk;					

	while (pPreviousChunk->bytesRead < pPreviousChunk->length)
	{
		ReadChunk(m_CurrentChunk);

		switch (m_CurrentChunk->ID)
		{
		case VERSION:					

			m_CurrentChunk->bytesRead += (UINT)fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);

			if (version > 0x03)
				MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK);
			break;

		case OBJECTINFO:						
			
			ReadChunk(m_TempChunk);

			m_TempChunk->bytesRead += (UINT)fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);

			m_CurrentChunk->bytesRead += m_TempChunk->bytesRead;

			ProcessNextChunk(pModel, m_CurrentChunk);
			break;

		case MATERIAL:						

			pModel->numOfMaterials++;

			pModel->pMaterials.push_back(newTexture);

			ProcessNextMaterialChunk(pModel, m_CurrentChunk);
			break;

		case OBJECT:					
				
			pModel->numOfObjects++;
		
			pModel->pObject.push_back(newObject);
			
			memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(OBJECT3DS));

			m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName);
			
			ProcessNextObjectChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk);
			break;

		case EDITKEYFRAME:

			m_CurrentChunk->bytesRead += (UINT)fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
			break;

		default: 
			m_CurrentChunk->bytesRead += (UINT)fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
			break;
		}

		pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;
	}

	delete m_CurrentChunk;
	m_CurrentChunk = pPreviousChunk;
}

void CLoad3DS::ProcessNextObjectChunk(MODEL3DS *pModel, OBJECT3DS *pObject, tChunk *pPreviousChunk)
{
	int buffer[50000] = {0};				

	m_CurrentChunk = new tChunk;

	while (pPreviousChunk->bytesRead < pPreviousChunk->length)
	{
		ReadChunk(m_CurrentChunk);

		switch (m_CurrentChunk->ID)
		{
		case OBJECT_MESH:					
		
			ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk);
			break;

		case OBJECT_VERTICES:		
			ReadVertices(pObject, m_CurrentChunk);
			break;

		case OBJECT_FACES:				
			ReadVertexIndices(pObject, m_CurrentChunk);
			break;

		case OBJECT_MATERIAL:			
			
			ReadObjectMaterial(pModel, pObject, m_CurrentChunk);			
			break;

		case OBJECT_UV:						

			ReadUVCoordinates(pObject, m_CurrentChunk);
			break;

		default:  

			m_CurrentChunk->bytesRead += (UINT)fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
			break;
		}


		pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;
	}

	delete m_CurrentChunk;
	m_CurrentChunk = pPreviousChunk;
}

void CLoad3DS::ProcessNextMaterialChunk(MODEL3DS *pModel, tChunk *pPreviousChunk)
{
	int buffer[50000] = {0};	

	m_CurrentChunk = new tChunk;

	while (pPreviousChunk->bytesRead < pPreviousChunk->length)
	{
		ReadChunk(m_CurrentChunk);

		switch (m_CurrentChunk->ID)
		{
		case MATNAME:

			m_CurrentChunk->bytesRead += (UINT)fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);						
			break;

		case MATDIFFUSE:

			ReadColorChunk(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk);
			break;
		
		case MATMAP:							
		
			ProcessNextMaterialChunk(pModel, m_CurrentChunk);
			break;

		case MATMAPFILE:					

			m_CurrentChunk->bytesRead += (UINT)fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
			break;
		
		default:  

			m_CurrentChunk->bytesRead += (UINT)fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
			break;
		}

		pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;
	}

	delete m_CurrentChunk;
	m_CurrentChunk = pPreviousChunk;
}

void CLoad3DS::ReadChunk(tChunk *pChunk)
{
	pChunk->bytesRead = (UINT)fread(&pChunk->ID, 1, 2, m_FilePointer);
	pChunk->bytesRead += (UINT)fread(&pChunk->length, 1, 4, m_FilePointer);
}

int CLoad3DS::GetString(char *pBuffer)
{
	int index = 0;

	fread(pBuffer, 1, 1, m_FilePointer);

	while (*(pBuffer + index++) != 0) 
	{
		fread(pBuffer + index, 1, 1, m_FilePointer);
	}

	return (int)strlen(pBuffer) + 1;
}

void CLoad3DS::ReadColorChunk(OBJMATERIALINFO *pMaterial, tChunk *pChunk)
{
	BYTE tmpColor[3];

	ReadChunk(m_TempChunk);

	m_TempChunk->bytesRead += (UINT)fread(tmpColor, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);

	pMaterial->color.r = tmpColor[0];
	pMaterial->color.g = tmpColor[1];
	pMaterial->color.b = tmpColor[2];

	pChunk->bytesRead += m_TempChunk->bytesRead;
}

void CLoad3DS::ReadVertexIndices(OBJECT3DS *pObject, tChunk *pPreviousChunk)
{
	unsigned short index = 0;				

	pPreviousChunk->bytesRead += (UINT)fread(&pObject->numOfFaces, 1, 2, m_FilePointer);

	pObject->pFaces = new OBJFACE [pObject->numOfFaces];
	memset(pObject->pFaces, 0, sizeof(OBJFACE) * pObject->numOfFaces);

	for(int i = 0; i < pObject->numOfFaces; i++)
	{
		for(int j = 0; j < 4; j++)
		{
			pPreviousChunk->bytesRead += (UINT)fread(&index, 1, sizeof(index), m_FilePointer);

			if(j < 3)
			{
			
				pObject->pFaces[i].vertIndex[j] = index;
			}
		}
	}
}

void CLoad3DS::ReadUVCoordinates(OBJECT3DS *pObject, tChunk *pPreviousChunk)
{
	pPreviousChunk->bytesRead += (UINT)fread(&pObject->numTexVertex, 1, 2, m_FilePointer);
	pObject->pTexVerts = new D3DXVECTOR2 [pObject->numTexVertex];
	pPreviousChunk->bytesRead += (UINT)fread(pObject->pTexVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);
}

void CLoad3DS::ReadVertices(OBJECT3DS *pObject, tChunk *pPreviousChunk)
{
	pPreviousChunk->bytesRead += (UINT)fread(&(pObject->numOfVerts), 1, 2, m_FilePointer);

	pObject->pVerts = new D3DXVECTOR3 [pObject->numOfVerts];
	memset(pObject->pVerts, 0, sizeof(D3DXVECTOR3) * pObject->numOfVerts);

	pPreviousChunk->bytesRead += (UINT)fread(pObject->pVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);

	for(int i = 0; i < pObject->numOfVerts; i++)
	{
		float fTempY = pObject->pVerts[i].y;
		pObject->pVerts[i].y = pObject->pVerts[i].z;
		pObject->pVerts[i].z = -fTempY;
	}
}

void CLoad3DS::ReadObjectMaterial(MODEL3DS *pModel, OBJECT3DS *pObject, tChunk *pPreviousChunk)
{
	char strMaterial[255] = {0};			
	int buffer[50000] = {0};				

	pPreviousChunk->bytesRead += GetString(strMaterial);

	for(int i = 0; i < pModel->numOfMaterials; i++)
	{
		if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0)
		{
			pObject->materialID = i;

			if(strlen(pModel->pMaterials[i].strFile) > 0) 
			{
				pObject->bHasTexture = true;
			}	
			break;
		}
		else
		{
			pObject->materialID = -1;
		}
	}

	pPreviousChunk->bytesRead += (UINT)fread(buffer, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);
}			

void CLoad3DS::ComputeNormals(MODEL3DS *pModel)
{
	D3DXVECTOR3 vVector1, vVector2, vNormal, vPoly[3];
	if(pModel->numOfObjects <= 0) return;

	for(int index = 0; index < pModel->numOfObjects; index++)
	{
		OBJECT3DS *pObject = &(pModel->pObject[index]);

		D3DXVECTOR3 *pNormals		= new D3DXVECTOR3 [pObject->numOfFaces];
		D3DXVECTOR3 *pTempNormals	= new D3DXVECTOR3 [pObject->numOfFaces];
		pObject->pNormals			= new D3DXVECTOR3 [pObject->numOfVerts];

		for(int i=0; i < pObject->numOfFaces; i++)
		{												
			vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]];
			vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]];
			vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]];

			vVector1 = vPoly[0] - vPoly[2];			
			vVector2 = vPoly[2] - vPoly[1];			

			D3DXVec3Cross(&vNormal, &vVector1, &vVector2);			
			pTempNormals[i] = vNormal;	
		
			D3DXVec3Normalize(&vNormal, &vNormal);

			pNormals[i] = vNormal;						
		}

		D3DXVECTOR3 vSum(0, 0, 0);
		D3DXVECTOR3 vZero = vSum;
		int shared=0;

		for (i = 0; i < pObject->numOfVerts; i++)			
		{
			for (int j = 0; j < pObject->numOfFaces; j++)	
			{												
				if (pObject->pFaces[j].vertIndex[0] == i || 
					pObject->pFaces[j].vertIndex[1] == i || 
					pObject->pFaces[j].vertIndex[2] == i)
				{
					vSum += pTempNormals[j];			
					shared++;							
				}
			}      
			
			pObject->pNormals[i] = vSum / (float)(-shared);

			D3DXVec3Normalize(&(pObject->pNormals[i]), &(pObject->pNormals[i]));

			vSum = vZero;								
			shared = 0;									
		}
	
		delete [] pTempNormals;
		delete [] pNormals;
	}
}
