#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <string.h>
#include "untar.h"


/* Returns > 0 if lock refers to a drawer */
LOCAL LONG
IsDirLock( BPTR lock )
{
	struct FileInfoBlock	*fib;
	LONG	rc = -1;

	if( fib = AllocDosObject( DOS_FIB, NULL ) )
	{
		if( Examine( lock, fib ) )
		{
			rc = fib->fib_DirEntryType > 0;
		}

		FreeDosObject( DOS_FIB, fib );
	}

	return( rc );
}


/* PrintFault with Printf capabilities */
VOID
PrintfFault( LONG err, const STRPTR msg, ... )
{
	VPrintf( msg, &msg + 1 );
	Printf( ": " );
	PrintFault( err, NULL );
}


/* Set filenames date to the specified time with Unix base. Returns TRUE if
 * successfull
 */
LONG
SetTarTime( const STRPTR filename, LONG time )
{
	struct DateStamp	date;
	LONG	rc = FALSE;

	time -= UNIX2AMIGA_TIME + GMTOffset;

	date.ds_Days   = time / 86400;
	time %= 86400;
	date.ds_Minute = time / 60;
	time %= 60;
	date.ds_Tick   = time * TICKS_PER_SECOND;

	if( SetFileDate( filename, &date ) )
	{
		rc = TRUE;
	}
	else
	{
		PrintfFault( IoErr(), GetString( MSG_ERR_SET_TIME ) );
	}

	return( rc );
}


#define REVERSE_BIT_MASK	( FIBF_READ | FIBF_WRITE | FIBF_EXECUTE | FIBF_DELETE )

struct U2ABits
{
	UWORD	Unix;
	UWORD	Amiga;
};

LOCAL struct U2ABits U2AB[] =
{
	TM_UREAD,	FIBF_READ,
	TM_UWRITE,	FIBF_WRITE,
	TM_UEXEC,	FIBF_EXECUTE,
	TM_GREAD,	FIBF_GRP_READ,
	TM_GWRITE,	FIBF_GRP_WRITE,
	TM_GEXEC,	FIBF_GRP_EXECUTE,
	TM_OREAD,	FIBF_OTR_READ,
	TM_OWRITE,	FIBF_OTR_WRITE,
	TM_OEXEC,	FIBF_OTR_EXECUTE
};


/* Set filenames protection bits to the specified unix mode long. Returns
 * TRUE if successfull
 */
LONG
SetTarMode( const STRPTR filename, LONG mode )
{
	/* Always set delete bit */
	LONG	prot = FIBF_DELETE, rc = FALSE, i;

	/* "Copy" the bits to new format */
	for( i = 0; i < ( sizeof( U2AB ) / sizeof( struct U2ABits ) ); ++i )
	{
		if( mode & U2AB[ i ].Unix )
		{
			prot |= U2AB[ i ].Amiga;
		}
	}

	/* Reverse the RWED bits ("active low") */
	prot ^= REVERSE_BIT_MASK;

	if( SetProtection( filename, prot ) )
	{
		rc = TRUE;
	}
	else
	{
		PrintfFault( IoErr(), GetString( MSG_ERR_SET_MODE ) );
	}

	return( rc );
}


/* Recursive make drawer. Returns TRUE if successfull.
 * Ignores any drawers called ".".
 */
LONG
Makedir( STRPTR newdir )
{
	STRPTR	s;
	LONG	rc;

	if( !*newdir )
	{
		return( FALSE );
	}

	s = newdir;
	rc = TRUE;

	while( *s && rc )
	{
		STRPTR	part;
		BPTR	dir;
		TEXT	hold;

		part = s;

		while( *s && ( *s != '/' ) )
		{
			++s;
		}

		hold = *s;
		*s = 0;

		if( Stricmp( part, "." ) == 0 )
		{
			/* Nop */
		}
		else if( !( dir = Lock( newdir, SHARED_LOCK ) ) )
		{
			if( dir = CreateDir( newdir ) )
			{
				UnLock( dir );
			}
			else
			{
				PrintfFault( IoErr(), GetString( MSG_ERR_CREATE_DRAWER ), newdir );
				rc = FALSE;
			}
		}
		else
		{
			LONG	i = IsDirLock( dir );

			if( i == 0 )
			{
				*s = hold;
				Printf( GetString( MSG_ERR_CREATE_DRAWER ), newdir );
				*s = '\0';
				Printf( GetString( MSG_ERR_EXISTS_AS_FILE ), newdir );
				rc = FALSE;
			}
			else if( i < 0 )
			{
				PrintfFault( IoErr(), GetString( MSG_ERR_EXAMINE ), newdir );
				rc = FALSE;
			}

			UnLock( dir );
		}

		*s++ = hold;
	}

	return( rc );
}


/* Lock dirname and CD to it. Returns old current dir */
BPTR
ChangeDir( const STRPTR dirname )
{
	BPTR	dir;

	if( dir = Lock( dirname, SHARED_LOCK ) )
	{
		LONG	i = IsDirLock( dir );

		if( i > 0 )
		{
			return( CurrentDir( dir ) );
		}
		else if( i < 0 )
		{
			PrintfFault( IoErr(), GetString( MSG_ERR_EXAMINE ), dirname );
		}
		else
		{
			Printf( GetString( MSG_ERR_NOT_DRAWER ), dirname );
		}

		UnLock( dir );
	}
	else
	{
		/* Try to avoid silly error messages... ;) */
		if( IoErr() == ERROR_OBJECT_NOT_FOUND )
		{
			Printf( GetString( MSG_ERR_NOT_FOUND ), dirname );
		}
		else
		{
			PrintfFault( IoErr(), GetString( MSG_ERR_NOT_FOUND ), dirname );
		}
	}

	return( NULL );
}


/* This should match the order in the string MSG_OVERWRITE_REPLIES in the
 * catalog.
 */
enum OverwriteReplies
{
	OVERWRITE_YES = 0,
	OVERWRITE_NO,
	OVERWRITE_ALL,
	OVERWRITE_SKIPALL
};


/* If name already exists, return TRUE if it is OK to overwrite the file. */
LONG
OverwriteOk( const STRPTR name )
{
	STATIC	TEXT	LastAnswer;
	STRPTR	replies = GetString( MSG_OVERWRITE_REPLIES );
	BPTR	lock;
	LONG	rc = TRUE;

	if( lock = Lock( name, SHARED_LOCK ) )
	{
		LONG	i = IsDirLock( lock );

		if( i > 0 )
		{
			Printf( GetString( MSG_ERR_EXISTS_AS_DRAWER ), name );
			rc = FALSE;
		}
		else if( i < 0 )
		{
			PrintfFault( IoErr(), GetString( MSG_ERR_EXAMINE ), name );
			rc = FALSE;
		}
		else if( !IsInteractive( Input() ) )
		{
			/* Skip if no prompting possible */
			LastAnswer = replies[ OVERWRITE_SKIPALL ];
		}
		else if( ( LastAnswer != replies[ OVERWRITE_ALL ] ) &&
			( LastAnswer != replies[ OVERWRITE_SKIPALL ] ) )
		{
			BPTR	input = Input();

			/* Ask user what to do */
			Printf( GetString( MSG_OVERWRITE_PROMPT ), name );
			Flush( Output() );
			Flush( input );
			/* Read answer */
			SetMode( input, 1 );
			Read( input, &LastAnswer, 1 );
			SetMode( input, 0 );
			/* Echo answer */
			Printf( " %lc\n", LastAnswer );
			LastAnswer = ToLower( ( ULONG ) LastAnswer );
		}

		if( ( LastAnswer != replies[ OVERWRITE_YES ] ) &&
			( LastAnswer != replies[ OVERWRITE_ALL ] ) )
		{
			rc = FALSE;
		}

		UnLock( lock );
	}

	return( rc );
}


/* -- Pattern matching -------------------------------------------------- */


/* Free the pattern list */
VOID
FreePatterns( struct List *list )
{
	struct Node	*node;

	while( node = RemHead( list ) )
	{
		MemFree( node );
	}
}


/* Convert the NULL terminated array of STRPTR patterns to a list, where
 * ln_Name of each node points to a parsed pattern. lh_Type is TRUE if case
 * sensitive pattern matching should be done.
 */
LONG
ParsePatterns( struct List *list, const STRPTR *patterns, LONG caseSensitive )
{
	struct Node	*pat;
	LONG	rc = TRUE, len;

	NewList( list );
	list->lh_Type = caseSensitive;
	SetIoErr( 0 );

	if( !patterns )
	{
		return( rc );
	}

	for( ; *patterns; ++patterns )
	{
		if( pat = MemAlloc( len = sizeof( struct Node ) + strlen( *patterns ) * 2 + 3 ) )
		{
			pat->ln_Name = ( STRPTR ) ( pat + 1 );

			if( caseSensitive )
			{
				if( ParsePattern( *patterns, pat->ln_Name, len ) < 0 )
				{
					rc = FALSE;
					break;
				}
			}
			else
			{
				if( DOSBase->dl_lib.lib_Version < 39 )
				{
					/* Handle Pre-V39 bug */
					STRPTR	s = *patterns;

					while( *s )
					{
						*s = ToUpper( ( ULONG ) *s );
						++s;
					}
				}

				if( ParsePatternNoCase( *patterns, pat->ln_Name, len ) < 0 )
				{
					rc = FALSE;
					break;
				}
			}

			AddTail( list, pat );
		}
		else
		{
			rc = FALSE;
			break;
		}
	}

	if( !rc )
	{
		FreePatterns( list );
		PrintfFault( IoErr(), GetString( MSG_ERR_PARSE_PATTERNS ) );
	}

	return( rc );
}


/* Returns TRUE if name matches a pattern in the list */
BOOL
MatchName( const STRPTR name, struct List *patterns )
{
	struct Node	*pat;

	pat = patterns->lh_Head;

	if( !pat->ln_Succ )
	{
		/* Empty list -> always match */
		return( TRUE );
	}

	for( ; pat->ln_Succ; pat = pat->ln_Succ )
	{
		if( patterns->lh_Type ? MatchPattern( pat->ln_Name, name ) :
			MatchPatternNoCase( pat->ln_Name, name ) )
		{
			return( TRUE );
		}
	}

	return( FALSE );
}
