/* -----------------------------------------------------------------------------

   PicoModel Library

   Copyright (c) 2002, Randy Reddig & seaw0lf
   All rights reserved.

   Redistribution and use in source and binary forms, with or without modification,
   are permitted provided that the following conditions are met:

   Redistributions of source code must retain the above copyright notice, this list
   of conditions and the following disclaimer.

   Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.

   Neither the names of the copyright holders nor the names of its contributors may
   be used to endorse or promote products derived from this software without
   specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

   ----------------------------------------------------------------------------- */

/* dependencies */
#include "picointernal.h"
#include "globaldefs.h"
#include "assert.h"

/* disable warnings */
#if GDEF_COMPILER_MSVC
#pragma warning( disable:4100 )		/* unref param */
#endif

/* remarks:
 * - loader seems stable
 * todo:
 * - fix uv coordinate problem
 * - check for buffer overflows ('bufptr' accesses)
 */
/* uncomment when debugging this module */
 #define DEBUG_PM_MS3D
 #define DEBUG_PM_MS3D_EX

/* ms3d limits */
const int MS3D_MAX_VERTS      = 8192;
const int MS3D_MAX_TRIS       = 16384;
const int MS3D_MAX_GROUPS     = 128;
const int MS3D_MAX_MATERIALS  = 128;
const int MS3D_MAX_JOINTS     = 128;
const int MS3D_MAX_KEYFRAMES  = 216;

/* ms3d flags */
const int MS3D_SELECTED       = 1;
const int MS3D_HIDDEN         = 2;
const int MS3D_SELECTED2      = 4;
const int MS3D_DIRTY          = 8;

/* this freaky loader needs byte alignment */
#pragma pack(push, 1)

/* ms3d header */
typedef struct SMsHeader
{
	char magic[10];
	int version;
}
TMsHeader;

/* ms3d vertex */
typedef struct SMsVertex
{
	unsigned char flags;                /* sel, sel2, or hidden */
	float xyz[3];
	char boneID;                        /* -1 means 'no bone' */
	unsigned char refCount;
}
TMsVertex;

/* ms3d triangle */
typedef struct SMsTriangle
{
	unsigned short flags;               /* sel, sel2, or hidden */
	unsigned short vertexIndices[3];
	float vertexNormals[3][3];
	float s[3];
	float t[3];
	unsigned char smoothingGroup;       /* 1 - 32 */
	unsigned char groupIndex;
}
TMsTriangle;

/* ms3d material */
typedef struct SMsMaterial
{
	char name[32];
	float ambient[4];
	float diffuse[4];
	float specular[4];
	float emissive[4];
	float shininess;                    /* range 0..128 */
	float transparency;                 /* range 0..1 */
	unsigned char mode;
	char texture [128];                 /* texture.bmp */
	char alphamap[128];                 /* alpha.bmp */
}
TMsMaterial;

// ms3d group (static part)
// followed by a variable size block (see below)
typedef struct SMsGroup
{
	unsigned char flags;                // sel, hidden
	char name[32];
	unsigned short numTriangles;
/*
    unsigned short	triangleIndices[ numTriangles ];
    char			materialIndex;		// -1 means 'no material'
 */
}
TMsGroup;

// ms3d joint
typedef struct SMsJoint
{
	unsigned char flags;
	char name[32];
	char parentName[32];
	float rotation[3];
	float translation[3];
	unsigned short numRotationKeyframes;
	unsigned short numTranslationKeyframes;
}
TMsJoint;

// ms3d keyframe
typedef struct SMsKeyframe
{
	float time;
	float parameter[3];
}
TMsKeyframe;

/* restore previous data alignment */
#pragma pack(pop)

/* _ms3d_canload:
 *	validates a milkshape3d model file.
 */
static int _ms3d_canload( PM_PARAMS_CANLOAD ){
	const TMsHeader *hdr;


	/* sanity check */
	if ( (size_t) bufSize < sizeof( TMsHeader ) ) {
		return PICO_PMV_ERROR_SIZE;
	}

	/* get ms3d header */
	hdr = (const TMsHeader *)buffer;

	/* check ms3d magic */
	if ( strncmp( hdr->magic,"MS3D000000",10 ) != 0 ) {
		return PICO_PMV_ERROR_IDENT;
	}

	/* check ms3d version */
	if ( _pico_little_long( hdr->version ) < 3 ||
		 _pico_little_long( hdr->version ) > 4 ) {
		_pico_printf( PICO_ERROR,"MS3D file ignored. Only MS3D 1.3 and 1.4 is supported." );
		return PICO_PMV_ERROR_VERSION;
	}
	/* file seems to be a valid ms3d */
	return PICO_PMV_OK;
}

static unsigned char *GetWord( unsigned char *bufptr, int *out ){
	assert( bufptr != NULL );
	*out = _pico_little_short( *(unsigned short *)bufptr );
	return( bufptr + 2 );
}

/* _ms3d_load:
 *	loads a milkshape3d model file.
 */
static picoModel_t *_ms3d_load( PM_PARAMS_LOAD ){
	picoModel_t    *model;
	unsigned char  *bufptr, *bufptr0;
	int shaderRefs[ MS3D_MAX_GROUPS ];
	int numGroups;
	int numMaterials;
//	unsigned char  *ptrToGroups;
	int numVerts;
	unsigned char  *ptrToVerts;
	int numTris;
	unsigned char  *ptrToTris;
	int i,k,m;

	/* create new pico model */
	model = PicoNewModel();
	if ( model == NULL ) {
		return NULL;
	}

	/* do model setup */
	PicoSetModelFrameNum( model, frameNum );
	PicoSetModelName( model, fileName );
	PicoSetModelFileName( model, fileName );

	bufptr0 = bufptr = (picoByte_t*) _pico_alloc( bufSize );

	if ( !bufptr )
	{
		return NULL;
	}

	memcpy( bufptr, buffer, bufSize );
	/* skip header */
	bufptr += sizeof( TMsHeader );

	/* get number of vertices */
	bufptr = GetWord( bufptr,&numVerts );
	ptrToVerts = bufptr;

#ifdef DEBUG_PM_MS3D
	printf( "NumVertices: %d\n",numVerts );
#endif
	/* swap verts */
	for ( i = 0; i < numVerts; i++ )
	{
		TMsVertex *vertex;
		vertex = (TMsVertex *)bufptr;
		bufptr += sizeof( TMsVertex );

		vertex->xyz[ 0 ] = _pico_little_float( vertex->xyz[ 0 ] );
		vertex->xyz[ 1 ] = _pico_little_float( vertex->xyz[ 1 ] );
		vertex->xyz[ 2 ] = _pico_little_float( vertex->xyz[ 2 ] );

#ifdef DEBUG_PM_MS3D_EX_
		printf( "Vertex: x: %f y: %f z: %f\n",
				msvd[i]->vertex[0],
				msvd[i]->vertex[1],
				msvd[i]->vertex[2] );
#endif
	}
	/* get number of triangles */
	bufptr = GetWord( bufptr,&numTris );
	ptrToTris = bufptr;

#ifdef DEBUG_PM_MS3D
	printf( "NumTriangles: %d\n",numTris );
#endif
	/* swap tris */
	for ( i = 0; i < numTris; i++ )
	{
		TMsTriangle *triangle;
		triangle = (TMsTriangle *)bufptr;
		bufptr += sizeof( TMsTriangle );

		triangle->flags = _pico_little_short( triangle->flags );

		/* run through all tri verts */
		for ( k = 0; k < 3; k++ )
		{
			/* swap tex coords */
			triangle->s[ k ] = _pico_little_float( triangle->s[ k ] );
			triangle->t[ k ] = _pico_little_float( triangle->t[ k ] );

			/* swap fields */
			triangle->vertexIndices[ k ]      = _pico_little_short( triangle->vertexIndices[ k ] );
			triangle->vertexNormals[ 0 ][ k ] = _pico_little_float( triangle->vertexNormals[ 0 ][ k ] );
			triangle->vertexNormals[ 1 ][ k ] = _pico_little_float( triangle->vertexNormals[ 1 ][ k ] );
			triangle->vertexNormals[ 2 ][ k ] = _pico_little_float( triangle->vertexNormals[ 2 ][ k ] );

			/* check for out of range indices */
			if ( triangle->vertexIndices[ k ] >= numVerts ) {
				_pico_printf( PICO_ERROR,"Vertex %d index %d out of range (%d, max %d)",i,k,triangle->vertexIndices[k],numVerts - 1 );
				PicoFreeModel( model );
				_pico_free( bufptr0 );
				return NULL; /* yuck */
			}
		}
	}
	/* get number of groups */
	bufptr = GetWord( bufptr,&numGroups );
//	ptrToGroups = bufptr;

#ifdef DEBUG_PM_MS3D
	printf( "NumGroups: %d\n",numGroups );
#endif
	/* run through all groups in model */
	for ( i = 0; i < numGroups && i < MS3D_MAX_GROUPS; i++ )
	{
		picoSurface_t *surface;
		TMsGroup      *group;

		group = (TMsGroup *)bufptr;
		bufptr += sizeof( TMsGroup );

		/* we ignore hidden groups */
		if ( group->flags & MS3D_HIDDEN ) {
			bufptr += ( group->numTriangles * 2 ) + 1;
			continue;
		}
		/* forced null term of group name */
		group->name[ 31 ] = '\0';

		/* create new pico surface */
		surface = PicoNewSurface( model );
		if ( surface == NULL ) {
			PicoFreeModel( model );
			_pico_free( bufptr0 );
			return NULL;
		}
		/* do surface setup */
		PicoSetSurfaceType( surface,PICO_TRIANGLES );
		PicoSetSurfaceName( surface,group->name );

		/* process triangle indices */
		for ( k = 0; k < group->numTriangles; k++ )
		{
			TMsTriangle *triangle;
			unsigned int triangleIndex;

			/* get triangle index */
			bufptr = GetWord( bufptr,(int *)&triangleIndex );

			/* get ptr to triangle data */
			triangle = (TMsTriangle *)( ptrToTris + ( sizeof( TMsTriangle ) * triangleIndex ) );

			/* run through triangle vertices */
			for ( m = 0; m < 3; m++ )
			{
				TMsVertex   *vertex;
				unsigned int vertexIndex;
				picoVec2_t texCoord;

				/* get ptr to vertex data */
				vertexIndex = triangle->vertexIndices[ m ];
				vertex = (TMsVertex *)( ptrToVerts + ( sizeof( TMsVertex ) * vertexIndex ) );

				/* store vertex origin */
				PicoSetSurfaceXYZ( surface,vertexIndex,vertex->xyz );

				/* store vertex color */
				PicoSetSurfaceColor( surface, 0, vertexIndex, picoColor_white );

				/* store vertex normal */
				PicoSetSurfaceNormal( surface,vertexIndex,triangle->vertexNormals[ m ] );

				/* store current face vertex index */
				PicoSetSurfaceIndex( surface,( k * 3 + ( 2 - m ) ),(picoIndex_t)vertexIndex );

				/* get texture vertex coord */
				texCoord[ 0 ] = triangle->s[ m ];
				texCoord[ 1 ] = -triangle->t[ m ];  /* flip t */

				/* store texture vertex coord */
				PicoSetSurfaceST( surface,0,vertexIndex,texCoord );
			}
		}
		/* store material */
		shaderRefs[ i ] = *bufptr++;

#ifdef DEBUG_PM_MS3D
		printf( "Group %d: '%s' (%d tris)\n",i,group->name,group->numTriangles );
#endif
	}
	/* get number of materials */
	bufptr = GetWord( bufptr,&numMaterials );

#ifdef DEBUG_PM_MS3D
	printf( "NumMaterials: %d\n",numMaterials );
#endif
	/* run through all materials in model */
	for ( i = 0; i < numMaterials; i++ )
	{
		picoShader_t *shader;
		picoColor_t ambient,diffuse,specular;
		TMsMaterial  *material;
		int k;

		material = (TMsMaterial *)bufptr;
		bufptr += sizeof( TMsMaterial );

		/* null term strings */
		material->name    [  31 ] = '\0';
		material->texture [ 127 ] = '\0';
		material->alphamap[ 127 ] = '\0';

		/* ltrim strings */
		_pico_strltrim( material->name );
		_pico_strltrim( material->texture );
		_pico_strltrim( material->alphamap );

		/* rtrim strings */
		_pico_strrtrim( material->name );
		_pico_strrtrim( material->texture );
		_pico_strrtrim( material->alphamap );

		/* create new pico shader */
		shader = PicoNewShader( model );
		if ( shader == NULL ) {
			PicoFreeModel( model );
			_pico_free( bufptr0 );
			return NULL;
		}
		/* scale shader colors */
		for ( k = 0; k < 4; k++ )
		{
			ambient [ k ] = (picoByte_t) ( material->ambient[ k ] * 255 );
			diffuse [ k ] = (picoByte_t) ( material->diffuse[ k ] * 255 );
			specular[ k ] = (picoByte_t) ( material->specular[ k ] * 255 );
		}
		/* set shader colors */
		PicoSetShaderAmbientColor( shader,ambient );
		PicoSetShaderDiffuseColor( shader,diffuse );
		PicoSetShaderSpecularColor( shader,specular );

		/* set shader transparency */
		PicoSetShaderTransparency( shader,material->transparency );

		/* set shader shininess (0..127) */
		PicoSetShaderShininess( shader,material->shininess );

		/* set shader name */
		PicoSetShaderName( shader,material->name );

		/* set shader texture map name */
		PicoSetShaderMapName( shader,material->texture );

#ifdef DEBUG_PM_MS3D
		printf( "Material %d: '%s' ('%s','%s')\n",i,material->name,material->texture,material->alphamap );
#endif
	}
	/* assign shaders to surfaces */
	for ( i = 0; i < numGroups && i < MS3D_MAX_GROUPS; i++ )
	{
		picoSurface_t *surface;
		picoShader_t  *shader;

		/* sanity check */
		if ( shaderRefs[ i ] >= MS3D_MAX_MATERIALS ||
			 shaderRefs[ i ] < 0 ) {
			continue;
		}

		/* get surface */
		surface = PicoGetModelSurface( model,i );
		if ( surface == NULL ) {
			continue;
		}

		/* get shader */
		shader = PicoGetModelShader( model,shaderRefs[ i ] );
		if ( shader == NULL ) {
			continue;
		}

		/* assign shader */
		PicoSetSurfaceShader( surface,shader );

#ifdef DEBUG_PM_MS3D
		printf( "Mapped: %d ('%s') to %d (%s)\n",
				shaderRefs[i],shader->name,i,surface->name );
#endif
	}
	/* return allocated pico model */
	_pico_free( bufptr0 );
	return model;
//	return NULL;
}

/* pico file format module definition */
const picoModule_t picoModuleMS3D =
{
	"0.4-a",                    /* module version string */
	"Milkshape 3D",             /* module display name */
	"seaw0lf",                  /* author's name */
	"2002 seaw0lf",             /* module copyright */
	{
		"ms3d",NULL,NULL,NULL   /* default extensions to use */
	},
	_ms3d_canload,              /* validation routine */
	_ms3d_load,                 /* load routine */
	NULL,                       /* save validation routine */
	NULL                        /* save routine */
};
