/*
   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 "inout.h"
#include "md4.h"

qboolean g_compress_pak;
qboolean g_release;             // don't grab, copy output data to new tree
qboolean g_pak;                 // if true, copy to pak instead of release
char g_releasedir[1024];        // c:\quake2\baseq2, etc
qboolean g_archive;             // don't grab, copy source data to new tree
qboolean do3ds;
char g_only[256];               // if set, only grab this cd
qboolean g_skipmodel;           // set true when a cd is not g_only

char        *ext_3ds = "3ds";
char        *ext_tri = "tri";
char        *trifileext;

char game[64] = "quake2";

void InitPaths( int *argc, char **argv );

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

   PAK FILES

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

typedef struct
{
	char name[56];
	int filepos, filelen;
} packfile_t;

typedef struct
{
	char id[4];
	int dirofs;
	int dirlen;
} packheader_t;

packfile_t pfiles[16384];
FILE            *pakfile;
packfile_t      *pf;
packheader_t pakheader;



/*
   ==============
   BeginPak
   ==============
 */
void BeginPak( char *outname ){
	if ( !g_pak ) {
		return;
	}

	pakfile = SafeOpenWrite( outname );

	// leave space for header
	SafeWrite( pakfile, &pakheader, sizeof( pakheader ) );

	pf = pfiles;
}


/*
   ==============
   ReleaseFile

   Filename should be gamedir reletive.
   Either copies the file to the release dir, or adds it to
   the pak file.
   ==============
 */
void ReleaseFile( char *filename ){
	int len;
	byte    *buf;
	char source[1024];
	char dest[1024];

	if ( !g_release ) {
		return;
	}

	sprintf( source, "%s%s", gamedir, filename );

	if ( !g_pak ) { // copy it
		sprintf( dest, "%s/%s", g_releasedir, filename );
		printf( "copying to %s\n", dest );
		QCopyFile( source, dest );
		return;
	}

	// pak it
	printf( "paking %s\n", filename );
	if ( strlen( filename ) >= sizeof( pf->name ) ) {
		Error( "Filename too long for pak: %s", filename );
	}

	len = LoadFile( source, (void **)&buf );

	if ( g_compress_pak && len < 4096 * 1024 ) {
		cblock_t in, out;
		cblock_t Huffman( cblock_t in );

		in.count = len;
		in.data = buf;

		out = Huffman( in );

		if ( out.count < in.count ) {
			printf( "   compressed from %i to %i\n", in.count, out.count );
			free( in.data );
			buf = out.data;
			len = out.count;
		}
		else{
			free( out.data );
		}
	}

	strcpy( pf->name, filename );
	pf->filepos = LittleLong( ftell( pakfile ) );
	pf->filelen = LittleLong( len );
	pf++;

	SafeWrite( pakfile, buf, len );

	free( buf );
}


/*
   ==============
   FinishPak
   ==============
 */
void FinishPak( void ){
	int dirlen;
	int d;
	int i;
	unsigned checksum;

	if ( !g_pak ) {
		return;
	}

	pakheader.id[0] = 'P';
	pakheader.id[1] = 'A';
	pakheader.id[2] = 'C';
	pakheader.id[3] = 'K';
	dirlen = (byte *)pf - (byte *)pfiles;
	pakheader.dirofs = LittleLong( ftell( pakfile ) );
	pakheader.dirlen = LittleLong( dirlen );

	checksum = Com_BlockChecksum( (void *)pfiles, dirlen );

	SafeWrite( pakfile, pfiles, dirlen );

	i = ftell( pakfile );

	fseek( pakfile, 0, SEEK_SET );
	SafeWrite( pakfile, &pakheader, sizeof( pakheader ) );
	fclose( pakfile );

	d = pf - pfiles;
	printf( "%i files packed in %i bytes\n",d, i );
	printf( "checksum: 0x%x\n", checksum );
}


/*
   ===============
   Cmd_File

   This is only used to cause a file to be copied during a release
   build (default.cfg, maps, etc)
   ===============
 */
void Cmd_File( void ){
	GetToken( false );
	ReleaseFile( token );
}

/*
   ===============
   PackDirectory_r

   ===============
 */
#if GDEF_OS_WINDOWS
#include "io.h"
void PackDirectory_r( char *dir ){
	struct _finddata_t fileinfo;
	int handle;
	char dirstring[1024];
	char filename[1024];

	sprintf( dirstring, "%s%s/*.*", gamedir, dir );

	handle = _findfirst( dirstring, &fileinfo );
	if ( handle == -1 ) {
		return;
	}

	do
	{
		sprintf( filename, "%s/%s", dir, fileinfo.name );
		if ( fileinfo.attrib & _A_SUBDIR ) { // directory
			if ( fileinfo.name[0] != '.' ) {  // don't pak . and ..
				PackDirectory_r( filename );
			}
			continue;
		}
		// copy or pack the file
		ReleaseFile( filename );
	} while ( _findnext( handle, &fileinfo ) != -1 );

	_findclose( handle );
}
#else

#include <sys/types.h>

#include <dirent.h>

void PackDirectory_r( char *dir ){
#ifdef NeXT
	struct direct **namelist, *ent;
#else
	struct dirent **namelist, *ent;
#endif
	int count;
	struct stat st;
	int i;
	int len;
	char fullname[1024];
	char dirstring[1024];
	char        *name;

	sprintf( dirstring, "%s%s", gamedir, dir );
	count = scandir( dirstring, &namelist, NULL, NULL );

	for ( i = 0 ; i < count ; i++ )
	{
		ent = namelist[i];
		name = ent->d_name;

		if ( name[0] == '.' ) {
			continue;
		}

		sprintf( fullname, "%s/%s", dir, name );
		sprintf( dirstring, "%s%s/%s", gamedir, dir, name );

		if ( stat( dirstring, &st ) == -1 ) {
			Error( "fstating %s", pf->name );
		}
		if ( st.st_mode & S_IFDIR ) { // directory
			PackDirectory_r( fullname );
			continue;
		}

		// copy or pack the file
		ReleaseFile( fullname );
	}
}
#endif


/*
   ===============
   Cmd_Dir

   This is only used to cause a directory to be copied during a
   release build (sounds, etc)
   ===============
 */
void Cmd_Dir( void ){
	GetToken( false );
	PackDirectory_r( token );
}

//========================================================================

#define MAX_RTEX    16384
int numrtex;
char rtex[MAX_RTEX][64];

void ReleaseTexture( char *name ){
	int i;
	char path[1024];

	for ( i = 0 ; i < numrtex ; i++ )
		if ( !Q_strncasecmp( name, rtex[i], strlen( name ) ) ) {
			return;
		}

	if ( numrtex == MAX_RTEX ) {
		Error( "numrtex == MAX_RTEX" );
	}

	strcpy( rtex[i], name );
	numrtex++;

	sprintf( path, "textures/%s.wal", name );
	ReleaseFile( path );
}

/*
   ===============
   Cmd_Maps

   Only relevent for release and pak files.
   Releases the .bsp files for the maps, and scans all of the files to
   build a list of all textures used, which are then released.
   ===============
 */
void Cmd_Maps( void ){
	char map[1024];
	int i;

	while ( TokenAvailable() )
	{
		GetToken( false );
		sprintf( map, "maps/%s.bsp", token );
		ReleaseFile( map );

		if ( !g_release ) {
			continue;
		}

		// get all the texture references
		sprintf( map, "%smaps/%s.bsp", gamedir, token );
		LoadBSPFileTexinfo( map );
		for ( i = 0 ; i < numtexinfo ; i++ )
			ReleaseTexture( texinfo[i].texture );
	}
}


//==============================================================

/*
   ===============
   ParseScript
   ===============
 */
void ParseScript( void ){
	while ( 1 )
	{
		do
		{   // look for a line starting with a $ command
			GetToken( true );
			if ( endofscript ) {
				return;
			}
			if ( token[0] == '$' ) {
				break;
			}
			while ( TokenAvailable() )
				GetToken( false );
		} while ( 1 );

		//
		// model commands
		//
		if ( !strcmp( token, "$modelname" ) ) {
			Cmd_Modelname();
		}
		else if ( !strcmp( token, "$base" ) ) {
			Cmd_Base();
		}
		else if ( !strcmp( token, "$cd" ) ) {
			Cmd_Cd();
		}
		else if ( !strcmp( token, "$origin" ) ) {
			Cmd_Origin();
		}
		else if ( !strcmp( token, "$scale" ) ) {
			Cmd_ScaleUp();
		}
		else if ( !strcmp( token, "$frame" ) ) {
			Cmd_Frame();
		}
		else if ( !strcmp( token, "$skin" ) ) {
			Cmd_Skin();
		}
		else if ( !strcmp( token, "$skinsize" ) ) {
			Cmd_Skinsize();
		}
		//
		// sprite commands
		//
		else if ( !strcmp( token, "$spritename" ) ) {
			Cmd_SpriteName();
		}
		else if ( !strcmp( token, "$load" ) ) {
			Cmd_Load();
		}
		else if ( !strcmp( token, "$spriteframe" ) ) {
			Cmd_SpriteFrame();
		}
		//
		// image commands
		//
		else if ( !strcmp( token, "$grab" ) ) {
			Cmd_Grab();
		}
		else if ( !strcmp( token, "$raw" ) ) {
			Cmd_Raw();
		}
		else if ( !strcmp( token, "$colormap" ) ) {
			Cmd_Colormap();
		}
		else if ( !strcmp( token, "$mippal" ) ) {
			Cmd_Mippal();
		}
		else if ( !strcmp( token, "$mipdir" ) ) {
			Cmd_Mipdir();
		}
		else if ( !strcmp( token, "$mip" ) ) {
			Cmd_Mip();
		}
		else if ( !strcmp( token, "$environment" ) ) {
			Cmd_Environment();
		}
		//
		// video
		//
		else if ( !strcmp( token, "$video" ) ) {
			Cmd_Video();
		}
		//
		// misc
		//
		else if ( !strcmp( token, "$file" ) ) {
			Cmd_File();
		}
		else if ( !strcmp( token, "$dir" ) ) {
			Cmd_Dir();
		}
		else if ( !strcmp( token, "$maps" ) ) {
			Cmd_Maps();
		}
		else if ( !strcmp( token, "$alphalight" ) ) {
			Cmd_Alphalight();
		}
		else if ( !strcmp( token, "$inverse16table" ) ) {
			Cmd_Inverse16Table();
		}
		else{
			Error( "bad command %s\n", token );
		}
	}
}

//=======================================================

/*
   ==============
   main
   ==============
 */
int main( int argc, char **argv ){
	static int i;           // VC4.2 compiler bug if auto...
	char path[1024];

	ExpandWildcards( &argc, &argv );

	InitPaths( &argc, argv );

	for ( i = 1 ; i < argc ; i++ )
	{
		if ( !strcmp( argv[i], "-archive" ) ) {
			// -archive f:/quake2/release/dump_11_30
			archive = true;
			strcpy( archivedir, argv[i + 1] );
			printf( "Archiving source to: %s\n", archivedir );
			i++;
		}
		else if ( !strcmp( argv[i], "-release" ) ) {
			g_release = true;
			strcpy( g_releasedir, argv[i + 1] );
			printf( "Copy output to: %s\n", g_releasedir );
			i++;
		}
		else if ( !strcmp( argv[i], "-compress" ) ) {
			g_compress_pak = true;
			printf( "Compressing pakfile\n" );
		}
		else if ( !strcmp( argv[i], "-pak" ) ) {
			g_release = true;
			g_pak = true;
			printf( "Building pakfile: %s\n", argv[i + 1] );
			BeginPak( argv[i + 1] );
			i++;
		}
		else if ( !strcmp( argv[i], "-only" ) ) {
			strcpy( g_only, argv[i + 1] );
			printf( "Only grabbing %s\n", g_only );
			i++;
		}
		else if ( !strcmp( argv[i], "-3ds" ) ) {
			do3ds = true;
			printf( "loading .3ds files\n" );
		}
		else if ( argv[i][0] == '-' ) {
			Error( "Unknown option \"%s\"", argv[i] );
		}
		else{
			break;
		}
	}

	if ( i >= argc ) {
		Error( "usage: %s [-archive <directory>] [-release <directory>] [-only <model>] [-3ds] file.qgr", argv[ 0 ] );
	}

	if ( do3ds ) {
		trifileext = ext_3ds;
	}
	else{
		trifileext = ext_tri;
	}

	for ( ; i < argc ; i++ )
	{
		printf( "--------------- %s ---------------\n", argv[i] );
		// load the script
		strcpy( path, argv[i] );
		DefaultExtension( path, ".qdt" );
		SetQdirFromPath( path );
		LoadScriptFile( ExpandArg( path ) );

		//
		// parse it
		//
		ParseScript();

		// write out the last model
		FinishModel();
		FinishSprite();
	}

	if ( g_pak ) {
		FinishPak();
	}

	return 0;
}
