/*
   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "qdata.h"
#include "flex.h"

#define     MAXFILES    2048

#if !GDEF_OS_WINDOWS
#define strlwr strlower
#endif // !GDEF_OS_WINDOWS

typedef struct
{
	int x;
	int y;
	int w;
	int h;
	int cw;
	int ch;
	int rw;
	int index;
	int depth;
	int col;
	int baseline;
	char name[128];
} Coords;

int filenum;
int valid;
Coords in[MAXFILES];
Coords out;
char outscript[256];
char sourcedir[256];
char outusage[256];
char root[32];

int destsize = 0;
byte            *pixels = NULL;             // Buffer to load image
long            *outpixels = NULL;          // Buffer to store combined textures
long            *usagemap = NULL;           // Buffer of usage map
void            *bmptemp = NULL;            // Buffer of usage map
byte            *map = NULL;

int xcharsize;
int ycharsize;
int dosort = 0;
int missed = 0;
int overlap = 0;
int nobaseline = 0;
int percent;

//////////////////////////////////////////////////
// Setting the char based usage map				//
//////////////////////////////////////////////////

byte    TryPlace( Coords *coord ){
	int x, y;
	byte entry = 0;
	byte    *mapitem;

	mapitem = map + ( coord->x / xcharsize ) + ( ( coord->y / ycharsize ) * out.cw );

	for ( y = 0; y < coord->ch; y++, mapitem += out.cw - coord->cw )
	{
		for ( x = 0; x < coord->cw; x++ )
		{
			if ( entry |= *mapitem++ & 8 ) {
				return( entry );
			}
		}
	}
	return( entry );
}

void    SetMap( Coords *coord ){
	int x, y;
	byte    *mapitem;

	mapitem = map + ( coord->x / xcharsize ) + ( ( coord->y / ycharsize ) * out.cw );

	for ( y = 0; y < coord->ch; y++, mapitem += out.cw - coord->cw )
		for ( x = 0; x < coord->cw; x++ )
			*mapitem++ |= 8;
}

//////////////////////////////////////////////////
// Setting the pixel based usage map			//
//////////////////////////////////////////////////

void    CheckOverlap( Coords *coord ){
	int x;
	int y;
	long        *dest;

	x = coord->x;
	y = coord->y;

	dest = (long *)( usagemap + x + ( y * out.w ) );

	for ( y = 0; y < coord->h; y++, dest += out.w - coord->w )
	{
		for ( x = 0; x < coord->w; x++ )
		{
			if ( *dest++ ) {
				overlap++;
				return;
			}
		}
	}
}

void    SetUsageMap( Coords *coord ){
	int x;
	int y;
	long        *dest;

	x = coord->x;
	y = coord->y;

	dest = (long *)( usagemap + x + ( y * out.w ) );

	for ( y = 0; y < coord->h; y++, dest += out.w - coord->w )
	{
		for ( x = 0; x < coord->w; x++ )
		{
			*dest++ = coord->col;
		}
	}
}

//////////////////////////////////////////////////
// Flips the BMP image to the correct way up	//
//////////////////////////////////////////////////

void    CopyLine( byte *dest, byte *src, int size ){
	int x;

	for ( x = 0; x < size; x++ )
		*dest++ = *src++;
}

/****************************************************/
/* Printing headers etc								*/
/****************************************************/

void RemoveLeading( char *name ){
	int i;
	char temp[128];

	for ( i = strlen( name ) - 1; i > 0; i-- )
	{
		if ( ( name[i] == '\\' ) || ( name[i] == '/' ) ) {
			strcpy( temp, name + i + 1 );
			strcpy( name, temp );
			return;
		}
	}
}

void RemoveExt( char *name ){
	while ( ( *name != '.' ) && *name )
		name++;
	*name = 0;
}

/****************************************************/
/* Misc calcualtions								*/
/****************************************************/

int TotalArea(){
	int i;
	int total = 0;

	for ( i = 0; i < ( filenum + 2 ); i++ )
		total += in[i].w * in[i].h;

	return( total );
}

/****************************************************/
/* Setup and checking of all info					*/
/****************************************************/

void    InitVars(){
	filenum = 0;
	valid = 0;
	dosort = 0;
	missed = 0;
	overlap = 0;
	nobaseline = 0;

	memset( outscript, 0, sizeof( outscript ) );
	memset( outscript, 0, sizeof( sourcedir ) );
	memset( outscript, 0, sizeof( outusage ) );
	memset( outscript, 0, sizeof( root ) );

	memset( in, 0, sizeof( in ) );
	memset( &out, 0, sizeof( out ) );
}
void Cleanup(){
	if ( pixels ) {
		free( pixels );
	}
	if ( usagemap ) {
		free( usagemap );
	}
	if ( outpixels ) {
		free( outpixels );
	}
	if ( bmptemp ) {
		free( bmptemp );
	}
	if ( map ) {
		free( map );
	}
}

typedef struct glxy_s
{
	float xl, yt, xr, yb;
	int w, h, baseline;
} glxy_t;

int SaveScript( char *name ){
	FILE        *fp;
	int i, j;
	glxy_t buff;

	if ( fp = fopen( name, "wb" ) ) {
		for ( j = 0; j < filenum; j++ )
		{
			for ( i = 0; i < filenum; i++ )
			{
				if ( in[i].index == j ) {
					if ( in[i].depth ) {
						buff.xl = (float)in[i].x / (float)out.w;
						buff.yt = (float)in[i].y / (float)out.h;
						buff.xr = ( (float)in[i].w + (float)in[i].x ) / (float)out.w;
						buff.yb = ( (float)in[i].h + (float)in[i].y ) / (float)out.h;
						buff.w = in[i].w;
						buff.h = in[i].h;
						buff.baseline = in[i].baseline;
					}
					else
					{
						memset( &buff, 0, sizeof( glxy_t ) );
					}
					fwrite( &buff, 1, sizeof( glxy_t ), fp );
					i = filenum;
				}
			}
		}
		fclose( fp );
		return( true );
	}
	else{
		return( false );
	}
}

int     GetScriptInfo( char *name ){
	FILE        *fp;
	char buffer[256];
	char tempbuff[256];
	char delims[] = {" \t,\n"};

	printf( "Opening script file %s.\n", name );

	if ( fp = fopen( name, "r" ) ) {
		while ( fgets( buffer, 256, fp ) )
		{
			if ( strncmp( buffer, "//", 2 ) && strncmp( buffer, "\n", 1 ) ) {
				strupr( buffer );
				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "OUTPUT" ) == 0 ) {
					strcpy( out.name, strtok( NULL, delims ) );
					strlwr( out.name );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "SOURCEDIR" ) == 0 ) {
					strcpy( tempbuff, strtok( NULL, delims ) );
					strcpy( sourcedir, ExpandPathAndArchive( tempbuff ) );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "DOSORT" ) == 0 ) {
					dosort = 1;
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "XCHARSIZE" ) == 0 ) {
					xcharsize = strtol( strtok( NULL, delims ), NULL, 0 );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "YCHARSIZE" ) == 0 ) {
					ycharsize = strtol( strtok( NULL, delims ), NULL, 0 );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "OUTSCRIPT" ) == 0 ) {
					strcpy( outscript, strtok( NULL, delims ) );
					strlwr( outscript );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "OUTUSAGE" ) == 0 ) {
					strcpy( outusage, strtok( NULL, delims ) );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "POS" ) == 0 ) {
					out.w = strtol( strtok( NULL, delims ), NULL, 0 );
					out.h = strtol( strtok( NULL, delims ), NULL, 0 );
				}

				strcpy( tempbuff, buffer );
				if ( strcmp( strtok( tempbuff, delims ), "FILE" ) == 0 ) {
					strcpy( in[filenum].name, strtok( NULL, delims ) );
					in[filenum].x = strtol( strtok( NULL, delims ), NULL, 0 );
					in[filenum].y = strtol( strtok( NULL, delims ), NULL, 0 );
					in[filenum].col = strtol( strtok( NULL, delims ), NULL, 0 );
					filenum++;
				}
			}
		}
		fclose( fp );
		return( true );
	}
	else
	{
		printf( "ERROR : Could not open script file.\n" );
		return( false );
	}
}

int CheckVars(){
	int i;

	if ( out.name[0] == 0 ) {
		printf( "ERROR : No output name specified.\n" );
		return( false );
	}
	if ( ( out.w <= 0 ) || ( out.h <= 0 ) ) {
		printf( "ERROR : Invalid VRAM coordinates.\n" );
		return( false );
	}
	if ( filenum == 0 ) {
		printf( "ERROR : No input files specified.\n" );
		return( false );
	}
	for ( i = 0; i < filenum; i++ )
		if ( in[i].name[0] == 0 ) {
			printf( "ERROR : Input filename invalid.\n" );
			return( false );
		}
	return( true );
}

// Makes sure texture is totally within the output area

int CheckCoords( Coords *coord ){
	if ( ( coord->x + coord->w ) > out.w ) {
		return( false );
	}
	if ( ( coord->y + coord->h ) > out.h ) {
		return( false );
	}

	return( true );
}
// Gets the width, height, palette width and palette height of each BMP file

int     GetFileDimensions(){
	int i;
	int width, height;
	char name[128];

	for ( i = 0; i < filenum; i++ )
	{
		in[i].index = i;

		strcpy( name, sourcedir );
		strcat( name, in[i].name );
		printf( "Getting file dimensions, file : %s        \r", in[i].name );
		if ( FileExists( name ) ) {
			LoadAnyImage( name, NULL, NULL, &width, &height );
			in[i].depth = 32;
			in[i].rw = width;
			in[i].w = width;                        // makes it width in
			in[i].h = height;
			in[i].cw = ( in[i].w + ( xcharsize - 1 ) ) / xcharsize;
			in[i].ch = ( in[i].h + ( ycharsize - 1 ) ) / ycharsize;

			if ( !CheckCoords( &in[i] ) && ( in[i].x >= 0 ) ) {
				printf( "Error : texture %s out of bounds.\n", in[i].name );
				return( false );
			}
			valid++;
		}
		else
		{
			in[i].depth = 0;
			in[i].x = -1;
			in[i].y = -1;
			in[i].w = 0;
			in[i].h = 0;
		}
	}
	printf( "\n\n" );
	return( true );
}

// Sorts files into order for optimal space finding
// Fixed position ones first, followed by the others in descending size
// The theory being that it is easier to find space for smaller textures.
// size = (width + height)
// For space finding it is easier to place a 32x32 than a 128x2

#define WEIGHT  0x8000

void    Swap( Coords *a, Coords *b ){
	Coords c;

	c = *a;
	*a = *b;
	*b = c;
}

void    SortInNames(){
	int i, j;
	int largest, largcount;
	int size;

	printf( "Sorting filenames by size.\n\n" );

	for ( j = 0; j < filenum; j++ )
	{
		largest = -1;
		largcount = -1;

		for ( i = j; i < filenum; i++ )
		{
			if ( in[i].depth ) {
				size = in[i].w + in[i].h;

				if ( ( in[i].x < 0 ) && ( size > largest ) ) {
					largcount = i;
					largest = size;
				}
			}
		}
		if ( ( largcount >= 0 ) && ( largcount != j ) ) {
			Swap( &in[j], &in[largcount] );
		}
	}
}

int SetVars( char *name ){
	if ( !GetScriptInfo( name ) ) {
		return( false );
	}

	if ( !CheckVars() ) {
		return( false );
	}

	destsize = out.w * out.h;

	out.cw = out.w / xcharsize;
	out.ch = out.h / ycharsize;

	if ( ( usagemap = (long *)SafeMalloc( destsize * 4, "" ) ) == NULL ) {
		return( false );
	}
	if ( ( outpixels = (long *)SafeMalloc( destsize * 4, "" ) ) == NULL ) {
		return( false );
	}
	if ( ( bmptemp = (void *)SafeMalloc( destsize * 4, "" ) ) == NULL ) {
		return( false );
	}
	if ( ( map = (byte *)SafeMalloc( destsize / ( xcharsize * ycharsize ), "" ) ) == NULL ) {
		return( false );
	}

	if ( GetFileDimensions() == false ) {
		return( false );
	}

	if ( dosort ) {
		SortInNames();
	}

	return( true );
}
/****************************************************/
/* Actual copying routines							*/
/****************************************************/

int FindCoords( Coords *coord ){
	int tx, ty;

	if ( coord->x >= 0 ) {
		SetMap( coord );
		return( true );
	}
	else
	{
		for ( ty = 0; ty < out.ch; ty++ )
		{
			for ( tx = 0; tx < out.cw; tx++ )
			{
				coord->x = ( tx * xcharsize );
				coord->y = ( ty * ycharsize );

				if ( CheckCoords( coord ) && !TryPlace( coord ) ) {
					SetMap( coord );
					return( true );
				}
			}
		}
	}
	coord->x = -1;
	coord->y = -1;

	return( false );
}

void CheckBaseline( int i ){
	int y;
	long    *pix;

	in[i].baseline = -1;
	pix = (long *)pixels;

	for ( y = 0; y < in[i].h; y++, pix += in[i].w )
	{
		if ( ( *pix & 0x00ffffff ) == 0x00ff00ff ) {
			in[i].baseline = y;
			break;
		}
	}
	pix = (long *)pixels;
	for ( y = 0; y < in[i].w * in[i].h; y++, pix++ )
	{
		if ( ( *pix & 0x00ffffff ) == 0x00ff00ff ) {
			*pix = 0;
		}
	}

	if ( in[i].baseline == -1 ) {
		printf( "\nERROR : %s has no baseline\n", in[i].name );
		nobaseline++;
	}
}

void    CopyToMain32( Coords *coord ){
	int x;
	int y;
	long        *source;
	long        *dest;

	x = coord->x;
	y = coord->y;

	source = (long *)pixels;
	dest = (long *)( outpixels + x + ( y * out.w ) );

	for ( y = 0; y < coord->h; y++, dest += out.w - coord->w )
	{
		for ( x = 0; x < coord->w; x++ )
		{
			*dest++ = *source++;
		}
	}
}

void CreateMain(){
	int i, count;
	int width, height;
	char name[128];

	for ( i = 0, count = 0; i < filenum; i++ )
	{
		if ( in[i].depth ) {
			printf( "\rProcessing %d of %d (%d missed, %d overlapping, %d nobase)\r", count + 1, valid, missed, overlap, nobaseline );
			count++;
			if ( !FindCoords( &in[i] ) ) {
				missed++;
			}
			else
			{
				strcpy( name, sourcedir );
				strcat( name, in[i].name );
				LoadAnyImage( name, &pixels, NULL, &width, &height );
				CheckBaseline( i );
				CheckOverlap( &in[i] );
				CopyToMain32( &in[i] );
				SetUsageMap( &in[i] );
			}
		}
	}
}

void Cmd_TextureMix(){
	miptex32_t      *qtex32;
	char filename[1024];
	int size;

	InitVars();

	GetScriptToken( false );

	strcpy( root, token );
	RemoveExt( root );
	RemoveLeading( root );

	strcpy( filename, ExpandPathAndArchive( token ) );
	if ( SetVars( filename ) ) {
		// Create combined texture
		percent = ( ( TotalArea() * 100 ) / ( out.w * out.h ) );
		printf( "Total area consumed : %d%%\n", percent );
		printf( "Texture resolution  : %dx%d pixels.\n", xcharsize, ycharsize );
		CreateMain();

		// Save image as m32
		sprintf( filename, "%spics/misc/%s.m32", gamedir, out.name );
		qtex32 = CreateMip32( (unsigned *)outpixels, out.w, out.h, &size, false );

		qtex32->contents = 0;
		qtex32->value = 0;
		qtex32->scale_x = 1.0;
		qtex32->scale_y = 1.0;
		sprintf( qtex32->name, "misc/%s", out.name );

		printf( "\n\nwriting %s\n", filename );
		SaveFile( filename, (byte *)qtex32, size );
		free( qtex32 );

		// Save out script file
		sprintf( filename, "%spics/misc/%s.fnt", gamedir, outscript );
		printf( "Writing %s as script file\n", filename );
		if ( !SaveScript( filename ) ) {
			printf( "Unable to save output script.\n" );
		}
	}
	printf( "Everythings groovy.\n" );
	Cleanup();
}

// end
