#include <fstream>
#include <iostream>
#include <string.h>
#include <memory.h>
#include "3DModel.h"
#include "dma.h"
#include "pipeline.h"


using namespace std;


Model::Model()
{
	m_numMeshes = 0;
	m_pMeshes = 0;
	m_numMaterials = 0;
	m_pMaterials = 0;
	m_numTriangles = 0;
	m_pTriangles = 0;
	m_numVertices = 0;
	m_pVertices = 0;
	m_pTextures = 0;
	
	m_pStaticAddr = 0;
	m_World = Matrix4x4::IDENTITY;
	m_WVP = Matrix4x4::IDENTITY;
	m_bWorldLoaded = false;
}

Model::~Model()
{
	int i;
	for ( i = 0; i < m_numMeshes; i++ )
		if(m_pMeshes[i].m_pTriangleIndices) delete[] m_pMeshes[i].m_pTriangleIndices;
	for ( i = 0; i < m_numMaterials; i++ )
		if(m_pMaterials[i].m_pTextureFilename) delete[] m_pMaterials[i].m_pTextureFilename;

	m_numMeshes = 0;
	if ( m_pMeshes != 0 )
	{
		delete[] m_pMeshes;
		m_pMeshes = 0;
	}

	m_numMaterials = 0;
	if ( m_pMaterials != 0 )
	{
		delete[] m_pMaterials;
		m_pMaterials = 0;
	}

	m_numTriangles = 0;
	if ( m_pTriangles != 0 )
	{
		delete[] m_pTriangles;
		m_pTriangles = 0;
	}

	m_numVertices = 0;
	if ( m_pVertices != 0 )
	{
		delete[] m_pVertices;
		m_pVertices = 0;
	}
	
	m_numTextures = 0;
	if ( m_pTextures != 0 )
	{
		delete[] m_pTextures;
		m_pTextures = 0;
	}
	
	if(m_pStaticAddr != 0)
		delete [] m_pStaticAddr;
	
}


MilkshapeModel::MilkshapeModel()
{
}

MilkshapeModel::~MilkshapeModel()
{
}
 
//	MS3D STRUCTURES 


// byte-align structures
#ifdef _MSC_VER
#	pragma pack( push, packing )
#	pragma pack( 1 )
#	define PATH_MAX _MAX_PATH
#	define PACK_STRUCT
#elif defined( __GNUC__ )
#	define PACK_STRUCT	__attribute__((packed))
#else
#	error you must byte-align these structures with the appropriate compiler directives
#endif

typedef unsigned char byte;
typedef unsigned short word;

// File header
struct MS3DHeader
{
	char m_ID[10];
	int m_version;
} PACK_STRUCT;

// Vertex information
struct MS3DVertex
{
	byte m_flags;
	float m_vertex[3];
	char m_boneID;
	byte m_refCount;
} PACK_STRUCT;

// Triangle information
struct MS3DTriangle
{
	word m_flags;
	word m_vertexIndices[3];
	float m_vertexNormals[3][3];
	float m_s[3], m_t[3];
	byte m_smoothingGroup;
	byte m_groupIndex;
} PACK_STRUCT;

// Material information
struct MS3DMaterial
{
    char m_name[32];
    float m_ambient[4];
    float m_diffuse[4];
    float m_specular[4];
    float m_emissive[4];
    float m_shininess;	// 0.0f - 128.0f
    float m_transparency;	// 0.0f - 1.0f
    byte m_mode;	// 0, 1, 2 is unused now
    char m_texture[128];
    char m_alphamap[128];
} PACK_STRUCT;

//	Joint information
struct MS3DJoint
{
	byte m_flags;
	char m_name[32];
	char m_parentName[32];
	float m_rotation[3];
	float m_translation[3];
	word m_numRotationKeyframes;
	word m_numTranslationKeyframes;
} PACK_STRUCT;

// Keyframe data
struct MS3DKeyframe
{
	float m_time;
	float m_parameter[3];
} PACK_STRUCT;

// Default alignment
#ifdef _MSC_VER
#	pragma pack( pop, packing )
#endif

#undef PACK_STRUCT



bool MilkshapeModel::loadModelData( const char * filename, bool bTransparent, bool bWireFrame)
{
	ifstream inputFile( filename, ios::in | ios::binary | ios::nocreate);
	if(!inputFile)
	{
		cerr << "Couldn't open the model file." << endl;
		return false;
	}

	if ( inputFile.fail()) 
	{
    	cerr << "Couldn't open the model file." << endl;
		return false;
	}
	
	cout << "model File  -" << filename << "-  opened successfully" << endl;

	inputFile.seekg( 0, ios::end );
	long fileSize = inputFile.tellg();
	inputFile.seekg( 0, ios::beg );
	
	cout << "File size is " << fileSize << endl;
	

	byte * pBuffer = new byte[fileSize];
	inputFile.read( (char *)pBuffer, fileSize );
	inputFile.close();

	const byte *pPtr = pBuffer;
	MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
	pPtr += sizeof( MS3DHeader );

	if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 ) {
                cerr << "Not an MS3D file." << endl;
		return false;
        }

	if ( pHeader->m_version < 3 ) {
		cerr << "I know nothing about MS3D v1.2" << endl;
		return false;
        }

	int nVertices = *( word* )pPtr; 
	m_numVertices = nVertices;
	m_pVertices = new Vertex[nVertices];
	pPtr += sizeof( word );

	for ( int i = 0; i < nVertices; i++ )
	{
		MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
		m_pVertices[i].m_boneID = pVertex->m_boneID;
		memcpy( m_pVertices[i].m_location, pVertex->m_vertex, sizeof( float )*3 );
		pPtr += sizeof( MS3DVertex );
	}

	int nTriangles = *( word* )pPtr;
	m_numTriangles = nTriangles;
	m_pTriangles = new Triangle[nTriangles];
	pPtr += sizeof( word );

	for ( int i = 0; i < nTriangles; i++ )
	{
		MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
		int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle->m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
		//float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle->m_t[2] };
		float t[3] = { pTriangle->m_t[0], pTriangle->m_t[1], pTriangle->m_t[2] };
		memcpy( m_pTriangles[i].m_vertexNormals, pTriangle->m_vertexNormals, sizeof( float )*3*3 );
		memcpy( m_pTriangles[i].m_s, pTriangle->m_s, sizeof( float )*3 );
		memcpy( m_pTriangles[i].m_t, t, sizeof( float )*3 );
		memcpy( m_pTriangles[i].m_vertexIndices, vertexIndices, sizeof( int )*3 );
		pPtr += sizeof( MS3DTriangle );
	}

	int nGroups = *( word* )pPtr;
	m_numMeshes = nGroups;
	m_pMeshes = new Mesh[nGroups];
	m_pStaticAddr = new int[nGroups];
	pPtr += sizeof( word );
	for ( int i = 0; i < nGroups; i++ )
	{
		pPtr += sizeof( byte );	// flags
		pPtr += 32;				// name

		word nTriangles = *( word* )pPtr;
		pPtr += sizeof( word );
		int *pTriangleIndices = new int[nTriangles];
		for ( int j = 0; j < nTriangles; j++ )
		{
			pTriangleIndices[j] = *( word* )pPtr;
			pPtr += sizeof( word );
		}

		char materialIndex = *( char* )pPtr;
		
		cout << "Mesh " << i << " has material " << (int)materialIndex << endl;
		
		pPtr += sizeof( char );
	
		m_pMeshes[i].m_materialIndex = materialIndex;
		m_pMeshes[i].m_numTriangles = nTriangles;
		m_pMeshes[i].m_pTriangleIndices = pTriangleIndices;
	}

	int nMaterials = *( word* )pPtr;
	m_numMaterials = nMaterials;
	m_pMaterials = new Material[nMaterials];
	pPtr += sizeof( word );
	for ( int i = 0; i < nMaterials; i++ )
	{
		MS3DMaterial *pMaterial = ( MS3DMaterial* )pPtr;
		memcpy( m_pMaterials[i].m_ambient, pMaterial->m_ambient, sizeof( float )*4 );
		memcpy( m_pMaterials[i].m_diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
		memcpy( m_pMaterials[i].m_specular, pMaterial->m_specular, sizeof( float )*4 );
		memcpy( m_pMaterials[i].m_emissive, pMaterial->m_emissive, sizeof( float )*4 );
		m_pMaterials[i].m_shininess = pMaterial->m_shininess;
		
		int NameLength = strlen( pMaterial->m_texture  );
		
		//cout << "File Name Length is " << NameLength << endl;
		
		m_pMaterials[i].m_pTextureFilename = new char[NameLength + 1];
		
		if ( strncmp( pMaterial->m_texture, ".\\", 2 ) == 0 ) 
		{
			// MS3D 1.5.x relative path
			strcpy( m_pMaterials[i].m_pTextureFilename, pMaterial->m_texture + 2 );
		}
		else 
		{
			// MS3D 1.4.x or earlier - absolute path
			strcpy( m_pMaterials[i].m_pTextureFilename, pMaterial->m_texture );
		}
		
		
		cout << "The texture file is " << m_pMaterials[i].m_pTextureFilename << endl;
		//cout << "Texture file name located at address  " << (int)pMaterial->m_texture - (int)pBuffer<< endl;
		pPtr += sizeof( MS3DMaterial );
	}


	delete[] pBuffer;
	inputFile.close();
	
	if(!InitialiseTextures())
		return false;
	
	InitialiseDMA(bTransparent, bWireFrame);
	 
	return true;
}


bool MilkshapeModel::InitialiseDMA(bool bTransparent, bool bWireFrame)
{
	// Organise the render data according to the texture being used.
	for(int nTex = 0; nTex < m_numTextures; nTex++)
	{

		m_pStaticAddr[nTex] = VIFStaticDMA.GetPointer();
	
		VIFStaticDMA.Add32(FLUSH);			// Make sure VU1 isn't busy
		VIFStaticDMA.Add32(STCYCL(1,1));	// Unpack linearly, i.e. don't skip any spaces
		VIFStaticDMA.Add32(BASE(32));		// The double buffers start at VU1 address 32 (giving us 32 QW to store data that won't change)
		VIFStaticDMA.Add32(OFFSET(496));	// The size of each buffer.
	
		VIFStaticDMA.AddUnpack(V4_32, 0, 1);	// Unpack a QW of data that won't change
		VIFStaticDMA.AddVector(Pipeline.GetScaleVector());	// 0: The scales
	
	
		int iNumTrisPerChunk;
		iNumTrisPerChunk = (bWireFrame == true) ? 13 : 27;
		bool bFirst = true;
		
		//cout << "Number of meshes  " << m_numMeshes << endl;
		for(int nMesh = 0; nMesh < m_numMeshes; nMesh++)
		{
			
			Mesh & pMesh = m_pMeshes[nMesh];
			
			// Only add meshes which use this texture
			if(pMesh.m_materialIndex != nTex)continue;
			
			
			int iNumTris = pMesh.m_numTriangles;
			int iAbsoluteTri = 0;
			
			while(iNumTris > 0)
			{
				int iTrisThisChunk;
		
				if(iNumTris > iNumTrisPerChunk)
				{
					iTrisThisChunk = iNumTrisPerChunk;
					iNumTris -= iTrisThisChunk;
				}
				else
				{
					iTrisThisChunk = iNumTris;
					iNumTris -= iTrisThisChunk;
				}
				
				if(bWireFrame)
				{
					VIFStaticDMA.AddUnpack(V4_32, 0, iTrisThisChunk * 18 + 2, 1);
			
					VIFStaticDMA.Add128(iTrisThisChunk * 18);
			
					VIFStaticDMA.Add128(
						GS_GIFTAG_BATCH(iTrisThisChunk * 6, // NLOOP
								1,	// EOP
								1,	// PRE
								// GS_PRIM(    PRIM, IIP, TME, FGE,  ABE         , AA1, FST, CTXT, FIX)
								GS_PRIM(GS_LINE,  1,   0,   0 , bTransparent ,   0,   0,    0,   0),	// PRIM
								GIF_FLG_PACKED,
								GS_BATCH_3(GIF_REG_ST, GIF_REG_RGBAQ, GIF_REG_XYZ2)));
			
					for(int iTriangle = 0; iTriangle < iTrisThisChunk; iTriangle++)
					{
						Triangle & pTriangle = m_pTriangles[pMesh.m_pTriangleIndices[iAbsoluteTri++]];
			
						Vertex & pVert1 = m_pVertices[pTriangle.m_vertexIndices[0]];
						Vertex & pVert2 = m_pVertices[pTriangle.m_vertexIndices[1]];
						Vertex & pVert3 = m_pVertices[pTriangle.m_vertexIndices[2]];
			
						float * pNorm1 = pTriangle.m_vertexNormals[0];
						float * pNorm2 = pTriangle.m_vertexNormals[1];
						float * pNorm3 = pTriangle.m_vertexNormals[2];
			
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[0], pTriangle.m_t[0], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm1[0], pNorm1[1], pNorm1[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert1.m_location[0], pVert1.m_location[1], pVert1.m_location[2], 1));
					
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[1], pTriangle.m_t[1], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm2[0], pNorm1[1], pNorm2[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert2.m_location[0], pVert2.m_location[1], pVert2.m_location[2], 1));
						
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[1], pTriangle.m_t[1], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm2[0], pNorm2[1], pNorm2[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert2.m_location[0], pVert2.m_location[1], pVert2.m_location[2], 1));
			
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[2], pTriangle.m_t[2], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm3[0], pNorm3[1], pNorm3[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert3.m_location[0], pVert3.m_location[1], pVert3.m_location[2], 1));
						
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[2], pTriangle.m_t[2], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm3[0], pNorm3[1], pNorm3[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert3.m_location[0], pVert3.m_location[1], pVert3.m_location[2], 1));
						
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[0], pTriangle.m_t[0], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm1[0], pNorm1[1], pNorm1[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert1.m_location[0], pVert1.m_location[1], pVert1.m_location[2], 1));
					}
				}
				else //solid model
				{
					VIFStaticDMA.AddUnpack(V4_32, 0, iTrisThisChunk * 9 + 2, 1);
		
					VIFStaticDMA.Add128(iTrisThisChunk * 9);
			
					VIFStaticDMA.Add128(
						GS_GIFTAG_BATCH(iTrisThisChunk * 3, // NLOOP
								1,	// EOP
								1,	// PRE
								// GS_PRIM(    PRIM, IIP, TME, FGE,  ABE         , AA1, FST, CTXT, FIX)
								GS_PRIM(GS_TRIANGLE,  1,   1,   0 , bTransparent ,   0,   0,    0,   0),	// PRIM
								GIF_FLG_PACKED,
								GS_BATCH_3(GIF_REG_ST, GIF_REG_RGBAQ, GIF_REG_XYZ2)));
			
					for(int iTriangle = 0; iTriangle < iTrisThisChunk; iTriangle++)
					{
						Triangle & pTriangle = m_pTriangles[pMesh.m_pTriangleIndices[iAbsoluteTri++]];
			
						Vertex & pVert1 = m_pVertices[pTriangle.m_vertexIndices[0]];
						Vertex & pVert2 = m_pVertices[pTriangle.m_vertexIndices[1]];
						Vertex & pVert3 = m_pVertices[pTriangle.m_vertexIndices[2]];
			
						float * pNorm1 = pTriangle.m_vertexNormals[0];
						float * pNorm2 = pTriangle.m_vertexNormals[1];
						float * pNorm3 = pTriangle.m_vertexNormals[2];
			
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[0], pTriangle.m_t[0], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm1[0], pNorm1[1], pNorm1[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert1.m_location[0], pVert1.m_location[1], pVert1.m_location[2], 1));
					
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[1], pTriangle.m_t[1], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm2[0], pNorm1[1], pNorm2[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert2.m_location[0], pVert2.m_location[1], pVert2.m_location[2], 1));
			
						VIFStaticDMA.AddVector(Vector4(pTriangle.m_s[2], pTriangle.m_t[2], 1, 0));
						VIFStaticDMA.AddVector(Vector4(pNorm3[0], pNorm3[1], pNorm3[2], 0));
						VIFStaticDMA.AddVector(Vector4(pVert3.m_location[0], pVert3.m_location[1], pVert3.m_location[2], 1));
						
					}
					
				}
				
				if(bFirst)
				{
					VIFStaticDMA.Add32(MSCALL(0));
					bFirst = false;
				}
				else
					VIFStaticDMA.Add32(MSCNT);
			}
		}
	

		VIFStaticDMA.Add32(FLUSH);

		VIFStaticDMA.DMARet();
	}
	return true;
}



void Model::SetWorldMatrix(const Matrix4x4 & matWorld)
{
	m_World = matWorld;
	m_WVP = m_World * Pipeline.GetViewProjection();
	m_bWorldLoaded = true;
	}


void MilkshapeModel::Render(void)
{
	assert(m_bWorldLoaded && m_pStaticAddr);

	
	for(int nTex = 0; nTex < m_numTextures; nTex++)
	{	
		// Wait for all to be idle in VU1 land
		// Before uploading the data
		VIFDynamicDMA.Add32(FLUSH);
		
		m_pTextures[nTex].Select();
		// Upload the matrices in the dynamic buffer
		// because they can change every frame.
		VIFDynamicDMA.AddUnpack(V4_32, 1, 16);
		VIFDynamicDMA.AddMatrix(Pipeline.GetLightDirs());
		VIFDynamicDMA.AddMatrix(Pipeline.GetLightCols());
		VIFDynamicDMA.AddMatrix(m_WVP);
		VIFDynamicDMA.AddMatrix(m_World);
		// Call the packet with the static data that doesn't change per frame.
		VIFDynamicDMA.DMACall(m_pStaticAddr[nTex]);
	}
}

bool  MilkshapeModel::InitialiseTextures(void)
{
	m_numTextures = m_numMaterials;
	
	assert(m_numTextures < 3); // only two materials (textures) supported per model
	
	cout << "Number of textures  " << m_numTextures << endl;
	
	m_pTextures = new CTexture[m_numTextures];
	
	//cout << "Texture file 0  " <<   m_pMaterials[0].m_pTextureFilename << endl;
	//cout << "Texture file 1  " <<   m_pMaterials[1].m_pTextureFilename << endl;
	
	for(int nTex = 0; nTex < m_numTextures; nTex++)
	{
		// Load the model's textures into static memeory.
		if(!m_pTextures[nTex].LoadBitmap(m_pMaterials[nTex].m_pTextureFilename))
		{
			cout << "Can't load texture file " <<   m_pMaterials[nTex].m_pTextureFilename << endl;
			return false;
		}
		cout << "Texture file loaded -> " <<   m_pMaterials[nTex].m_pTextureFilename << endl;
		
	}
	
	return true;	
}

void MilkshapeModel::UploadTextures(void)
{
	assert(m_numTextures < 3); // only two materials (textures) supported per model
	
	for(int nTex = 0; nTex < m_numTextures; nTex++)
	{
		// Load the model's textures into GS memeory.
		m_pTextures[nTex].Upload(nTex == 0 ? TEXBUF480 : TEXBUF496);
	}
	
}