#include <stdio.h>
#include <stdlib.h>
#include <cmdlib.h>
#include <mathlib.h>
#include <trilib.h>
#include <io.h>
#include "lightwave.h"

// global variables
static int	currenttexture, texmodechange;

static int	currentsurface;
static lwsurface_t	*psurface;

static byte	*lwostart, *lwoend;

lwinfo_t	lwinfo;

/*
=============================================================================

Support functions

=============================================================================
*/

/*
================
CheckTexModeChange
================
*/
void CheckTexModeChange (void)
{
	if (texmodechange)
	{
		currenttexture++;
		texmodechange = 0;
	}
	texmodechange = 1;
}

/*
================
SurfaceForName
================
*/
int SurfaceNumForName (lwsurface_t *surfaces, char *name)
{
	int i;

	for (i = 0; i < lwinfo.num_surfaces; i++)
	{
		if (!strcmp(surfaces[i].name, name))
			return(i);
	}

	return(-1);
}

/*
================
align
================
*/
int align (int len)
{
	if (len & 1)
		return len + 1;
	return len;
}

/*
=============================================================================

Lightwave file operations

=============================================================================
*/

/*
================
LW_OpenFile

Opens a .LWO file and verifies for reading.
start	- pointer to begining of chunks
end		- end of file
================
*/
int LW_OpenFile (char *filename, byte **start, byte **end)
{
	byte	*plwo;
	int		formlength;

	StripExtension (filename);
	strcat (filename, ".lwo");

	// open file, allocate buffer and read into buffer
	if (!LoadFile (filename, &lwostart))
		return 0;
	
	plwo = lwostart;

	// check for FORM ID
	if (strncmp(plwo, "FORM", 4))
		Error("No FORM id at start of file!\n");

	// get form length
	plwo += 4;
	formlength = BigLong( *(int *)plwo );

	// set end
	plwo += 4;								
	lwoend = plwo + align(formlength);
	
	// check for Lightwave "LWO" chunk
    if (strncmp(plwo, "LWOB", 4))
		Error("Not an LWOB file.\n");

	plwo += 4;

	*start = plwo;
	*end = lwoend;

	return 1;
}

/*
================
LW_CloseFile
================
*/
void LW_CloseFile (void)
{
	if (lwostart != NULL)
		free (lwostart);

	if (lwinfo.lwpoint != NULL)
		free (lwinfo.lwpoint);

	if (lwinfo.lwpoly != NULL)
		free (lwinfo.lwpoly);

	if (lwinfo.lwcurve != NULL)
		free (lwinfo.lwcurve);

	if (lwinfo.lwsurface != NULL)
		free (lwinfo.lwsurface);

	lwinfo.lwpoint = NULL;
	lwinfo.lwpoly = NULL;
	lwinfo.lwcurve = NULL;
	lwinfo.lwsurface = NULL;
}

/*
================
LW_ParseSubChunkCOLR
================
*/
void LW_ParseSubChunkCOLR (byte *in)
{
	psurface[currentsurface].colr[0] = *in;			// red
	psurface[currentsurface].colr[1] = *(in + 1);	// green
	psurface[currentsurface].colr[2] = *(in + 2);	// blue
	psurface[currentsurface].colr[3] = 0;			// currently unused, should always be 0
}

/*
================
LW_ParseSubChunkFLAG
================
*/
void LW_ParseSubChunkFLAG (byte *in)
{
	psurface[currentsurface].flag = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkLUMI
================
*/
void LW_ParseSubChunkLUMI (byte *in)
{
	psurface[currentsurface].lumi = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkDIFF
================
*/
void LW_ParseSubChunkDIFF (byte *in)
{
	psurface[currentsurface].diff = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkSPEC
================
*/
void LW_ParseSubChunkSPEC (byte *in)
{
	psurface[currentsurface].spec = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkREFL
================
*/
void LW_ParseSubChunkREFL (byte *in)
{
	psurface[currentsurface].refl = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkTRAN
================
*/
void LW_ParseSubChunkTRAN (byte *in)
{
	psurface[currentsurface].tran = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkGLOS
================
*/
void LW_ParseSubChunkGLOS (byte *in)
{
	psurface[currentsurface].glos = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkRIMG
================
*/
void LW_ParseSubChunkRIMG (byte *in)
{
	strcpy (psurface[currentsurface].rimg, (char *) in);
}

/*
================
LW_ParseSubChunkRSAN
================
*/
void LW_ParseSubChunkRSAN (byte *in)
{
	psurface[currentsurface].rsan = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkRIND
================
*/
void LW_ParseSubChunkRIND (byte *in)
{
	psurface[currentsurface].rind = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkEDGE
================
*/
void LW_ParseSubChunkEDGE (byte *in)
{
	psurface[currentsurface].edge = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkSMAN
================
*/
void LW_ParseSubChunkSMAN (byte *in)
{
	psurface[currentsurface].sman = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkCTEX
================
*/
void LW_ParseSubChunkCTEX (byte *in)
{
	psurface[currentsurface].texmode[currenttexture] = TEXMODE_CTEX;
	strcpy (psurface[currentsurface].textype[currenttexture], (char *) in);
	CheckTexModeChange ();
}

/*
================
LW_ParseSubChunkDTEX
================
*/
void LW_ParseSubChunkDTEX (byte *in)
{
	psurface[currentsurface].texmode[currenttexture] = TEXMODE_DTEX;
	strcpy (psurface[currentsurface].textype[currenttexture], (char *) in);
	CheckTexModeChange ();
}

/*
================
LW_ParseSubChunkSTEX
================
*/
void LW_ParseSubChunkSTEX (byte *in)
{
	psurface[currentsurface].texmode[currenttexture] = TEXMODE_STEX;
	strcpy (psurface[currentsurface].textype[currenttexture], (char *) in);
	CheckTexModeChange ();
}

/*
================
LW_ParseSubChunkRTEX
================
*/
void LW_ParseSubChunkRTEX (byte *in)
{
	psurface[currentsurface].texmode[currenttexture] = TEXMODE_RTEX;
	strcpy (psurface[currentsurface].textype[currenttexture], (char *) in);
	CheckTexModeChange ();
}

/*
================
LW_ParseSubChunkTTEX
================
*/
void LW_ParseSubChunkTTEX (byte *in)
{
	psurface[currentsurface].texmode[currenttexture] = TEXMODE_TTEX;
	strcpy (psurface[currentsurface].textype[currenttexture], (char *) in);
	CheckTexModeChange ();
}

/*
================
LW_ParseSubChunkBTEX
================
*/
void LW_ParseSubChunkBTEX (byte *in)
{
	psurface[currentsurface].texmode[currenttexture] = TEXMODE_BTEX;
	strcpy (psurface[currentsurface].textype[currenttexture], (char *) in);
	CheckTexModeChange ();
}

/*
================
LW_ParseSubChunkTIMG
================
*/
void LW_ParseSubChunkTIMG (byte *in)
{
	strcpy (psurface[currentsurface].timg[currenttexture], (char *) in);
}

/*
================
LW_ParseSubChunkTFLG
================
*/
void LW_ParseSubChunkTFLG (byte *in)
{
	psurface[currentsurface].tflg[currenttexture] = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkTSIZ
================
*/
void LW_ParseSubChunkTSIZ (byte *in)
{
	psurface[currentsurface].tsiz[currenttexture][0] = BigFloat (* (float *) in);
	psurface[currentsurface].tsiz[currenttexture][1] = BigFloat (* (float *) in + 1);
	psurface[currentsurface].tsiz[currenttexture][2] = BigFloat (* (float *) in + 2);
}

/*
================
LW_ParseSubChunkTCTR
================
*/
void LW_ParseSubChunkTCTR (byte *in)
{
	psurface[currentsurface].tctr[currenttexture][0] = BigFloat (* (float *) in);
	psurface[currentsurface].tctr[currenttexture][1] = BigFloat (* (float *) in + 1);
	psurface[currentsurface].tctr[currenttexture][2] = BigFloat (* (float *) in + 2);
}

/*
================
LW_ParseSubChunkTFAL
================
*/
void LW_ParseSubChunkTFAL (byte *in)
{
	psurface[currentsurface].tfal[currenttexture][0] = BigFloat (* (float *) in);
	psurface[currentsurface].tfal[currenttexture][1] = BigFloat (* (float *) in + 1);
	psurface[currentsurface].tfal[currenttexture][2] = BigFloat (* (float *) in + 2);
}

/*
================
LW_ParseSubChunkTVEL
================
*/
void LW_ParseSubChunkTVEL (byte *in)
{
	psurface[currentsurface].tvel[currenttexture][0] = BigFloat (* (float *) in);
	psurface[currentsurface].tvel[currenttexture][1] = BigFloat (* (float *) in + 1);
	psurface[currentsurface].tvel[currenttexture][2] = BigFloat (* (float *) in + 2);
}

/*
================
LW_ParseSubChunkTCLR
================
*/
void LW_ParseSubChunkTCLR (byte *in)
{
	if (psurface[currentsurface].texmode[currenttexture] != TEXMODE_CTEX)
	{
		printf ("LW_ParseChunkSURF: TCLR appears in wrong texture mode\n");
		return;
	}
	psurface[currentsurface].tclr[0] = *in;		// red
	psurface[currentsurface].tclr[1] = *(in + 1);	// green
	psurface[currentsurface].tclr[2] = *(in + 2);	// blue
	psurface[currentsurface].tclr[3] = 0;			// currently unused, should always be 0
}

/*
================
LW_ParseSubChunkTVAL
================
*/
void LW_ParseSubChunkTVAL (byte *in)
{
	if (psurface[currentsurface].texmode[currenttexture] < TEXMODE_DTEX || psurface[currentsurface].texmode[currenttexture] > TEXMODE_TTEX)
	{
		printf ("LW_ParseChunkSURF: TVAL appears in wrong texture mode\n");
		return;
	}

	psurface[currentsurface].tval[currenttexture] = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkTAMP
================
*/
void LW_ParseSubChunkTAMP (byte *in)
{
	if (psurface[currentsurface].texmode[currenttexture] != TEXMODE_BTEX)
	{
		printf ("LW_ParseChunkSURF: TAMP appears in wrong texture mode\n");
		return;
	}

	psurface[currentsurface].tamp = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkTFRQ
================
*/
void LW_ParseSubChunkTFRQ (byte *in)
{
	psurface[currentsurface].tfrq[currenttexture] = BigShort ( *(short *) in);
}

/*
================
LW_ParseSubChunkTSP0
================
*/
void LW_ParseSubChunkTSP0 (byte *in)
{
	psurface[currentsurface].tsp0[currenttexture] = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkTSP1
================
*/
void LW_ParseSubChunkTSP1 (byte *in)
{
	psurface[currentsurface].tsp1[currenttexture] = BigFloat (* (float *) in);
}

/*
================
LW_ParseSubChunkTSP2
================
*/
void LW_ParseSubChunkTSP2 (byte *in)
{
	psurface[currentsurface].tsp2[currenttexture] = BigFloat (* (float *) in);
}

/*
================
LW_ParseChunkPNTS
================
*/
void LW_ParseChunkPNTS (byte *in, int chunklength)
{
	int		i, j;
	int		num_points;
	
	num_points = chunklength / 12;

	// alloc memory for points
	lwinfo.lwpoint = (lwpoint_t *) malloc (num_points * sizeof(lwpoint_t));
	if (!lwinfo.lwpoint)
		Error ("LW_ParseChunkSRFS: Error allocating num_points\n");

	for(i = 0; i < num_points; i++)
	{
		for (j = 0; j < 3; j++)
		{
			lwinfo.lwpoint[i].point[j] = BigFloat(*(float *)in);
			in += sizeof(float);
		}
	}

	if(i != num_points)
		Error("GrabPoints: count != num_points.\n");

	lwinfo.num_points = i;
}

/*
================
LW_ParseChunkSRFS
================
*/
void LW_ParseChunkSRFS (byte *in, int chunklength)
{
	byte	*chunkend;
	int		surfacecount;

	// set the end of the chunk
	chunkend = in + chunklength;

	// init surface count
	surfacecount = 0;

	lwinfo.lwsurface = (lwsurface_t *) malloc (MAX_SURFACES * sizeof(lwsurface_t));
	if (!lwinfo.lwsurface)
		Error ("LW_ParseChunkSRFS: Error allocating MAX_SURFACES\n");

	// read the chunks
	while (in < chunkend)
	{
		if (surfacecount >= MAX_SURFACES)
			Error("Increase MAX_SURFACES");

		// read in the surface name
		strcpy (lwinfo.lwsurface[surfacecount].name, (char *) in);

		// increment to the end of the string
		in += strlen((char *) in) + 1;

		// all strings are stored in even numbers of bytes
		if (!*in) 
			in++;

		// increment surfacecount
		surfacecount++;
	}

	lwinfo.num_surfaces = surfacecount;
}

/*
================
LW_ParseChunkPOLS
================
*/
void LW_ParseChunkPOLS (byte *in, int chunklength)
{
	int		i, j, k;
	byte	*chunkend;

	chunkend = in + chunklength;

	lwinfo.lwpoly = (lwpoly_t *) malloc (MAX_POLYGONS * sizeof (lwpoly_t));
	if (!lwinfo.lwpoly)
		Error ("LW_ParseChunkPOLS: Error allocating MAX_POLYGONS\n");

	for(i = 0; in < chunkend; i++)
	{
		if (i >= MAX_POLYGONS)
			Error("Increase MAX_POLYGONS");

		lwinfo.lwpoly[i].num_xyz = BigShort(*(short *) in);
		in += sizeof(short);

		lwinfo.lwpoly[i].index_xyz = (short *) malloc (lwinfo.lwpoly[i].num_xyz * sizeof (short));

		if (!lwinfo.lwpoly[i].index_xyz)
			Error ("LW_ParseChunkPOLS: Error allocating index_xyz\n");

		for(j = 0; j < lwinfo.lwpoly[i].num_xyz; j++)
		{
			lwinfo.lwpoly[i].index_xyz[j] = BigShort(*(short *)in);
			in += sizeof(short);
		}		

		lwinfo.lwpoly[i].index_surface = BigShort(*(short *)in);
		in += sizeof(short);

		// we could use recursion for this next part, 
		// but we only want one level deep for detail polygons

		// check to see if this is polygon has detail polygons

		if (lwinfo.lwpoly[i].index_surface < 0)
		{
			lwinfo.lwpoly[i].index_surface = abs(lwinfo.lwpoly[i].index_surface);

			lwinfo.lwpoly[i].num_detailpolys = BigShort(*(short *)in);
			in += sizeof(short);

			lwinfo.lwpoly[i].detailpolys = (lwpoly_t *) malloc (lwinfo.lwpoly[i].num_detailpolys * sizeof (lwpoly_t));
			if (!lwinfo.lwpoly[i].detailpolys)
				Error ("LW_ParseChunkPOLS: Error allocating detailpolys\n");

			for (k = 0; k < lwinfo.lwpoly[i].num_detailpolys; k++)
			{
				lwinfo.lwpoly[i].num_xyz = BigShort(*(short *) in);
				in += sizeof(short);

				for(j = 0; j < lwinfo.lwpoly[i].num_xyz; j++)
				{
					lwinfo.lwpoly[i].detailpolys[k].index_xyz[j] = BigShort(*(short *)in);
					in += sizeof(short);

					lwinfo.lwpoly[i].index_surface = BigShort(*(short *)in);
					in += sizeof(short);
				}		
			}
		}
	}

	lwinfo.num_polys = i;
}

/*
================
LW_ParseChunkCRVS
================
*/
void LW_ParseChunkCRVS (byte *in, int chunklength)
{
	int		i, j;
	byte	*chunkend;

	chunkend = in + chunklength;

	lwinfo.lwcurve = (lwcurve_t *) malloc (MAX_CURVES * sizeof (lwcurve_t));
	if (!lwinfo.lwcurve)
		Error ("LW_ParseChunkCRVS: Error allocating MAX_CURVES\n");
	
	for(i = 0; in < chunkend; i++)
	{
		if (i >= MAX_CURVES)
			Error("Increase MAX_CURVES");

		lwinfo.lwcurve[i].num_xyz = BigShort(*(short *) in);
		in += sizeof(short);

		lwinfo.lwcurve[i].index_xyz = malloc (lwinfo.lwcurve[i].num_xyz * sizeof (short));
		if (!lwinfo.lwcurve[i].index_xyz)
			Error ("LW_ParseChunkCRVS: Error allocating index_xyz\n");

		for(j = 0; j < lwinfo.lwcurve[i].num_xyz; j++)
		{
			lwinfo.lwcurve[i].index_xyz[j] = BigShort(*(short *)in);
			in += sizeof(short);
		}		

		lwinfo.lwcurve[i].index_surface = BigShort(*(short *)in);
		in += sizeof(short);

		lwinfo.lwcurve[i].flags = BigShort(*(short *)in);
		in += sizeof(short);
	}

	lwinfo.num_curves = i;
}

/*
================
LW_ParseChunkSURF
================
*/
void LW_ParseChunkSURF (byte *in, int chunklength)
{
	int		subchunklength;
	byte	*chunkend;
	char	*name, subchunkname[5];

	// initialize texture mode variables
	currenttexture = texmodechange = 0;
	
	// calculate end of this chunk
	chunkend = in + chunklength;

	// get the surface name
	name = in;
	in += strlen(name) + 1;

	// alignment
	if (!*in) 
		in++;

	// set pointer to surfaces
	psurface = lwinfo.lwsurface;

	currentsurface = SurfaceNumForName (psurface, name);
	
	while (in < chunkend)
	{
		memcpy(subchunkname, in, 4); 
		subchunkname[4] = 0x00;
		in += 4;

		subchunklength = BigShort( *(short *) in);
		in += sizeof(short);

		if (!strcmp(subchunkname, "COLR"))
			LW_ParseSubChunkCOLR (in);
		else if (!strcmp(subchunkname, "FLAG"))
			LW_ParseSubChunkFLAG (in);
		else if (!strcmp(subchunkname, "LUMI"))
			LW_ParseSubChunkLUMI (in);
		else if (!strcmp(subchunkname, "DIFF"))
			LW_ParseSubChunkDIFF (in);
		else if (!strcmp(subchunkname, "SPEC"))
			LW_ParseSubChunkSPEC (in);
		else if (!strcmp(subchunkname, "REFL"))
			LW_ParseSubChunkREFL (in);
		else if (!strcmp(subchunkname, "TRAN"))
			LW_ParseSubChunkTRAN (in);
		else if (!strcmp(subchunkname, "GLOS"))
			LW_ParseSubChunkGLOS (in);
		else if (!strcmp(subchunkname, "RIMG"))
			LW_ParseSubChunkRIMG (in);
		else if (!strcmp(subchunkname, "RSAN"))
			LW_ParseSubChunkRSAN (in);
		else if (!strcmp(subchunkname, "RIND"))
			LW_ParseSubChunkRIND (in);
		else if (!strcmp(subchunkname, "EDGE"))
			LW_ParseSubChunkEDGE (in);
		else if (!strcmp(subchunkname, "SMAN"))
			LW_ParseSubChunkSMAN (in);
		else if (!strcmp(subchunkname, "CTEX"))
			LW_ParseSubChunkCTEX (in);
		else if (!strcmp(subchunkname, "DTEX"))
			LW_ParseSubChunkDTEX (in);
		else if (!strcmp(subchunkname, "STEX"))
			LW_ParseSubChunkSTEX (in);
		else if (!strcmp(subchunkname, "RTEX"))
			LW_ParseSubChunkRTEX (in);
		else if (!strcmp(subchunkname, "TTEX"))
			LW_ParseSubChunkTTEX (in);
		else if (!strcmp(subchunkname, "BTEX"))
			LW_ParseSubChunkBTEX (in);
		else if (!strcmp(subchunkname, "TIMG"))
			LW_ParseSubChunkTIMG (in);
		else if (!strcmp(subchunkname, "TFLG"))
			LW_ParseSubChunkTFLG (in);
		else if (!strcmp(subchunkname, "TSIZ"))
			LW_ParseSubChunkTSIZ (in);
		else if (!strcmp(subchunkname, "TCTR"))
			LW_ParseSubChunkTCTR (in);
		else if (!strcmp(subchunkname, "TFAL"))
			LW_ParseSubChunkTFAL (in);
		else if (!strcmp(subchunkname, "TVEL"))
			LW_ParseSubChunkTVEL (in);
		else if (!strcmp(subchunkname, "TCLR"))
			LW_ParseSubChunkTCLR (in);
		else if (!strcmp(subchunkname, "TVAL"))
			LW_ParseSubChunkTVAL (in);
		else if (!strcmp(subchunkname, "TAMP"))
			LW_ParseSubChunkTAMP (in);
		else if (!strcmp(subchunkname, "TFRQ"))
			LW_ParseSubChunkTFRQ (in);
		else if (!strcmp(subchunkname, "TSP0"))
			LW_ParseSubChunkTSP0 (in);
		else if (!strcmp(subchunkname, "TSP1"))
			LW_ParseSubChunkTSP1 (in);
		else if (!strcmp(subchunkname, "TSP2"))
			LW_ParseSubChunkTSP2 (in);

		in += subchunklength;
	}
}

/*
================
LW_ParseChunkUnknown

returns pointer to unknown chunk
================
*/
byte *LW_ParseChunkUnknown (byte *in, int chunklength)
{
	byte	*ptr;

	ptr = malloc (chunklength);
	if (!ptr)
		Error ("LW_ParseChunkUnknown: Error allocating %d bytes", chunklength);

	memcpy (ptr, in, chunklength);

	return ptr;
}
