/*  Milkshape model loader
 *  Copyright (C) Joakim Kolsjö 2004-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 "model.h"
#include "mtexture.h"
#include <fstream>
#include <cstdlib>

#ifdef __APPLE__
#ifdef __MACH__
#define MACOSX
#define macosx
#endif
#endif

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;
	}
	
	int Model::Load(const char *pFile)
	{
		m_file = pFile;
		
		ifstream fmodel(pFile, ios::in|ios::binary);
		if(fmodel.fail()) {
			cout << "[Model::Load] Could not open model file: " << pFile << endl;
			return 1;
		}
	
		// Get filesize
		fmodel.seekg(0, ios::end);
		long fileSize = fmodel.tellg();
		fmodel.seekg(0, ios::beg);
	
		// Read file to memory and close
		byte* pMModelData = new byte[fileSize];
		fmodel.read(pMModelData, fileSize);
		fmodel.close();
	
		// Read header
		const byte* pPtr = pMModelData;
		MS3DHeader* pMS3DHeader = (MS3DHeader*)pPtr;
		pPtr += sizeof(MS3DHeader);
	
		string id = pMS3DHeader->id;
		if(id.substr(0, 10) != "MS3D000000") {
			cout << "[Model::Load] " << pFile << " is not a valid milkshape3d model." << endl;
			return 1;
		}
#ifdef macosx
#if BYTE_ORDER == BIG_ENDIAN
   pMS3DHeader->version = SWAPEND32( pMS3DHeader->version );
#endif
#endif		
		if(pMS3DHeader->version < 3 || pMS3DHeader->version > 4 ) {
			cout << "[Model::Load] Milkshape model " << pFile << "(" << pMS3DHeader->version << ") is not supported. Version 1.3 or 1.4 needed." << endl;
			return 1;
		}

		// 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]);

				// Remove any directory paths in name
				texture = texture.substr(texture.find_last_of("\\")+1, texture.size() - texture.find_last_of("\\")-1);
				texture = texture.substr(texture.find_last_of("/")+1, texture.size() - texture.find_last_of("/")-1);
				
				// Set texture identity to use
				texture = texture.substr(0, texture.size()-4);
			}
			
			m_textures.push_back(texture);
			pPtr += sizeof(MS3DMaterial);
		}
		
		// Remove temporary model data
		delete pMModelData;
		
#ifdef DEBUG
//		cout << "DEBUG: Model " << pFile << " loaded: " << m_nTriangles
//		<< " triangles in " << m_nMeshes << " mesh[es]." << endl;
#endif
		
		return 0;
	}
	
	int Model::LoadGLTextures()
	{
		MTexture* pMTexture = MTexture::GetPointer();
		for(int i = 0; i < (int)m_textures.size(); i++)
		{
			string identity = m_textures[i];
			if(identity.size()) {
				m_pMaterials[i].texture = pMTexture->GetTexture(identity.c_str());
				if(!m_pMaterials[i].texture) {
					cout << "[Model::LoadGLTextures] Could not get texture for "
						<< m_file << endl;
					//return 1;
				}
			}
		}
		return 0;
	}
	
	void Model::Draw()
	{
		// Save GL_TEXTURE_2D state
		//GLboolean bMTexture2D = glIsEnabled(GL_TEXTURE_2D);
		
		// 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);
					//glEnable(GL_TEXTURE_2D);
				}
				//else
					//glDisable(GL_TEXTURE_2D);
			}
			/*else
				glDisable(GL_TEXTURE_2D);*/
			
			// 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();
		}
		
		// Restore GL_TEXTURE_2D state
		/*if(bMTexture2D)
			glEnable(GL_TEXTURE_2D);
		else
			glDisable(GL_TEXTURE_2D);*/
	}
}
