/*  DS
 *  Copyright (C) Joakim Kolsjö and Anders Asplund 2005
 *	This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 
 */ 
 
/* Code derived from Brett Porter's lesson31 at nehe. */
 
#include "load_ms3d.h"

using namespace std;

#define SWAPEND16(x) ((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF))
#define SWAPEND32(x) ((((x) & 0xFF) << 24) | ((((x) >> 8) & 0xFF) << 16) | \
                      ((((x) >> 16) & 0xFF) << 8) | (((x) >> 24) & 0xFF))

float swapendf32(float floatValue)
{
   union mapUnion { unsigned char bytes[ 4 ]; float floatVal; } *mapping, newMapping;
   mapping = (union mapUnion *) &floatValue;
   newMapping.bytes[ 0 ] = mapping->bytes[ 3 ];
   newMapping.bytes[ 1 ] = mapping->bytes[ 2 ];
   newMapping.bytes[ 2 ] = mapping->bytes[ 1 ];
   newMapping.bytes[ 3 ] = mapping->bytes[ 0 ];
   return newMapping.floatVal;
}

namespace DS
{
	Model::Model()
	{
		m_nMeshes = 0;
		m_pMeshes = 0;
		m_nTriangles = 0;
		m_pTriangles = 0;
		m_nMaterials = 0;
		m_pMaterials = 0;
		m_pVertices = 0;
		m_nVertices = 0;
	}	
	
	Model::~Model()
	{
		for(int i = 0; i < m_nMeshes; i++)
			delete m_pMeshes[i].ptindices;

		if(m_pVertices)
			delete m_pVertices;
		if(m_pTriangles)
			delete m_pTriangles;
		if(m_pMaterials)
			delete m_pMaterials;
		if(m_pMeshes)
			delete m_pMeshes;
	}
	
	void Model::Load(const char *pFile, MemFile* pMemFile)
	{
		static char* fn = "[Model::Load]";
		m_file = pFile;

		// Read header
		const byte* pPtr = pMemFile->GetData();
		MS3DHeader* pMS3DHeader = (MS3DHeader*)pPtr;
		pPtr += sizeof(MS3DHeader);
		
		string id = pMS3DHeader->id;
		if(id.substr(0, 10) != "MS3D000000")
			throw Exception(fn, (string)pFile + " is not a valid milkshape3d model.");
		
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
   pMS3DHeader->version = SWAPEND32( pMS3DHeader->version );
#endif
#endif	

		// Causes some loading problems on "torus.ms3d" on osx, temp removed
		//if(pMS3DHeader->version < 3 || pMS3DHeader->version > 4 )
			//throw Exception(fn, (string)"Milkshape model " + pFile +
				//"(" + itoa(pMS3DHeader->version) + ") is not supported. Version 1.3 or 1.4 needed.");
	
		// Read vertices
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
		m_nVertices = SWAPEND16(*(word *) pPtr);
#endif
#else
		m_nVertices = *(word*)pPtr;
#endif
		m_pVertices = new Vertex[m_nVertices];
		pPtr += sizeof(word);
		
		for(int i = 0; i < m_nVertices; i++)
		{
			MS3DVertex* pMS3DVertex = (MS3DVertex*)pPtr;
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
			for(int j = 0; j < 3; j++)
				m_pVertices[i].position[j] = swapendf32(pMS3DVertex->vertex[j]);
#endif
#else
			for(int j = 0; j < 3; j++)
				m_pVertices[i].position[j] = pMS3DVertex->vertex[j];
#endif
			
			pPtr += sizeof(MS3DVertex);
		}
	
		// Read triangles
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
		m_nTriangles = SWAPEND16( *(word *) pPtr );
#endif
#else
		m_nTriangles = *(word*)pPtr;
#endif
		m_pTriangles = new Triangle[m_nTriangles];
		pPtr += sizeof(word);
	
		for(int i = 0; i < m_nTriangles; i++)
		{
			MS3DTriangle* pMS3DTriangle = (MS3DTriangle*)pPtr;
			
			// Read texture cordinates, vertex indices and vertex normals
			for(int j = 0; j < 3; j++) {
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
				m_pTriangles[i].vindices[j] = SWAPEND16(pMS3DTriangle->vindices[j]);
				m_pTriangles[i].tex_t[j] = 1 - swapendf32(pMS3DTriangle->tex_t[j]);
				m_pTriangles[i].tex_s[j] = swapendf32(pMS3DTriangle->tex_s[j]);
				for(int k = 0; k < 3; k++)
					m_pTriangles[i].vnormals[j][k] = swapendf32(pMS3DTriangle->vnormals[j][k]);
#endif
#else
				m_pTriangles[i].vindices[j] = pMS3DTriangle->vindices[j];
				m_pTriangles[i].tex_t[j] = 1 - pMS3DTriangle->tex_t[j];
				m_pTriangles[i].tex_s[j] = pMS3DTriangle->tex_s[j];
				for(int k = 0; k < 3; k++)
					m_pTriangles[i].vnormals[j][k] = pMS3DTriangle->vnormals[j][k];
#endif
			}
			
			pPtr += sizeof(MS3DTriangle);
		}
	
		// Read meshes
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
		m_nMeshes = SWAPEND16( *(word *) pPtr );
#endif
#else
		m_nMeshes = *(word*)pPtr;
#endif
		m_pMeshes = new Mesh[m_nMeshes];
		pPtr += sizeof(word);
		
		for(int i = 0; i < m_nMeshes; i++ )
		{
			pPtr += sizeof(byte);	// Skip flags
			pPtr += 32;				// Skip name
	
			// Read number of triangles
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
	m_pMeshes[i].nTriangles = SWAPEND16( *(word *) pPtr );
#endif
#else
		m_pMeshes[i].nTriangles = *(word*)pPtr;
#endif
			pPtr += sizeof(word);
			
			// Read triangle indices
			m_pMeshes[i].ptindices = new int[m_pMeshes[i].nTriangles];
			for(int j = 0; j < m_pMeshes[i].nTriangles; j++)
			{
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
		m_pMeshes[i].ptindices[j]  = SWAPEND16( *(word *) pPtr );
#endif
#else
		m_pMeshes[i].ptindices[j]  = *(word*)pPtr;
#endif
				pPtr += sizeof(word);
			}

			// Read material index number
			m_pMeshes[i].material = *(char*)pPtr;
			pPtr += sizeof(char);
		}
	
		// Read materials/textures
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
		m_nMaterials  = SWAPEND16( *(word *) pPtr );
#endif
#else
		m_nMaterials  = *(word*)pPtr;
#endif
		m_pMaterials = new Material[m_nMaterials];
		pPtr += sizeof(word);
		
		for(int i = 0; i < m_nMaterials; i++)
		{
			MS3DMaterial *pMS3DMaterial = (MS3DMaterial*)pPtr;
			
			// Read light properties
			m_pMaterials[i].shininess = pMS3DMaterial->shininess;
			for(int j = 0; j < 4; j++)
			{
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
	
				m_pMaterials[i].ambient[j] = swapendf32(pMS3DMaterial->ambient[j]);
				m_pMaterials[i].diffuse[j] = swapendf32(pMS3DMaterial->diffuse[j]);
				m_pMaterials[i].specular[j] = swapendf32(pMS3DMaterial->specular[j]);
				m_pMaterials[i].emissive[j] = swapendf32(pMS3DMaterial->emissive[j]);
#endif
#else
				m_pMaterials[i].ambient[j] = pMS3DMaterial->ambient[j];
				m_pMaterials[i].diffuse[j] = pMS3DMaterial->diffuse[j];
				m_pMaterials[i].specular[j] = pMS3DMaterial->specular[j];
				m_pMaterials[i].emissive[j] = pMS3DMaterial->emissive[j];
#endif
			}
		
			// Get texture identity for this material
			string texture = pMS3DMaterial->texture;
			m_pMaterials[i].texture = 0;
			
			if(texture.size())
			{
				// Lowcase the filename
				for(int j = 0; j < (int)texture.size(); j++)
					texture[j] = tolower(texture[j]);
				
				// Add .png
				texture = texture.substr(0, texture.size()-4) + ".png";
				
				// Add texture path
				texture = "textures/" + texture;
			}
			
			m_textures.push_back(texture);
			pPtr += sizeof(MS3DMaterial);
		}
	}
	
	void Model::MakeGL(TextureLoader* pTextureLoader)
	{
		static char* fn = "[Model::MakeGL]";
		for(int i = 0; i < (int)m_textures.size(); i++)
		{
			if(m_textures[i].size()) {
				m_pMaterials[i].texture = pTextureLoader->GetTexture(m_textures[i].c_str());
				if(!m_pMaterials[i].texture)
					cout << fn << " Could not get texture for " << m_file << "." << endl;
			}
		}
	}
	
	void Model::Draw()
	{
		// Draw model
		for(int i = 0; i < m_nMeshes; i++)
		{
			// Set material for the current mesh
			int mindex = m_pMeshes[i].material;
			
			// Only use material if it's loaded
			if(mindex >= 0)
			{
				glMaterialfv(GL_FRONT, GL_AMBIENT, m_pMaterials[mindex].ambient);
				glMaterialfv(GL_FRONT, GL_DIFFUSE, m_pMaterials[mindex].diffuse);
				glMaterialfv(GL_FRONT, GL_SPECULAR, m_pMaterials[mindex].specular);
				glMaterialfv(GL_FRONT, GL_EMISSION, m_pMaterials[mindex].emissive);
				glMaterialf(GL_FRONT, GL_SHININESS, m_pMaterials[mindex].shininess);

				// Only use texture if it's loaded
				if(m_pMaterials[mindex].texture)
					glBindTexture(GL_TEXTURE_2D, m_pMaterials[mindex].texture);
			}
			
			// Draw the triangles of the current mesh
			glBegin(GL_TRIANGLES);
			{			
				for(int j = 0; j < m_pMeshes[i].nTriangles; j++)
				{	
					int tindex = m_pMeshes[i].ptindices[j];
					Triangle* pTri = &m_pTriangles[tindex];
					
					for(int k = 0; k < 3; k++)
					{	
						int vindex = pTri->vindices[k];
						glNormal3fv(pTri->vnormals[k]);
						glTexCoord2f(pTri->tex_s[k], pTri->tex_t[k]);
						glVertex3fv(m_pVertices[vindex].position);
					}
				}
			}
			glEnd();
		}
	}
}
