#include <dos/dosasl.h>
#include <dos/rdargs.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/xpkmaster.h>
#include <string.h>


#define XFH_ID		"XFH A"
#define XFH_ID_LEN	5
#define GETLONG(x,i)	( ( ( ULONG * ) x )[ i ] )


struct xPackArgs
{
	STRPTR	*Files;
	STRPTR	Method;
	ULONG	*MinSizeArg;
	STRPTR	Suffix;
	STRPTR	Password;
	LONG	All;
	LONG	Force;
	LONG	Program;
	LONG	xScan;
	LONG	Lossy;
	LONG	Quiet;

	struct FileInfoBlock	FIB;
	struct XpkFib		*XpkFIB;
	struct Hook		ChunkHook;
	TEXT	XpkErrorBuffer[ XPKERRMSGSIZE ];
	TEXT	DestName[ 108 ];
	LONG	MinSize;
};

#define TEMPLATE_MSG \
	"FILES/M/A,MET=METHOD/K,MINSIZE/N/K,SUFFIX/K,PASSWORD/K,ALL/S," \
	"FORCE/S,PROGRAM/S,XSCAN/S,LOSSY/S,QUIET/S"


LONG Main( VOID );
LONG ProcessFiles( STRPTR *, LONG, struct Hook * );


struct DosLibrary	*DOSBase;
struct ExecBase		*SysBase;
struct Library		*UtilityBase;
struct Library		*XpkBase;


__saveds LONG
Startup( VOID )
{
	LONG	rc = RETURN_FAIL;

	SysBase = *( ( struct ExecBase ** ) 4 );

	{
		struct Process	*proc;

		proc = ( struct Process * ) FindTask( NULL );

		if( !proc->pr_CLI )
		{
			WaitPort( &proc->pr_MsgPort );
			Forbid();
			ReplyMsg( GetMsg( &proc->pr_MsgPort ) );
			return( 0 );
		}
	}

	if( DOSBase = ( struct DosLibrary * ) OpenLibrary( "dos.library", 37 ) )
	{
		if( UtilityBase = OpenLibrary( "utility.library", 37 ) )
		{
			if( XpkBase = OpenLibrary( "xpkmaster.library", 2 ) )
			{
				rc = Main();
				CloseLibrary( XpkBase );
			}
			else
			{
				PutStr( "Couldn't open xpkmaster.library version 2 or higher\n" );
			}

			CloseLibrary( UtilityBase );
		}
		else
		{
			PutStr( "Couldn't open utility.library version 37 or higher\n" );
		}

		CloseLibrary( ( struct Library * ) DOSBase );
	}

	return( rc );
}


static const TEXT	Version[] = "$VER: xPack 2.0 (7.6.98)";


VOID
PrintfErr( STRPTR fmt, ... )
{
	LONG	err;

	err = IoErr();
	VPrintf( fmt, &fmt + 1 );
	PrintFault( err, NULL );
	PutStr( "\n" );
}


VOID
TempName( STRPTR buf )
{
	ULONG	i, a;

	for( i = 0, a = ( ULONG ) FindTask( NULL ); i < 8; i++, a >>= 4 )
	{
		*buf++ = 'A' + ( TEXT ) ( a & 15 );
	}

	strcpy( buf, ".zop" );
}


STRPTR
ULTA( STRPTR s, ULONG x )
{
	ULONG	i;

	*s++= ' ';

	for( i = 0; i < 8; i++, x >>= 4 )
	{
		s[ 7 - i ] = 'A' + ( TEXT ) ( x & 15 );
	}

	return( s + 8 );
}


static LONG
PackFile( STRPTR sourceName, struct xPackArgs *args )
{
	BPTR	file;
	LONG	error, size;

	if( args->FIB.fib_Protection & FIBF_DELETE )
	{
		Printf( "Skipping %s (delete protected)\n", sourceName );
		return( RETURN_OK );
	}

	if( args->FIB.fib_Size <= args->MinSize )
	{
		Printf( "Skipping %s (too small)\n", sourceName );
		return( RETURN_OK );
	}

	if( !( file = Open( sourceName, MODE_OLDFILE ) ) )
	{
		PrintfErr( "Couldn't open %s:\n", sourceName );
		return( RETURN_WARN );
	}

	if( !args->Force )
	{
		if( XpkExamineTags( args->XpkFIB,
			XPK_GetError,	args->XpkErrorBuffer,
			XPK_InFH,	file,
		TAG_DONE ) )
		{
			Printf( "XPK couldn't examine %s:\n%s\n", sourceName, args->XpkErrorBuffer );
			Close( file );
			return( RETURN_WARN );
		}

		if( args->XpkFIB->xf_Type != XPKTYPE_UNPACKED )
		{
			Printf( "Skipping %s (already crunched)\n", sourceName );
			Close( file );
			return( RETURN_OK );
		}
	}

	if( args->Program )
	{
		if( Read( file, args->XpkErrorBuffer, 20 ) != 20 )
		{
			Printf( "Skipping %s (to small for executable)\n", sourceName );
			Close( file );
			return( RETURN_OK );
		}

		if( GETLONG( args->XpkErrorBuffer, 0 ) != 0x3f3 )
		{
			Printf( "Skipping %s (not executable)\n", sourceName );
			Close( file );
			return( RETURN_OK );
		}

		if( ( GETLONG( args->XpkErrorBuffer, 3 ) !=0 ) ||
			( ( GETLONG( args->XpkErrorBuffer, 4 ) + 1 ) !=
				GETLONG( args->XpkErrorBuffer, 2 ) ) )
		{
			Printf( "Skipping %s (overlayed)\n", sourceName );
			Close( file );
			return( RETURN_OK );
		}

		SetIoErr( 0 );
		Seek( file, 0, OFFSET_BEGINNING );

		if( IoErr() )
		{
			PrintfErr( "Seek in %s failed: ", sourceName );
			Close( file );
			return( RETURN_WARN );
		}
	}

	if( args->Suffix )
	{
		ULONG	len;

		len = strlen( sourceName );
		strcpy( args->DestName, sourceName );
		strncpy( &args->DestName[ len ], args->Suffix,
			( size_t ) ( sizeof( args->DestName ) - len - 1 ) );
	}
	else
	{
		TempName( args->DestName );
	}

	size = 0;

	error = XpkPackTags(
		XPK_GetError,	args->XpkErrorBuffer,
		XPK_Password,	args->Password,
		XPK_PackMethod,	args->Method,
		XPK_ChunkHook,	args->Quiet ? NULL : &args->ChunkHook,
		XPK_FileName,	sourceName,
		XPK_InFH,	file,
		XPK_OutName,	args->DestName,
		XPK_GetOutLen,	&size,
		XPK_LossyOK,	args->Lossy,
	TAG_DONE );

	Close( file );

	if( error )
	{
		if( error == XPKERR_ABORTED )
		{
			PutStr( "***Break\n" );
			return( RETURN_OK );
		}
		else
		{
			Printf( "Couldn't compress %s:\n%s\n", sourceName, args->XpkErrorBuffer );
			return( RETURN_WARN );
		}
	}

	if( ( size> args->FIB.fib_Size ) && !args->Force )
	{
		DeleteFile( args->DestName );
		Printf( "Skipping %s (couldn't reduce size)\n", sourceName );
		return( RETURN_OK );
	}

	if( ( args->FIB.fib_Comment[ 0 ] == '\0' ) && args->xScan )
	{
		STRPTR	s;

		s = strcpy( args->FIB.fib_Comment, XFH_ID ) + XFH_ID_LEN;
		s = ULTA( s, args->FIB.fib_Date.ds_Days );
		s = ULTA( s, args->FIB.fib_Date.ds_Minute );
		s = ULTA( s, args->FIB.fib_Date.ds_Tick );
		s = ULTA( s, size);
		s = ULTA( s, args->FIB.fib_Size );
		*s = '\0';
	}

	if( args->FIB.fib_Comment[ 0 ] )
	{
		SetComment( args->DestName, args->FIB.fib_Comment );
	}

	if( args->FIB.fib_Protection )
	{
		SetProtection( args->DestName, args->FIB.fib_Protection );
	}

	SetFileDate( args->DestName, &args->FIB.fib_Date );

	if( args->Suffix )
	{
		return( RETURN_OK );
	}

	if( !DeleteFile( sourceName ) )
	{
		DeleteFile( args->DestName );
		PrintfErr( "Couldn't delete %s:\n", sourceName );
		return( RETURN_WARN );
	}

	if( !Rename( args->DestName, sourceName ) )
	{
		PrintfErr( "Couldn't rename %s to %s:\n", args->DestName, sourceName );
		return( RETURN_ERROR );
	}

	return( RETURN_OK );
}


LONG
UnPackFile( STRPTR sourceName, struct xPackArgs *args )
{
	BPTR	file;
	LONG	sameFile, error;

	if( args->FIB.fib_Protection & FIBF_DELETE )
	{
		Printf( "Skipping %s (delete protected)\n", sourceName );
		return( RETURN_OK );
	}

	if( !( file = Open( sourceName, MODE_OLDFILE ) ) )
	{
		PrintfErr( "Couldn't open %s\n", sourceName );
		return( RETURN_WARN );
	}

	if( XpkExamineTags( args->XpkFIB,
		XPK_GetError,	args->XpkErrorBuffer,
		XPK_InFH,	file,
	TAG_DONE ) )
	{
		Printf( "XPK couldn't examine %s:\n%s\n", sourceName, args->XpkErrorBuffer );
		Close( file );
		return( RETURN_OK );
	}

	if( args->XpkFIB->xf_Type != XPKTYPE_PACKED )
	{
		Printf( "Skipping %s (not compressed)\n", sourceName );
		Close( file );
		return( RETURN_OK );
	}

	TempName( args->DestName );
	sameFile = TRUE;

	if( args->Suffix )
	{
		LONG	l1, l2;

		l1 = strlen( args->FIB.fib_FileName );
		l2 = strlen( args->Suffix );

		if( ( l1 > l2 ) && !stricmp( &sourceName[ l1 - l2 ], args->Suffix ) )
		{
			strcpy( args->DestName, sourceName )[ l1 - l2 ] = '\0';
			sameFile = FALSE;
		}
	}

	error = XpkUnpackTags(
		XPK_GetError,	args->XpkErrorBuffer,
		XPK_Password,	args->Password,
		XPK_ChunkHook,	args->Quiet ? NULL : &args->ChunkHook,
		XPK_FileName,	sourceName,
		XPK_InFH,	file,
		XPK_OutName,	args->DestName,
	TAG_DONE );

	Close( file );

	if( error )
	{
		if( error == XPKERR_ABORTED )
		{
			PutStr( "***Break\n" );
			return( RETURN_OK );
		}
		else
		{
			Printf( "Couldn't decompress %s:\n%s\n", sourceName, args->XpkErrorBuffer );
			return( RETURN_WARN );
		}
	}

	if( args->FIB.fib_Comment[ 0 ] && strncmp( args->FIB.fib_Comment, XFH_ID, XFH_ID_LEN ) )
	{
		SetComment( args->DestName, args->FIB.fib_Comment );
	}

	if( args->FIB.fib_Protection )
	{
		SetProtection( args->DestName, args->FIB.fib_Protection );
	}

	SetFileDate( args->DestName, &args->FIB.fib_Date );

	if( !sameFile )
	{
		return( RETURN_OK );
	}

	if( !DeleteFile( sourceName ) )
	{
		DeleteFile( args->DestName );
		PrintfErr( "Couldn't delete %s:\n", sourceName );
		return( RETURN_WARN );
	}

	if( !Rename( args->DestName, sourceName ) )
	{
		PrintfErr( "Couldn't rename %s to %s:\n", args->DestName, sourceName );
		return( RETURN_WARN );
	}

	return( RETURN_OK );
}


__asm __saveds static LONG
ChunkFunc( register __a0 struct Hook *hook, register __a1 struct XpkProgress *progress )
{
	LONG	rc;

	if( progress->xp_Type == XPKPROG_START )
	{
		PutStr( "\x9b" "0 p" );
	}

	/* "aaaaaaaaa nnnnnnnnnnnnnn (uuuuuu bytes, ddd% done, cc% CF) " */
	Printf( "\r%-9s %-14s (%6ld bytes, %3ld%% done, %2ld%% CF) ",
		progress->xp_Activity, progress->xp_FileName,
		progress->xp_ULen, progress->xp_Done,
		progress->xp_CF );

	rc = CheckSignal( SIGBREAKF_CTRL_C );

	if( rc || ( progress->xp_Type == XPKPROG_END ) )
	{
		PutStr( "\n\x9b" "1 p" );
	}

	return( rc );
}


__asm static LONG
FileFunc( register __a0 struct Hook *hook, register __a1 STRPTR name )
{
	struct xPackArgs	*args;
	BPTR	lock;
	LONG	rc = RETURN_OK;

	args = hook->h_Data;

	if( lock = Lock( name, SHARED_LOCK ) )
	{
		if( !Examine( lock, &args->FIB ) )
		{
			Printf( "Couldn't examine %s\n", name );
			rc = RETURN_WARN;
		}

		UnLock( lock );
	}
	else
	{
		Printf( "Couldn't lock %s\n", name );
		rc = RETURN_WARN;
	}

	if( !rc )
	{
		if( !args->Method )
		{
			rc = UnPackFile( name, args );
		}
		else
		{
			rc = PackFile( name, args );
		}
	}

	return( rc );
}


LONG
Main( VOID )
{
	struct RDArgs		*rdArgs;
	struct xPackArgs	*args;
	LONG	rc = RETURN_FAIL;

	if( args = AllocVec( sizeof( struct xPackArgs ), MEMF_ANY | MEMF_CLEAR ) )
	{
		if( ( XpkBase->lib_Version < 4 ) ||
			( args->XpkFIB = XpkAllocObject( XPKOBJ_FIB, NULL ) ) )
		{
			if( rdArgs = ReadArgs( TEMPLATE_MSG, ( LONG * ) args, NULL ) )
			{
				struct Hook	fileHook;

				args->MinSize = args->MinSizeArg ? *args->MinSizeArg : 512;

				if( args->Password )
				{
					args->Force = TRUE;
				}

				fileHook.h_Entry = ( HOOKFUNC ) FileFunc;
				fileHook.h_Data = ( APTR ) args;
				args->ChunkHook.h_Entry = ( HOOKFUNC ) ChunkFunc;

				rc = ProcessFiles( args->Files, args->All, &fileHook );

				if( rc > RETURN_WARN )
				{
					PrintFault( IoErr(), NULL );
				}

				FreeArgs( rdArgs );
			}
			else
			{
				PrintFault( IoErr(), NULL );
				rc = RETURN_ERROR;
			}

			XpkFreeObject( XPKOBJ_FIB, args->XpkFIB );
		}
		else
		{
			PrintFault( IoErr(), NULL );
			rc = RETURN_ERROR;
		}

		FreeVec( args );
	}
	else
	{
		PrintFault( IoErr(), NULL );
	}

	return( rc );
}
