/*
    Unmass - unpacker for game archives.
    Copyright (C) 2002-2007  Mirex

    This program 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.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdarg.h>
#include <stdio.h>

#include <string.h>

#include "config.h"

#ifdef _UNIX
	#include <ctype.h>
	#include <strings.h>
	#include <unistd.h>
	#include <sys/stat.h>
#endif

#include "utools.h"


UnmassTools::s_params	UnmassTools::param_def[ UnmassTools::param_def_count ] =
{
	{ param_help,	"-help", 0 },
	{ param_help,	"--help", 0 },
	{ param_help,	"/?", 0 },

	{ param_list_of_modules, "-modules", 0 },
	{ param_print_file_list, "-list", 1 },
	{ param_extract_files, "-e", -2 },


} ;

char UnmassTools::version_string[ 20 ] = "0.9 console";
char UnmassTools::executable_name_string[ 20 ] = "unmass";


void UnmassTools::Log( int severity, const char* format, ... )
{
	va_list argptr;
	va_start( argptr, format );

	vprintf( format, argptr );

	va_end( argptr );
}

int UnmassTools::Start( int argc, char* argv[] )
{
	int		action = param_help;
	int		i, c, required_params_number;
	bool	or_more = false;

	UnmassTools::array_of_filenames		names_array;

	if ( argc <= 1 ) {
		action = param_none;
	} else {

		for( i=0; i<param_def_count; i++ )
			if ( _strcasecmp( argv[ 1 ], param_def[ i ].string ) == 0 ) {
				action = param_def[ i ].ident;

				required_params_number = param_def[ i ].param_count;
				if ( required_params_number < 0 ) {
					required_params_number = - required_params_number;
					or_more = true;
				}
				
				c = argc - 2;

				if (( c > required_params_number ) && ( or_more == false )) {
					Log( 0, "Supplied more than enough parameters.\n" );
				}

				if ( c < required_params_number ) {
					Log( 0, "Not enough parameters.\n" );
					action = param_none;
				}

				break;
			}

	}


	switch( action ) {
	case param_none:
		PrintProgramInfo();
		return 0;

	case param_help:
		PrintHelp();
		return 0;

	case param_list_of_modules:
		PrintModules();
		return 0;

	case param_print_file_list:
		
		return PrintMassFileList( argv[ 2 ] );

	case param_extract_files:
	
		names_array.clear();
		for( i=0; i<c-1; i++ )
			names_array.push_back( argv[ 3 + i ] );
	
		return ExtractMultipleFiles( argv[ 2 ], names_array );

	default:
		Log( 1, "Incorrect action\n" );
		break;

	}

	return 1;
}

void UnmassTools::PrintProgramInfo( void )
{
	Log( 0, "-*= Unmass =*-\n" );
	Log( 0, "version %s\n\n", version_string );
	Log( 0, "Unmass is archive unpacker for game archives.\n" );
	Log( 0, "Use parameter /? to see help.\n" );
}

void UnmassTools::PrintHelp( void )
{
	PrintProgramInfo();
	Log( 0, "\n" );

	int	i, j;

	for( i=0; i<param_count; i++ ) {

		for( j=0; j<param_def_count; j++ ) {
			if ( param_def[ j ].ident == i ) {

				Log( 0, "  %s\n", param_def[ j ].string );

			}
		}

		switch( i ) {
		case param_help:
			Log( 0, "Prints out this help\n" );
			break;
		case param_list_of_modules:
			Log( 0, "Prints out list of modules for archive loading\n" );
			break;
		case param_print_file_list:
			Log( 0, "Parameters: <archive_file>\n" );
			Log( 0, "Opens the archive and prints list of the files inside.\n" );
			break;
		case param_extract_files:
			Log( 0, "Parameters: <archive file> <file name> [...<file name>]\n" );
			Log( 0, "Opens the archive and extract files.\n" );
			Log( 0, " file name can be specified also with * wildcard in one of these forms:\n");
			Log( 0, " 'name', 'prefix*', '*suffix' or 'prefix*suffix' \n");
			Log( 0, " so 'level*.txt' should work well.\n");
			Log( 0, " Enclose the filenames with wildcards into ' ' so unix system won't replace\n" );
			Log( 0, " those names with currently existing files names.\n" );
			break;
		}

		Log( 0, "\n" );
	}

	Log( 0, "Example:\n" );
	Log( 0, "  %s -list battle.lgp\n", executable_name_string );
	Log( 0, "    Displays list of files contained inside archive 'battle.lgp'\n\n" );
	Log( 0, "  %s -e battle.lgp aabc.txt *dat\n", executable_name_string );
	Log( 0, "    Opens 'battle.lgp' and extracts file 'aabc.txt' and all files ending\n" );
	Log( 0, "    with 'dat' into current directory\n\n" );

	Log( 0, "\n" );

	return;
}

void UnmassTools::PrintModules( void )
{
	int		i, c;
	CMassArchive*	pArchive;

	Log( 0, "Plugins and their supported types:\n" );

	for( i=0; i<CMassFiles::massftypes_count; i++ ) {
		pArchive = massfs.Archive[ i ];
		if ( pArchive == NULL )
			Log( 0, "null\n" );
		else {
			Log( 0, "%10s %s\n", pArchive->GetExtension(), pArchive->GetIdent() );
		}
	}

}

int UnmassTools::PrintMassFileList( const char* mass_filename )
{
	if ( massfs.Open( mass_filename ) == 0 ) {
		Log( 1, "Error, could not open file (%s)\n", massfs.GetErrorStr() );
		return 0;
	}

	int	i, c;
	c = massfs.MassfInfo.files_count;

	massfs.ReadAllRecords();

	for( i=0; i<c; i++ ) {
		massfs.GetRec( i );

		printf( "%s\n", massfs.FileRec.name );
	}
	
	massfs.Close();

}

//  const char* filename
int UnmassTools::ExtractMultipleFiles( 
		const char* mass_filename, UnmassTools::array_of_filenames names )
{
	// first try to open the file
	if ( massfs.Open( mass_filename ) == 0 ) {
		Log( 1, "Error, could not open file (%s)\n", massfs.GetErrorStr() );
		return 0;
	}

	int	i, j, c, extracted_files = 0;
	bool	extract, prefix_match, suffix_match;
	c = massfs.MassfInfo.files_count;
	massfs.ReadAllRecords();

	// second, parse the parameter list and try to extract the files
	int number_of_files = names.size();
	const char*	ccp;

	// filename can be either "name" or "nam*" or "*me" or "na*me" or "*"
	enum {
		name_len = 512,
	} ;
	struct {
		char	name[ name_len ];
		bool	valid;
		bool	all;	// extract all files ?
		char	prefix[ name_len ];
		char	suffix[ name_len ];
				
		int		prefix_length, suffix_length;

		void Reset( void ) {
			valid = false;
			all = false;
			name[ 0 ] = 0;
			prefix[ 0 ] = 0;
			suffix[ 0 ] = 0;
			prefix_length = suffix_length = 0;
		}

		void Parse( const char* fname ) {
			int			stars = 0, c;
			const char*	ccp = fname;

			Reset();

			// is it a single star ? then it means 'all files'
			if (( fname[ 0 ] == '*' ) && ( fname[ 1 ] == 0 )) {
				all = true;
				valid = true;
				return;
			}

			while( *ccp != 0 ) {

				if ( *ccp == '*' ) {
				
					// count the star characters. there can be only one in the filename
					stars++;
					if ( stars > 1 )
						break;
					
					// treat all characters before star as prefix
					if ( ccp != fname ) {
						c = ( ccp - fname );
						if ( c >= name_len )
							c = name_len - 1;
						memcpy( prefix, fname, c );
						prefix[ c ] = 0;
						prefix_length = c;
					}

				} else {
					// if there was star already, and suffix is empty then create it
					if (( stars > 0 ) && ( suffix[ 0 ] == 0 )) {
						strncpy( suffix, ccp, name_len-1 );
						suffix_length = strlen( suffix );
					}

				}

				ccp++;
			}

			// too many stars ? invalid mask !
			if ( stars > 1 ) {
				Reset();
				valid = false;
				
			} else {
			
				strncpy( name, fname, name_len-1 );
				valid = true;
			
			}
		} // Parse

	} filespec;

	for( i=0; i<number_of_files; i++ ) {
		ccp = names[ i ];
		
		filespec.Parse( ccp );
		
		if ( filespec.valid == false ) {
			Log( 1, "Error: filename specification '%s' is invalid.", ccp );
			continue;
		}

		// loop through all files, and see if the filename matches the search pattern
		for( j=0; j<massfs.MassfInfo.files_count; j++ ) {
		
			extract = false;
			prefix_match = false;
			suffix_match = false;
			massfs.GetRec( j );
			
			if ( filespec.all )
				extract = true;
				
			// whole name
			if (( extract == false ) && ( _strcasecmp( ccp, massfs.FileRec.name ) == 0 ))
				extract = true;
			
			// check for prefix
			if (( extract == false ) && ( filespec.prefix_length > 0 ) &&
				( _strncasecmp( filespec.prefix, massfs.FileRec.name, filespec.prefix_length ) == 0 ))
				prefix_match = true;
		
			// check for suffix
			if (( extract == false ) && ( filespec.suffix_length > 0 ) &&
				( _strcasecmp( filespec.suffix, 
				massfs.FileRec.name + strlen( massfs.FileRec.name ) - filespec.suffix_length ) == 0 ))
				suffix_match = true;
				
			// if there is both prefix and suffix defined, then extract only if both match
			if (( filespec.prefix_length > 0 ) && ( filespec.suffix_length > 0 )) {
				if (( prefix_match) && ( suffix_match ))
					extract = true;
			} else {
				if (( prefix_match ) || ( suffix_match ))
					extract = true;
			}
		
			if ( extract ) {
				extracted_files++;
				Log( 0, "Extracting file '%s'\n", massfs.FileRec.name );
				massfs.Extract();
			}
		}
		
		// if I extract all files its not needed to extract any more
		if ( filespec.all ) {
			break;
		}
		
	}

	Log( 0, "Extracted %i files.\n", extracted_files );

	massfs.Close();

	return 1;
}

void UnmassTools::_strlwr( char* string )
{
	#ifdef _UNIX
		char*	cp = string;

		while( *cp != 0 ) {
			*cp = tolower( *cp );
			cp++;
		}
		return;
	#else
		strlwr( string );
		return;
	#endif
}

int UnmassTools::_strcasecmp( const char* s1, const char* s2 )
{
	#ifdef _UNIX
		return strcasecmp( s1, s2 );
	#else
		return stricmp( s1, s2 );
	#endif
}

int UnmassTools::_strncasecmp( const char* s1, const char* s2, int n )
{
	#ifdef _UNIX
		return strncasecmp( s1, s2, n );
	#else
		error, don't know of suitable function
		return stricmp( s1, s2 );
	#endif
}
