/*
    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 <stdlib.h>
#include <assert.h>

#include "config.h"

#ifdef _WIN32
	#include <direct.h>
#endif

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

#include "massfs.h"
#include "utools.h"

#include "ma.h"
#include "ma_lgp.h"
#include "ma_pak.h"
#include "ma_wad2.h"
#include "ma_ipwad.h"
#include "ma_grp.h"
#include "ma_lbx.h"
#include "ma_vol.h"
#include "ma_wtn.h"
#include "ma_dune2.h"
#include "ma_moor3.h"
#include "ma_swine.h"
#include "ma_roll.h"
#include "ma_umod.h"
#include "ma_pbo.h"
#include "ma_pbo.h"
#include "ma_crism.h"
#include "ma_ff8.h"
#include "ma_bif.h"
#include "ma_eth2.h"
#include "ma_oni_d.h"
#include "ma_ecou.h"
#include "ma_vf1bi.h"
#include "ma_mgmnl.h"
#include "ma_mgs.h"
#include "ma_mea.h"
#include "ma_gunme.h"
#include "ma_fpk.h"


CMassFiles::CMassFiles( void )
{
	int		i;

	for( i=0; i<massftypes_count; i++ )
		Archive[ i ] = NULL;

	Archive[ massftype_lgp		] = new CMassLgp;
	Archive[ massftype_pak		] = new CMassPak;
	Archive[ massftype_wad2		] = new CMassWad2;
	Archive[ massftype_ipwad	] = new CMassIPwad;
	Archive[ massftype_grp		] = new CMassGrp;
	Archive[ massftype_lbx		] = new CMassLbx;
	Archive[ massftype_vol		] = new CMassVol;
	Archive[ massftype_wtn		] = new CMassWtn;
	Archive[ massftype_dune2	] = new CMassDune2;
	Archive[ massftype_moor3	] = new CMassMoor3;
	Archive[ massftype_swine	] = new CMassSwine;
	Archive[ massftype_roll		] = new CMassRoll;
	Archive[ massftype_umod		] = new CMassUmod;
	Archive[ massftype_pbo		] = new CMassPbo;
	Archive[ massftype_crismon	] = new CMassCrismon;
	Archive[ massftype_ff8		] = new CMassFF8;
	Archive[ massftype_bif		] = new CMassBif;
	Archive[ massftype_eth2		] = new CMassEth2;
	Archive[ massftype_oni_dat	] = new CMassOniDat;
	Archive[ massftype_ecou		] = new CMassEcou;
	Archive[ massftype_vf1bin	] = new CMassVF1bin;
	Archive[ massftype_megamanL	] = new CMassMegamanLegends;
	Archive[ massftype_mgs		] = new CMassMgs;
	Archive[ massftype_mea		] = new CMassMea;
	Archive[ massftype_gunmetal	] = new CMassGunMetal;
	Archive[ massftype_fpk		] = new CMassFpk;
	
	
	for( i=0; i<massftypes_count; i++ )
		if ( Archive[ i ] == NULL ) {
			SetErrorStr( "Could not alloc !" );
		}

	memset( &MassfInfo, 0, sizeof( MassfInfo ) );
	MassfInfo.type = massftype_unknown;
}

CMassFiles::~CMassFiles()
{
	int		i;

	Close();

	for( i=0; i<massftypes_count; i++ )
		if ( Archive[ i ] != NULL )
			delete Archive[ i ];
}

// 0 = no error
int CMassFiles::Open( const char *MassFileName )
{
	int		i;
	FILE	*f;

	//cleanup 
	Close();

	//check filename
	if ( strlen( MassFileName ) >= FileNameWithPathMaxLen ) {
		SetErrorStr( "Open: filename too long" );
		return 0;
	}

	//try if it exists
	f = fopen( MassFileName, "rb" );
	if ( f == NULL ) {
		SetErrorStr( "Open: could not open file" );
		return 0;
	}
	fclose( f );

	//check out the file type and open it
	for( i=0; i<massftypes_count; i++ ) {

		Archive[ i ]->OpenFile( MassFileName );
		if ( Archive[ i ]->CheckFileType() )
			break;
		Archive[ i ]->CloseFile();
	}

	if ( i == massftypes_count ) {
		SetErrorStr( "Open: unknown filetype" );
		return 0;
	}
	MassfInfo.type = i;


	//=== get archive info ===
	strncpy( MassfInfo.filenm, MassFileName, FileNameWithPathMaxLen );

	//i'll split file name into filename and file extension
	//find last slash (or start) and last dot
	int		dot, slash;

	slash = strlen( MassFileName ) - 1;
	dot = -1;
	while ( slash > -1 ) {
		if (( MassFileName[ slash ] == '\\' ) ||
			( MassFileName[ slash ] == '/' ))
			break;
		if (( MassFileName[ slash ] == '.' ) && ( dot == -1 ))
			dot = slash;
		slash --;
	} ;
	if ( dot == -1 ) {
		strncpy( MassfInfo.fileextension, "", 19 );
		dot = strlen( MassFileName );
	} else
		strncpy( MassfInfo.fileextension, &MassFileName[ dot + 1 ], 19 );

	memcpy( MassfInfo.filedir, MassFileName, FileNameWithPathMaxLen );
	MassfInfo.filedir[ slash+1 ] = 0;
	memcpy( MassfInfo.filename, &MassFileName[ slash + 1 ],
			dot - slash - 1 );
	MassfInfo.filename[ dot - slash - 1 ] = '\0';
	UnmassTools::_strlwr( MassfInfo.filename );
	UnmassTools::_strlwr( MassfInfo.fileextension );

	
	Archive[ MassfInfo.type ]->GetFileMainInfo();
	strncpy( MassfInfo.typestring, Archive[ MassfInfo.type ]->GetIdent(), FileNameWithPathMaxLen );

	MassfInfo.files_count = Archive[ MassfInfo.type ]->GetFilesCount();
	MassfInfo.flags = Archive[ MassfInfo.type ]->GetFlags();
	MassfInfo.set = 1;

	return 1;
}

void CMassFiles::Close( void )
{
	int		i;

	for( i=0; i<massftypes_count; i++ )
		Archive[ i ]->CloseFile();

	MassfInfo.type = massftype_unknown;

	memset( &MassfInfo, 0, sizeof( MassfInfo ) );
	MassfInfo.set = 0;
}

char *CMassFiles::GetTypeIdentString( int type )
{
	static	char	ident[ 256 ];

	if (( type < 0 ) || ( type >= massftypes_count )) {
		ident[0] = 0;
		return ident;
	}

	strncpy( ident, Archive[ type ]->GetIdent(), 255 );
	
	return ident;
}

char *CMassFiles::GetTypeExt( int type )
{
	static	char	ext[ 256 ];

	if (( type < 0 ) || ( type >= massftypes_count )) {
		ext[0] = 0;
		return ext;
	}

	strncpy( ext, Archive[ type ]->GetExtension(), 255 );
	
	return ext;
}

int CMassFiles::GetRec( long num )
{
	if ( MassfInfo.type == massftype_unknown )
		return 0;

	Archive[ MassfInfo.type ]->FileRec.Reset();

	Archive[ MassfInfo.type ]->GetRec( num );
	
	FileRec = Archive[ MassfInfo.type ]->FileRec;

	return FileRec.set;
}

int CMassFiles::ReadRecords( void )
{
	return Archive[ MassfInfo.type ]->ReadRecords();
}

int	CMassFiles::ReadAllRecords( void )
{
	int	res = 1;

	for( int i=0; i<MassfInfo.files_count; i++ )
		res &= Archive[ MassfInfo.type ]->ReadRecords();

	return res;
}

int CMassFiles::Extract( void )
{
	enum { Massfs_BufSize = 20000 };
	int             ei, i;
	char            newname[ FileNameWithPathMaxLen ],
					newdir[ FileNameWithPathMaxLen ];
	FILE            *newf;
	char            str[ FileNameWithPathMaxLen ];
	unsigned char	buf[ Massfs_BufSize ];
	unsigned long	pos, size;
	
	if ( MassfInfo.type == massftype_unknown ) {
		SetErrorStr( "Unknown file type" );
		return 0;
	}

	Archive[ MassfInfo.type ]->FileRec = FileRec;
	
	strncpy( newname, FileRec.name, FileNameWithPathMaxLen-1 );
	for( ei=0; ei<(int)strlen( newname ); ei++ ) {
		if ( newname[ ei ] == '/' )
			newname[ ei ] = '\\';
		if ( newname[ ei ] == ' ' )
			newname[ ei ] = '_';
	}
	//dir
	strncpy( newdir, newname, FileNameWithPathMaxLen-1 );
	ei = strlen( newdir );
	while (( newdir[ ei ] != '\\' ) && ( ei > 0 ))
		ei--;
	newdir[ ei ] = '\0';
	if ( strlen( newdir ) != 0 )
		MakeDir( newdir );


	//create new file
	sprintf( str, "Creating [%s] ...", newname );
//	Msg( str );
	newf = fopen( newname, "wb" );			//?? check for existing !!
	if ( newf == NULL ) {
		sprintf( error, "Error creating [%s].", newname );
		return 0;
	}

	if ( MassfInfo.flags & CMassArchive::ma_flag_extract_only_whole_file ) {
		i = Archive[ MassfInfo.type ]->ExtractWholeFile( newf );
		if ( i == 0 ) {
			fclose( newf );
			return 0;
		}
	}
	else {

		pos = 0; size = Massfs_BufSize;
		while ( pos < FileRec.size ) {

			if ( pos + size > FileRec.size )
				size = FileRec.size - pos;


			i = Archive[ MassfInfo.type ]->Extract
				( pos, size, buf );

			if ( i == 0 ) {
				fclose( newf );
				return 0;
			}

			pos += i;

			fwrite( buf, i, 1, newf );
		}
	}

	fclose( newf );


	sprintf( str, "Extracted [%s]", newname );

	return 1;
}//Extract

//if there is no [mpath] directory, proc creates it
//watch out, it will return error on double slashes, so avoid them !
void CMassFiles::MakeDir( char *path )
{
	char    oldPath[301];           //drive:\path before mk, chdir ...
	char    str[301];
	int     mi, mj;
	int     pos;                //pointer into mpath, where am i in process
	int     oldDisk = -1;
	char	c;

	if ( path[0] == '\0' )                 //if no dir
		return;
//#ifdef WIN32
	getcwd( oldPath, 300 );
//#else
//	getcurdir( 0, str );
//#endif

//	sprintf( oldPath, "\\%s", str );
	//try first, if it is not present already
	if ( chdir( path ) == 0 ) {		//fine, path is present
		chdir( oldPath );
		return;
	}
	//if not, create path.
	sprintf( str, "Creating dir [%s]", path );
//	Msg( str );

	pos = 0;

#ifdef _WIN32
	// you can change drives on WIN32 or DOS systems
	//check if drive is included
	if ( path[1] == ':' ) {
		oldDisk = _getdrive();
		c = path[0];
		if (( 'a' <= c ) && ( c <= 'z' ))
			c += 'A' - 'a';

		mi = c - 'A' + 1;
//		mj = setdrive( mi );
		mj = _chdrive( mi );
		if ( mj == -1 ) {
			sprintf( str, "Bad drive [%c%c]", path[0], path[1] );
//			Msg( str );
		}
		pos = 2;
	}
#endif

	//change to root
	if ( path[pos] == '\\') {
		chdir( "\\" );
		pos++;
	}

	//go as far as possible
	do {
		mj = pos;   //start of dir = now
		while ( (pos < (int)strlen(path)) &&
				(path[pos] != '/') && (path[pos] != '\\') )
			pos++;
		strncpy( str, &path[mj], 300 );
        str[pos-mj] = '\0';
		mi = chdir( str );
        pos++;
	} while ( mi == 0 );                //do while chdir is ok

	//can't go further, create. Allways have to create something, if im here
	pos = mj;                           //pos to first bad dir
	do {
		mj = pos;                       //start of dir = now
		while ( (pos < (int)strlen(path)) &&
				(path[pos] != '/' ) && ( path[pos] != '\\' ))
			pos++;
		strncpy( str, &path[mj], 300 );
		str[pos-mj] = '\0';
#ifdef _WIN32
		if ( mkdir( str ) != 0 )
			return;
#endif
#ifdef _UNIX
		// oct 666
		if ( mkdir( str, 438 ) != 0 )
			return;
#endif
//			Msg( "MakeDir: Creating dir" );
		chdir( str );
		pos++;
	} while ( pos < (int)strlen( path ) );
//	Msg( "Created." );

#ifdef _WIN32
	if ( oldDisk != -1 )
//		setdisk( oldDisk );
		_chdrive( oldDisk );
#endif
//	if ( chdir( oldPath ) != 0 )
//		Msg( "MakeDir: error change old path" );

}//MakeDir

//return index of added file, -1 on error
int CMassFiles::AddFile( char* filename, char* archive_name )
{
	FILE			*fi = NULL;
	unsigned long	fs, p, bs;
	char			*buffer = NULL;
	int				res;

#define BUFFER_SIZE	64000

	if ( MassfInfo.type == massftype_unknown ) {
		SetErrorStr( "Unknown file type" );
		return 0;
	}

	if (( MassfInfo.flags & CMassArchive::ma_flag_add_file ) == 0 ) {
		assert( false );
		SetErrorStr( "This filetype does not support adding files" );
		goto AddFile_ret_error;
	}

	buffer = (char*) malloc( BUFFER_SIZE );
	if ( buffer == NULL ) {
		SetErrorStr( "Not enough memory" );
		goto AddFile_ret_error;
	}

	fi = fopen( filename, "rb" );
	if ( fi == NULL ) {
		SetErrorStr( "File not found" );
		goto AddFile_ret_error;
	}

	fseek( fi, 0, SEEK_END );
	fs = ftell( fi );
	fseek( fi, 0, SEEK_SET );

	strncpy( Archive[ MassfInfo.type ]->FileRec.name, archive_name, CMassArchive::FileNameWithPathMaxLen-1 );
	Archive[ MassfInfo.type ]->FileRec.size	= fs;

	res = Archive[ MassfInfo.type ]->AddFileRecord();
	if ( res == 0 ) {
		SetErrorStr( "Error adding file record" );
		goto AddFile_ret_error;
	}

	MassfInfo.files_count++;

	bs = BUFFER_SIZE;
	p = 0;

	while( p < fs ) {

		if ( p + bs > fs )
			bs = fs - p;

		fread( buffer, bs, 1, fi );
		Archive[ MassfInfo.type ]->AddFileData( p, bs, buffer );

		p += bs;
		
	}

	p = Archive[ MassfInfo.type ]->FileRec.index;

AddFile_ret:

	if ( fi != NULL )
		fclose( fi );

	if ( buffer != NULL )
		free( buffer );

	return p;

AddFile_ret_error:

	p = -1;

	goto AddFile_ret;
}

int CMassFiles::ReopenForWriting( void )
{
	if ( MassfInfo.type == massftype_unknown )
		return 0;

	return Archive[ MassfInfo.type ]->ReopenForWriting();
}

int CMassFiles::UnlinkFile( unsigned long index )
{
	if ( MassfInfo.type == massftype_unknown ) {
		SetErrorStr( "Unknown file type" );
		return 0;
	}

	if ( index >= MassfInfo.files_count ) {
		assert( false );
		SetErrorStr( "Index out of bounds" );
		return 0;
	}

	if ( Archive[ MassfInfo.type ]->UnlinkFile( index ) == 0 ) {
		SetErrorStr( Archive[ MassfInfo.type ]->GetErrorStr() );
		return 0;
	}

	MassfInfo.files_count--;

	return 1;
}

int CMassFiles::CreateNewArchive( char* newfilename, int type )
{
	FILE	*nf;
	int		i;

	if (( type < 0 ) || ( type >= massftypes_count )) {
		SetErrorStr( "type out of bounds" );
		return 0;
	}

	nf = fopen( newfilename, "wb" );
	if ( nf == NULL ) {
		SetErrorStr( "Could not create new file" );
		return 0;
	}

	i = Archive[ type ]->CreateNewArchive( nf );

	if ( i == 0 )
		SetErrorStr( Archive[ type ]->GetErrorStr() );

	fclose( nf );

	return i;
}

const char* CMassFiles::TypeExtToWinDlgExt( int type )
{
	if (( type < 0 ) || ( type >= massftypes_count )) {
		SetErrorStr( "type out of bounds" );
		return NULL;
	}

	char	s[ 1024 ], *chp;
	int		i, sl, sp;

	//get ext
	chp = Archive[ type ]->GetExtension();
	sl = strlen( chp );

	strncpy( s, chp, 1000 );

	i = 0;
	sp = 0;

	do {
		strncpy( &str[ sp ], "*.", 1000 - sp );
		sp += 2;

		while (( s[ i ] != 0 ) && ( s[ i ] != ',' )) {
			str[ sp++ ] = s[ i++ ];
		}

		if ( s[ i ] == ',' )
			strcat( str, "; " );
	} while ( s[ i ] != 0 );

	str[ sp++ ] = 0;

	return str;
}

const char* CMassFiles::GetTypeFirstExt( unsigned long type )
{
	if ( type >= massftypes_count ) {
		SetErrorStr( "type out of bounds" );
		return NULL;
	}

	char	*chp;
	int		i, sl, sp;

	chp = Archive[ type ]->GetExtension();
	sl = strlen( chp );
	strncpy( str, chp, 1023 );

	i = 0;
	while (( str[ i ] != 0 ) && ( str[ i ] != ',' ))
		i++;

	str[ i ] = 0;

	return str;
}

