/* Rnd.c
 *
 * Random Workbench/Opus background picture selector.
 *
 * Public domain in 1996-97 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 )


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


STATIC ULONG	Seed;


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


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 */


/* ----- Main stuff ------------------------------------------------------- */


STATIC LONG
GetEntryType( STRPTR name, LONG def )
{
	BPTR	lock;

	if( lock = Lock( name, SHARED_LOCK ) )
	{
		struct FileInfoBlock	*fib;

		if( fib = AllocDosObject( DOS_FIB, NULL ) )
		{
			if( Examine( lock, fib ) )
			{
				SetIoErr( 0 );
				def = fib->fib_DirEntryType;
			} /* if */

			FreeDosObject( DOS_FIB, fib );
		} /* if */

		UnLock( lock );
	} /* if */

	return( def );
} /* GetEntryType */


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

	if( dir && !IoErr() )
	{
		if( GetEntryType( dir, 1 ) < 0 )
		{
			/* Name refers to a file */
			return( dir );
		}
		else if( IoErr() )
		{
			PrintfFault( IoErr(), "%s: Couldn't examine \"%s\":\n", ProgramName, dir );
		}
		else if( !( node = GetRandomFile( dir ) ) )
		{
			PrintfFault( IoErr(), "%s: Error scanning drawer \"%s\":\n", ProgramName, dir );
		}
		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
		{
			PrintfFault( ERROR_NO_FREE_STORE, "%s: Not enough memory\n", ProgramName );
		} /* if */
	} /* if */

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