/* RndPat
 *
 * Random Workbench background picture selector.
 *
 * Public domain in 1996 by Magnus Holmgren.
 */

#include "RndPat.h"


/******************** Useful macros ********************/


#define FOREACHNODE(list,node,next)     \
	for( node = ( APTR ) ( ( ( struct List * ) ( list ) )->lh_Head );       \
		( next = ( APTR ) ( ( ( struct Node * ) node )->ln_Succ ) );    \
		node = next )
#define MIN(a,b)	( ( a ) < ( b ) ? ( a ) : ( b ) )


/******************** Global variables ********************/


struct DosLibrary	*DOSBase;
struct Library		*IFFParseBase;
struct IntuitionBase	*IntuitionBase;
struct ExecBase		*SysBase;
struct Library		*UtilityBase;


/******************** Startup code ********************/


#define CloseLib(l)	CloseLibrary( ( struct Library * ) l )

STATIC APTR	OpenLib( const STRPTR, const LONG );
STATIC LONG	Main( VOID );


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

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

	if( DOSBase = ( struct DosLibrary * ) OpenLibrary( "dos.library", 39 ) )
	{
		if( IntuitionBase = OpenLib( "intuition.library", 39 ) )
		{
			if( UtilityBase = OpenLib( "utility.library", 39 ) )
			{
				if( IFFParseBase = OpenLib( "iffparse.library", 39 ) )
				{
					rc = Main();

					CloseLib( IFFParseBase );
				} /* if */

				CloseLib( UtilityBase );
			} /* if */

			CloseLib( IntuitionBase );
		} /* if */

		CloseLib( DOSBase );
	} /* if */

	return( rc );
} /* Startup */


STATIC APTR
OpenLib( const STRPTR name, const LONG version )
{
	APTR	lib;

	if( !( lib = OpenLibrary( name, version ) ) )
	{
		Printf( "RndPat: Couldn't open %s version %ld or higher\n", name, version );
	} /* if */

	return( lib );
} /* OpenLib */


const STATIC TEXT Version[] = "$VER: RndPat 1.0 (27.10.96)";


/******************** Random stuff ********************/


STATIC ULONG	Seed;


/* Basically FastRand() in amiga.lib */
STATIC ULONG
Rand( const ULONG limit )
{
	Seed = ( Seed << 1 ) ^ 0x1D872B41;
	return( Seed % limit );
} /* Rand */


STATIC VOID
InitSeed( VOID )
{
	ULONG	t;

	CurrentTime( &Seed, &t );
} /* InitSeed */


/******************** Random file stuff ********************/


/* Buffer size for fairly quick drawer scanning,
 * but without excessive memory needs.
 */
#define EXALLBUF 2048


/* Allocate a node, with name stored in ln_Name.
 * Use FreeVec() to free the node.
 */
STATIC struct Node *
AllocNameNode( const STRPTR name )
{
	struct Node	*node;

	if( node = AllocVec( ( ULONG ) ( sizeof( struct Node ) + ( strlen( name ) + 1 ) ), MEMF_ANY ) )
	{
		node->ln_Name = ( STRPTR ) ( node + 1 );
		strcpy( node->ln_Name, name );
	} /* if */

	return( node );
} /* AllocNameNode */


/* Scan the specified dir (only accepting the files that match the parsed
 * pattern, which may be NULL, in which case all files match), and add the
 * resulting file names to the end of list (ln_Name holds the name).
 *
 * Returns number of files found, or -1 for an error. Check IoErr() for
 * reason.
 *
 * To free a node, simply call FreeVec() on it.
 */
STATIC LONG
ScanDir( struct List *list, const STRPTR dir, const STRPTR pattern )
{
	struct ExAllData    *eaData;
	LONG	numFiles = -1;

	if( eaData = AllocVec( EXALLBUF, MEMF_PUBLIC | MEMF_CLEAR ) )
	{
		struct ExAllControl *eac;

		if( eac = AllocDosObject( DOS_EXALLCONTROL, NULL ) )
		{
			BPTR    lock;

			if( lock = Lock( dir, SHARED_LOCK ) )
			{
				struct Node		*node;
				struct ExAllData	*ead;
				BOOL	more;

				numFiles = 0;
				eac->eac_LastKey = 0;
				eac->eac_MatchString = pattern;

				do
				{
					more = ExAll( lock, eaData, EXALLBUF, ED_TYPE, eac );

					if( !more && ( IoErr() != ERROR_NO_MORE_ENTRIES ) )
					{
						break;
					} /* if */

					if( !eac->eac_Entries )
					{
						continue;
					} /* if */

					numFiles += eac->eac_Entries;

					for( ead = eaData; ead; ead = ead->ed_Next )
					{
						/* Ignore any drawers found. It won't
						 * handle soft links, but I don't want to
						 * mess around with that here and now! ;)
						 */
						if( ead->ed_Type > 0 )
						{
							continue;
						}
						else if( node = AllocNameNode( ead->ed_Name ) )
						{
							AddTail( list, node );
						}
						else
						{
							/* End processing */
							ExAllEnd( lock, eaData, EXALLBUF, ED_TYPE, eac );
							more = FALSE;	/* Break out of loop now */
							break;
						} /* if */
					} /* for */
				} while( more );

				if( !node )
				{
					SetIoErr( ERROR_NO_FREE_STORE );
				} /* if */

				if( IoErr() != ERROR_NO_MORE_ENTRIES )
				{
					numFiles = -1;
				} /* if */

				UnLock( lock );
			} /* if */

			FreeDosObject( DOS_EXALLCONTROL, eac );
		} /* if */

		FreeVec( eaData );
	} /* if */

	return( numFiles );
} /* ScanDir */


/* Free a list of AllocVec:ed nodes */
STATIC VOID
FreeList( struct List *list )
{
	struct Node	*node, *next;

	FOREACHNODE( list, node, next )
	{
		FreeVec( node );
	} /* FOREACHNODE */

	NewList( list );
} /* FreeList */


/* Assumes name refers to a directory, possibly with a filename part of name
 * that is a pattern. Scans the directory the name and file pattern refers
 * to, and randomly select one of the files. If the name does contain a
 * pattern, it will be removed.
 *
 * Returns a struct Node, with ln_Name set to the file name, or NULL.
 * FreeVec() the node to get rid of it.
 *
 * In case of NULL return, IoErr() contains more information.
 */
STATIC struct Node *
GetRandomFile( STRPTR name )
{
	struct List	fileList;
	struct Node	*rc = NULL;
	STRPTR	pattern, file;
	LONG	i, numFiles;

	NewList( &fileList );
	file = FilePart( name );
	i = ( strlen( file ) + 1 ) * 2 * sizeof( TEXT );

	if( pattern = AllocVec( i, MEMF_PUBLIC ) )
	{
		if( 1 != ParsePatternNoCase( file, pattern, i ) )
		{
			FreeVec( pattern );
			pattern = NULL;
		}
		else
		{
			/* Remove pattern from directory name */
			*file = '\0';
		} /* if */
	} /* if */

	if( ( numFiles = ScanDir( &fileList, name, pattern ) ) > 0 )
	{
		i = Rand( numFiles ) + 1;
		rc = fileList.lh_Head;

		while( --i )
		{
			/* Go to the right file */
			rc = rc->ln_Succ;
		} /* while */

		Remove( rc );
		FreeList( &fileList );
	} /* if */

	FreeVec( pattern );
	return( rc );
} /* GetRandomFile */


/******************** WBPattern prefs stuff ********************/


#define IFFERR_NOFILE	IFF_RETURN2CLIENT


/* Return error string based on input IFF error code. Assumes code to be
 * different from zero, and within range.
 */
STATIC const STRPTR
IffError( const LONG code )
{
	const STATIC STRPTR
	ErrorStrings[] =
	{
		"End of file",
		"End of chunk",
		"No valid scope for property",
		"Not enough memory",
		"Read error",
		"Write error",
		"Seek error",
		"Mangled file",
		"Syntax error",
		"Not an IFF file",
		"No hook provided",
		"Couldn't open file"
	}; /* ErrorStrings */

	return( ErrorStrings[ ~code ] );
} /* IffError */


/* Hm.. Not the best macro perhaps, but... ;) */
#define IFF(x)	if( ( rc = x ) < 0 ) { return( rc ); }


/* Write the actual prefs data. Returns IFF error code */
STATIC LONG
WriteWBPatternPrefs( struct IFFHandle *iff, STRPTR *patterns, const BOOL remap )
{
	struct PrefHeader	header;
	struct WBPatternPrefs	prefs;
	LONG	rc, i;

	/* Clear prefs structs */
	memset( &header, 0, sizeof( header ) );
	memset( &prefs, 0, sizeof( prefs ) );

	if( remap )
	{
		prefs.wbp_Flags = WBPF_NOREMAP;
	} /* if */

	IFF( OpenIFF( iff, IFFF_WRITE ) );
	IFF( PushChunk( iff, ID_PREF, ID_FORM, IFFSIZE_UNKNOWN ) );
	IFF( PushChunk( iff, ID_PREF, ID_PRHD, sizeof( header ) ) );
	IFF( WriteChunkBytes( iff, &header, sizeof( header ) ) );
	IFF( PopChunk( iff ) );

	for( i = WBP_ROOT; i <= WBP_SCREEN; ++i, ++patterns )
	{
		if( *patterns )
		{
			prefs.wbp_Which = i;
			prefs.wbp_DataLength = strlen( *patterns ) + 1;

			IFF( PushChunk( iff, ID_PREF, ID_PTRN, ( LONG ) ( sizeof( prefs ) + prefs.wbp_DataLength ) ) );
			IFF( WriteChunkBytes( iff, &prefs, sizeof( prefs ) ) );
			IFF( WriteChunkBytes( iff, *patterns, ( LONG ) prefs.wbp_DataLength ) );
			IFF( PopChunk( iff ) );
		} /* if */
	} /* for */

	return( PopChunk( iff ) );
} /* WriteWBPatternPrefs */


STATIC LONG
WritePrefs( const STRPTR file, const STRPTR *patterns, const BOOL remap )
{
	struct IFFHandle	*iff;
	LONG	rc = IFFERR_NOMEM;

	if( iff = AllocIFF() )
	{
		if( iff->iff_Stream = Open( file, MODE_NEWFILE ) )
		{
			InitIFFasDOS( iff );

			if( !( rc = OpenIFF( iff, IFFF_WRITE ) ) )
			{
				rc = WriteWBPatternPrefs( iff, patterns, remap );
				CloseIFF( iff );
			} /* if */

			if( !Close( iff->iff_Stream ) )
			{
				rc = FALSE;
			} /* if */
		}
		else
		{
			rc = IFFERR_NOFILE;
		} /* if */

		FreeIFF( iff );
	} /* if */

	return( rc );
} /* WritePrefs */


STATIC LONG
ShowWBPatternPrefs( struct IFFHandle *iff )
{
	struct CollectionItem	*ci = NULL;
	LONG	rc;

	/* Load and store all PTRN chunks */
	IFF( CollectionChunk( iff, ID_PREF, ID_PTRN ) );

	/* Stop before leaving PREF chunk, so we can process the stored PTRN chunks. */
	IFF( StopOnExit( iff, ID_PREF, ID_FORM ) );

	/* Read the stuff */
	rc = ParseIFF( iff, IFFPARSE_SCAN );

	if( rc == IFFERR_EOC )
	{
		/* Get the last loaded chunk */
		ci = FindCollection( iff, ID_PREF, ID_PTRN );
		rc = 0;
	} /* if */

	/* Process all chunks */
	while( ci )
	{
		const STATIC STRPTR
		Types[] =
		{
			"Workbench", "Window   ", "Screen   "
		}; /* Types[] */
		struct WBPatternPrefs	*chunk;
		STRPTR	type;

		chunk = ( struct WBPatternPrefs * ) ci->ci_Data;
		type = Types[ chunk->wbp_Which ];

		if( ci->ci_Size < sizeof( struct WBPatternPrefs ) )
		{
			Printf( "%s pattern chunk is too small (%ld bytes)\n", type, ci->ci_Size );
		}
		else if( chunk->wbp_Flags & WBPF_PATTERN )
		{
			Printf( "%s pattern is not a picture\n", type );
		}
		else if( !chunk->wbp_DataLength )
		{
			Printf( "%s picture: Empty name\n", type );
		}
		else
		{
			LONG	len;

			/* Make sure we only print the string... */
			len = strlen( ( STRPTR ) ( chunk + 1 ) );
			len = MIN( len, chunk->wbp_DataLength );

			/* Name data is stored after the WBPatternPrefs structure */
			Printf( "%s picture: ", type );
			FWrite( Output(), chunk + 1, len, 1 );
			PutStr( "\n" );
		} /* if */

		ci = ci->ci_Next;
	} /* while */

	return( rc );
} /* ShowWBPatternPrefs */


STATIC LONG
ShowPrefs( const STRPTR file )
{
	struct IFFHandle	*iff;
	LONG	rc = IFFERR_NOMEM;

	if( iff = AllocIFF() )
	{
		if( iff->iff_Stream = Open( file, MODE_OLDFILE ) )
		{
			InitIFFasDOS( iff );

			if( !( rc = OpenIFF( iff, IFFF_READ ) ) )
			{
				rc = ShowWBPatternPrefs( iff );
				CloseIFF( iff );
			} /* if */

			Close( iff->iff_Stream );
		}
		else
		{
			rc = IFFERR_NOFILE;
		} /* if */

		FreeIFF( iff );
	} /* if */

	return( rc );
} /* ShowPrefs */


/******************** Main ********************/


struct Args
{
	STRPTR	Workbench;
	STRPTR	Window;
	STRPTR	Screen;
	STRPTR	File;
	LONG	NoRemap;
	LONG	Show;
}; /* struct Args */

#define TEMPLATE	"WORKBENCH,WINDOW,SCREEN,FILE/K,NOREMAP/S,SHOW/S"


STATIC STRPTR
GetPattern( STRPTR dir )
{
	struct Node	*node = NULL;
	STRPTR	rc = NULL;
	LONG	i;

	if( dir && !IoErr() )
	{
		if( !( node = GetRandomFile( dir ) ) )
		{
			i = IoErr();
			Printf( "RndPat: Error scanning drawer \"%s\":\n", dir );
			PrintFault( i, NULL );
		}
		else if( rc = AllocVec( i = strlen( dir ) + strlen( node->ln_Name ) + 2, MEMF_PUBLIC ) )
		{
			strcpy( rc, dir );
			AddPart( rc, node->ln_Name, i );
			SetIoErr( 0 );
		}
		else
		{
			PutStr( "RndPat: Not enough memory\n" );
			SetIoErr( ERROR_NO_FREE_STORE );
		} /* if */
	} /* if */

	FreeVec( node );
	return( rc );
} /* GetPattern */


STATIC LONG
Main( VOID )
{
	struct RDArgs	*rda;
	struct Args	args;
	LONG	rc = RETURN_ERROR, err;

	memset( &args, 0, sizeof( args ) );
	args.File = "Env:sys/wbpattern.prefs";

	if( rda = ReadArgs( TEMPLATE, ( LONG * ) &args, NULL ) )
	{
		if( args.Show )
		{
			if( err = ShowPrefs( args.File ) )
			{
				Printf( "RndPat: Couldn't show \"%s\":\n%s\n", args.File, IffError( err ) );
			}
			else
			{
				rc = RETURN_OK;
			} /* if */
		}
		else
		{
			InitSeed();
			SetIoErr( 0 );

			if( args.Screen || args.Workbench || args.Window )
			{
				STRPTR	patterns[ 3 ];

				patterns[ 0 ] = GetPattern( args.Workbench );
				patterns[ 1 ] = GetPattern( args.Window    );
				patterns[ 2 ] = GetPattern( args.Screen    );

				if( !IoErr() )
				{
					if( err = WritePrefs( args.File, patterns, ( BOOL ) args.NoRemap ) )
					{
						Printf( "RndPat: Couldn't create \"%s\":\n%s\n", args.File, IffError( err ) );
					}
					else
					{
						rc = RETURN_OK;
					} /* if */
				} /* if */

				FreeVec( patterns[ 0 ] );
				FreeVec( patterns[ 1 ] );
				FreeVec( patterns[ 2 ] );
			}
			else
			{
				PutStr( "RndPat: No pattern(s) specified\n" );
				rc = RETURN_WARN;
			} /* if */
		} /* if */

		FreeArgs( rda );
	} /* if */

	return( rc );
} /* Main */
