/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
****/

//
// spritegen.c: generates a .spr file from a series of .lbm frame files.
// Result is stored in /raid/quake/id1/sprites/<scriptname>.spr.
//
#pragma warning(disable : 4244)     // type conversion warning.
#define INCLUDELIBS


#ifdef NeXT
#include <libc.h>
#endif

#include "spritegn.h"

#define MAX_BUFFER_SIZE		0x100000
#define MAX_FRAMES			1000

dsprite_t		sprite;
byte			*byteimage, *lbmpalette;
int				byteimagewidth, byteimageheight;
byte			*lumpbuffer = NULL, *plump;
char			spritedir[1024];
char			spriteoutname[1024];
int				framesmaxs[2];
int				framecount;

qboolean do16bit = true;

typedef struct {
	spriteframetype_t	type;		// single frame or group of frames
	void				*pdata;		// either a dspriteframe_t or group info
	float				interval;	// only used for frames in groups
	int					numgroupframes;	// only used by group headers
} spritepackage_t;

spritepackage_t	frames[MAX_FRAMES];

void FinishSprite (void);
void Cmd_Spritename (void);


/*
============
WriteFrame
============
*/
void WriteFrame (FILE *spriteouthandle, int framenum)
{
	dspriteframe_t	*pframe;
	dspriteframe_t	frametemp;

	pframe = (dspriteframe_t *)frames[framenum].pdata;
	frametemp.origin[0] = LittleLong (pframe->origin[0]);
	frametemp.origin[1] = LittleLong (pframe->origin[1]);
	frametemp.width = LittleLong (pframe->width);
	frametemp.height = LittleLong (pframe->height);

	SafeWrite (spriteouthandle, &frametemp, sizeof (frametemp));
	SafeWrite (spriteouthandle,
			   (byte *)(pframe + 1),
			   pframe->height * pframe->width);
}


/*
============
WriteSprite
============
*/
void WriteSprite (FILE *spriteouthandle)
{
	int			i, groupframe, curframe;
	dsprite_t	spritetemp;

	sprite.boundingradius = sqrt (((framesmaxs[0] >> 1) *
								   (framesmaxs[0] >> 1)) +
								  ((framesmaxs[1] >> 1) *
								   (framesmaxs[1] >> 1)));

//
// write out the sprite header
//
	spritetemp.type = LittleLong (sprite.type);
	spritetemp.texFormat = LittleLong (sprite.texFormat);
	spritetemp.boundingradius = LittleFloat (sprite.boundingradius);
	spritetemp.width = LittleLong (framesmaxs[0]);
	spritetemp.height = LittleLong (framesmaxs[1]);
	spritetemp.numframes = LittleLong (sprite.numframes);
	spritetemp.beamlength = LittleFloat (sprite.beamlength);
	spritetemp.synctype = LittleFloat (sprite.synctype);
	spritetemp.version = LittleLong (SPRITE_VERSION);
	spritetemp.ident = LittleLong (IDSPRITEHEADER);

	SafeWrite (spriteouthandle, &spritetemp, sizeof(spritetemp));

	if( do16bit )
	{
		// Write out palette in 16bit mode
		short cnt = 256;
		SafeWrite( spriteouthandle, (void *) &cnt, sizeof(cnt) );
		SafeWrite( spriteouthandle, lbmpalette, cnt * 3 );
	}

//
// write out the frames
//
	curframe = 0;

	for (i=0 ; i<sprite.numframes ; i++)
	{
		SafeWrite (spriteouthandle, &frames[curframe].type,
				   sizeof(frames[curframe].type));

		if (frames[curframe].type == SPR_SINGLE)
		{
		//
		// single (non-grouped) frame
		//
			WriteFrame (spriteouthandle, curframe);
			curframe++;
		}
		else
		{
			int					j, numframes;
			dspritegroup_t		dsgroup;
			float				totinterval;

			groupframe = curframe;
			curframe++;
			numframes = frames[groupframe].numgroupframes;

		//
		// set and write the group header
		//
			dsgroup.numframes = LittleLong (numframes);

			SafeWrite (spriteouthandle, &dsgroup, sizeof(dsgroup));

		//
		// write the interval array
		//
			totinterval = 0.0;

			for (j=0 ; j<numframes ; j++)
			{
				dspriteinterval_t	temp;

				totinterval += frames[groupframe+1+j].interval;
				temp.interval = LittleFloat (totinterval);

				SafeWrite (spriteouthandle, &temp, sizeof(temp));
			}

			for (j=0 ; j<numframes ; j++)
			{
				WriteFrame (spriteouthandle, curframe);
				curframe++;
			}
		}
	}

}


/*
============
ExecCommand
============
*/
int	cmdsrun;

void ExecCommand (char *cmd, ...)
{
	int		ret;
	char	cmdline[1024];
	va_list	argptr;
	
	cmdsrun++;
	
	va_start (argptr, cmd);
	vsprintf (cmdline,cmd,argptr);
	va_end (argptr);
	
//	printf ("=============================================================\n");
//	printf ("spritegen: %s\n",cmdline);
	fflush (stdout);
	ret = system (cmdline);
//	printf ("=============================================================\n");
	
	if (ret)
		Error ("spritegen: exiting due to error");
}

/*
==============
LoadScreen
==============
*/
void LoadScreen (char *name)
{
	static byte origpalette[256*3];
	int iError;

	printf ("grabbing from %s...\n",name);
	iError = LoadBMP( name, &byteimage, &lbmpalette );
	if (iError)
		Error( "unable to load file \"%s\"\n", name );

	byteimagewidth = bmhd.w;
	byteimageheight = bmhd.h;

	if (sprite.numframes == 0)
		memcpy( origpalette, lbmpalette, sizeof( origpalette ));
	else if (memcmp( origpalette, lbmpalette, sizeof( origpalette )) != 0)
	{
		Error( "bitmap \"%s\" doesn't share a pallette with the previous bitmap\n", name );
	}
}


/*
===============
Cmd_Type
===============
*/
void Cmd_Type (void)
{
	GetToken (false);
	if (!strcmp (token, "vp_parallel_upright"))
		sprite.type = SPR_VP_PARALLEL_UPRIGHT;
	else if (!strcmp (token, "facing_upright"))
		sprite.type = SPR_FACING_UPRIGHT;
	else if (!strcmp (token, "vp_parallel"))
		sprite.type = SPR_VP_PARALLEL;
	else if (!strcmp (token, "oriented"))
		sprite.type = SPR_ORIENTED;
	else if (!strcmp (token, "vp_parallel_oriented"))
		sprite.type = SPR_VP_PARALLEL_ORIENTED;
	else
		Error ("Bad sprite type\n");
}


/*
===============
Cmd_Texture
===============
*/
void Cmd_Texture (void)
{
	GetToken (false);

	if (!strcmp (token, "additive"))
		sprite.texFormat = SPR_ADDITIVE;
	else if (!strcmp (token, "normal"))
		sprite.texFormat = SPR_NORMAL;
	else if (!strcmp (token, "indexalpha"))
		sprite.texFormat = SPR_INDEXALPHA;
	else if (!strcmp (token, "alphatest"))
		sprite.texFormat = SPR_ALPHTEST;
	else
		Error ("Bad sprite texture type\n");
}


/*
===============
Cmd_Beamlength
===============
*/
void Cmd_Beamlength ()
{
	GetToken (false);
	sprite.beamlength = atof (token);
}


/*
===============
Cmd_Load
===============
*/
void Cmd_Load (void)
{
	GetToken (false);
	LoadScreen (ExpandPathAndArchive(token));
}


/*
===============
Cmd_Frame
===============
*/
void Cmd_Frame ()
{
	int             x,y,xl,yl,xh,yh,w,h;
	byte            *screen_p, *source;
	int             linedelta;
	dspriteframe_t	*pframe;
	int				pix;
	
	GetToken (false);
	xl = atoi (token);
	GetToken (false);
	yl = atoi (token);
	GetToken (false);
	w = atoi (token);
	GetToken (false);
	h = atoi (token);

	if ((xl & 0x07) || (yl & 0x07) || (w & 0x07) || (h & 0x07))
		Error ("Sprite dimensions not multiples of 8\n");

	if ((w > 256) || (h > 256))
		Error ("Sprite has a dimension longer than 256");

	xh = xl+w;
	yh = yl+h;

	pframe = (dspriteframe_t *)plump;
	frames[framecount].pdata = pframe;
	frames[framecount].type = SPR_SINGLE;

	if (TokenAvailable ())
	{
		GetToken (false);
		frames[framecount].interval = atof (token);
		if (frames[framecount].interval <= 0.0)
			Error ("Non-positive interval");
	}
	else
	{
		frames[framecount].interval = (float)0.1;
	}
	
	if (TokenAvailable ())
	{
		GetToken (false);
		pframe->origin[0] = -atoi (token);
		GetToken (false);
		pframe->origin[1] = atoi (token);
	}
	else
	{
		pframe->origin[0] = -(w >> 1);
		pframe->origin[1] = h >> 1;
	}

	pframe->width = w;
	pframe->height = h;

	if (w > framesmaxs[0])
		framesmaxs[0] = w;
	
	if (h > framesmaxs[1])
		framesmaxs[1] = h;
	
	plump = (byte *)(pframe + 1);

	screen_p = byteimage + yl*byteimagewidth + xl;
	linedelta = byteimagewidth - w;

	source = plump;

	for (y=yl ; y<yh ; y++)
	{
		for (x=xl ; x<xh ; x++)
		{
			pix = *screen_p;
			*screen_p++ = 0;
//			if (pix == 255)
//				pix = 0;
			*plump++ = pix;
		}
		screen_p += linedelta;
	}

	framecount++;
	if (framecount >= MAX_FRAMES)
		Error ("Too many frames; increase MAX_FRAMES\n");
}


/*
===============
Cmd_GroupStart	
===============
*/
void Cmd_GroupStart (void)
{
	int			groupframe;

	groupframe = framecount++;

	frames[groupframe].type = SPR_GROUP;
	frames[groupframe].numgroupframes = 0;

	while (1)
	{
		GetToken (true);
		if (endofscript)
			Error ("End of file during group");

		if (!strcmp (token, "$frame"))
		{
			Cmd_Frame ();
			frames[groupframe].numgroupframes++;
		}
		else if (!strcmp (token, "$load"))
		{
			Cmd_Load ();
		}
		else if (!strcmp (token, "$groupend"))
		{
			break;
		}
		else
		{
			Error ("$frame, $load, or $groupend expected\n");
		}

	}

	if (frames[groupframe].numgroupframes == 0)
		Error ("Empty group\n");
}


/*
===============
ParseScript	
===============
*/
void ParseScript (void)
{
	while (1)
	{
		GetToken (true);
		if (endofscript)
			break;
	
		if (!strcmp (token, "$load"))
		{
			Cmd_Load ();
		}
		if (!strcmp (token, "$spritename"))
		{
			Cmd_Spritename ();
		}
		else if (!strcmp (token, "$type"))
		{
			Cmd_Type ();
		}
		else if (!strcmp (token, "$texture"))
		{
			Cmd_Texture ();
		}
		else if (!strcmp (token, "$beamlength"))
		{
			Cmd_Beamlength ();
		}
		else if (!strcmp (token, "$sync"))
		{
			sprite.synctype = ST_SYNC;
		}
		else if (!strcmp (token, "$frame"))
		{
			Cmd_Frame ();
			sprite.numframes++;
		}		
		else if (!strcmp (token, "$load"))
		{
			Cmd_Load ();
		}
		else if (!strcmp (token, "$groupstart"))
		{
			Cmd_GroupStart ();
			sprite.numframes++;
		}
	}
}

/*
==============
Cmd_Spritename
==============
*/
void Cmd_Spritename (void)
{
	if (sprite.numframes)
		FinishSprite ();

	GetToken (false);
	sprintf (spriteoutname, "%s%s.spr", spritedir, token);
	memset (&sprite, 0, sizeof(sprite));
	framecount = 0;

	framesmaxs[0] = -9999999;
	framesmaxs[1] = -9999999;

	if ( !lumpbuffer )
		lumpbuffer = malloc (MAX_BUFFER_SIZE * 2);	// *2 for padding

	if (!lumpbuffer)
		Error ("Couldn't get buffer memory");

	plump = lumpbuffer;
	sprite.synctype = ST_RAND;	// default
}

/*
==============
FinishSprite	
==============
*/
void FinishSprite (void)
{
	FILE	*spriteouthandle;

	if (sprite.numframes == 0)
		Error ("no frames\n");

	if (!strlen(spriteoutname))
		Error ("Didn't name sprite file");
		
	if ((plump - lumpbuffer) > MAX_BUFFER_SIZE)
		Error ("Sprite package too big; increase MAX_BUFFER_SIZE");

	spriteouthandle = SafeOpenWrite (spriteoutname);
	printf ("saving in %s\n", spriteoutname);
	WriteSprite (spriteouthandle);
	fclose (spriteouthandle);
	
	printf ("spritegen: successful\n");
	printf ("%d frame(s)\n", sprite.numframes);
	printf ("%d ungrouped frame(s), including group headers\n", framecount);
	
	spriteoutname[0] = 0;		// clear for a new sprite
}

/*
==============
main
	
==============
*/
extern char qproject[];

int main (int argc, char **argv)
{
	int		i;

	printf( "sprgen.exe v 1.1 (%s)\n", __DATE__ );
	printf ("----- Sprite Gen ----\n");

	if (argc < 2 || argc > 7 )
		Error ("usage: sprgen [-archive directory] [-no16bit] [-proj <project>] file.qc");
		
	for( i=1; i<argc; i++ )
	{
		if( *argv[ i ] == '-' )
		{
			if( !strcmp( argv[ i ], "-archive" ) )
			{
				archive = true;
				strcpy (archivedir, argv[i+1]);
				printf ("Archiving source to: %s\n", archivedir);
				i++;
			}
			else if( !strcmp( argv[ i ], "-proj" ) )
			{
				strcpy( qproject, argv[i+1] );
				i++;
			}
			else if( !strcmp( argv[ i ], "-16bit" ) )
			{
				do16bit = true;
			}
			else if( !strcmp( argv[ i ], "-no16bit" ) )
			{
				do16bit = false;
			} 
			else
			{
				printf( "Unsupported command line flag: '%s'", argv[i] );
			}

		}
		else
			break;
	}

	// SetQdirFromPath (argv[i]);
	ExtractFilePath (argv[i], spritedir);	// chop the filename

//
// load the script
//
	LoadScriptFile (argv[i]);
	
	ParseScript ();
	FinishSprite ();

	return 0;
}