/**************************
**
**	$Id:	finder.c
**
**	File Finder Utility with wildcards for the Amiga Shopper C programming
**	course. Demonstrates recursive programming techniques and argument
**	passing.
**
**	By Toby Simpson
**
**	To compile under dice:
**		dcc finder.c -o finder
**	The resultant executable will be called "finder" To compile under
**	SAS C, copy the "starter_project" drawer to where you want, and
**	type the listing as "finder.c". Double click on "Build" to make the
**	executable.
**
**	Tested under DICE (Complete Amiga C version) and SAS C 6.51
**
**	NOTE FOR DICE USERS:
**		Earlier DICE releases (And some freeware versions) do not support
**		the __aligned keyword. The version with "Complete Amiga C" does.
*/

#define	FINDER_VERSION				"Finder 1.01 (06.10.94)"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <dos/dos.h>
#include <exec/exec.h>
#include <intuition/intuition.h>
#include <libraries/gadtools.h>

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/alib_protos.h>

/*
**	Defines:
*/
#define	TOTAL_GADGETS		4
#define BORDER					8
#define MIN_HEIGHT			130
#define MIN_WIDTH				320

#define GID_LIST				0
#define GID_QUIT				1
#define GID_FIND				2
#define GID_CANCEL			3

/*
**	Function prototypes:
*/
BOOL	SearchDir(char *directory, char *pattern);
BOOL	NotifyFind(char *file);
void	cleanexit(int returnvalue);
BOOL	OpenGUI(void);
void	CloseGUI(void);

/*
**	Global variables:
*/
long	files_matched	= 0;					/* Total files found */
char	*VERSION			= "\0$VER:"FINDER_VERSION;

struct Gadget *first_gadget, *context_gadget, *previous_gadget; 
struct Gadget *gadget_list[TOTAL_GADGETS];
struct Window *finder_window = NULL;

char	*button_text[] =
	{
	"_Quit", "_Find", "_Cancel", NULL
	};

/*
**	Library bases:
*/
struct Library *GadToolsBase = NULL;
struct Library *IntuitionBase = NULL;
struct List find_list;

/**************************
**
**	void	main(void)
**
**	Main program entry function.
*/

void	main(int argc, char *argv[])
{
	char	search_dir[64];
	char	search_string[64];
	char	search_pattern[128];
	
	/*
	**	Title us and parse arguments:
	*/
	printf("%s\n", FINDER_VERSION);

	if (argc != 3)
		{
		printf( "Argument count incorrect:\n"
						"Usage: FINDER path matchpattern\n");
		return;
		}

	strcpy(search_dir, *++argv);
	strcpy(search_string, *++argv);

	/*
	**	Initialise our list:
	*/
	NewList(&find_list);

	/*
	**	Open any libraries we might want:
	*/
	if (!(IntuitionBase = OpenLibrary("intuition.library", 37L)))
		{
		printf("Can't open intuition library V37\n");
		cleanexit(RETURN_FAIL);
		}
	if (!(GadToolsBase = OpenLibrary("gadtools.library", 37L)))
		{
		printf("Can't open gadtools.library V37\n");
		cleanexit(RETURN_FAIL);
		}

	if (!(OpenGUI()))
		{
		printf("Unable to open window\n");
		cleanexit(RETURN_FAIL);
		}

	printf("Scanning '%s' with a match string of '%s'\n",
		search_dir, search_string);

	/*
	**	Pre-Parse the AmigaDOS search pattern:
	*/
	ParsePatternNoCase(search_string, search_pattern, 127);

	/*
	**	Start the search:
	*/
	if (!(SearchDir(search_dir, search_pattern)))
		printf("Operation not totally successful.\n");

	/* THIS IS TEMPORARY SO YOU CAN SEE THE LIST */
	Delay(1000);

	/*
	**	End program stats:
	*/
	printf( "Operation complete, %ld matches found.\n", files_matched);

	cleanexit(0);				/* Exit with no error code */
}

/**************************
**
**	void	cleanexit(int returnvalue)
**
**	Exits the program, closing any allocated resources.
*/

void	cleanexit(int returnvalue)
{
	struct	Node			*node;

	/*
	**	Shut down any GUI components we opened:
	*/
	CloseGUI();
	
	/*
	**	Close libraries:
	*/
	if (IntuitionBase)	CloseLibrary(IntuitionBase);
	if (GadToolsBase)		CloseLibrary(GadToolsBase);

	/*
	**	Free our list:
	*/
	while (node = RemHead(&find_list))
		{
		free(node->ln_Name);
		free(node);
		}

	/*
	**	Exit program with correct error code:
	*/
	exit(returnvalue);
}

/**************************
**
**	BOOL	SearchDir(char *directory, char *pattern)
**
**	Search the named directory. All variables are local, this function
**	is recursive. Returns TRUE if the operation was OK, or FALSE for an
**	error.
*/

BOOL	SearchDir(char *directory, char *pattern)
{
#ifdef _DCC
	__aligned struct	FileInfoBlock		fib;
#else
	struct	FileInfoBlock	__aligned	fib;
#endif

	BPTR		lk 	= NULL;
	char		full_path[255];

	/*
	**	Attempt to get a lock and the initial FIB:
	*/
	if (!(lk = Lock(directory, ACCESS_READ)))	return FALSE;
	if (!(Examine(lk, &fib)))									return FALSE;

	/*
	**	Scan directory:
	*/
	while (ExNext(lk, &fib))
		{
		/*
		**	Deal with CTRL-C:
		*/
    if (SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
    	{
    	printf("*** Program Aborted\n");
			UnLock(lk);
    	return FALSE;
    	}
		
		/*
		**	Build full path spec:
		*/
		strcpy(full_path, directory);
		AddPart(full_path, fib.fib_FileName, 255);
		
		/*
		**	ID File entry type:
		*/
		if (fib.fib_DirEntryType > 0)
			{
			/*
			**	Got a directory, recursively scan it:
			*/
			if (!(SearchDir(full_path, pattern)))
				{
				UnLock(lk);
				return FALSE;
				}
			}
		else
			{
			/*
			**	Got a file, try a match check:
			*/
			if (MatchPatternNoCase(pattern, fib.fib_FileName))
				NotifyFind(full_path);
			}
		}

	UnLock(lk);

	return TRUE;
}

/**************************
**
**	BOOL	NotifyFind(char *file)
**
**	Notify that a file was found. The file which matched is passed in
**	and this is then shown on the screen in what every way the program
**	decides. Returns FALSE for an error. (This months version always
**	returns TRUE however)
*/

BOOL	NotifyFind(char *file)
{
	struct	Node	*node;
	char					*text_ptr;

	/*
	**	Count matches:
	*/
	files_matched++;
	
	/*
	**	Allocate memory for a list node:
	*/
	if (!(node = (struct Node *)malloc(sizeof(struct Node))))
		return FALSE;

	if (!(text_ptr = malloc(strlen(file) + 1)))
		{
		free(node);
		return FALSE;
		}

	/*
	**	Initialise our new node, and add it to the list:
	*/
	strcpy(text_ptr, file);
	
	node->ln_Name = text_ptr;
	node->ln_Pri = 0;

	AddTail(&find_list, node);

	/*
	**	Update the display gadget so it shows the new list:
	*/
	GT_SetGadgetAttrs(gadget_list[GID_LIST], finder_window, NULL,
		GTLV_Labels, &find_list,
		GTLV_Top, files_matched,
			TAG_DONE);

	return TRUE;
}

/**************************
**
**	BOOL	OpenGUI(void)
**
**	Opens the GUI components for our program. This means opening the
**	window with gadgets on it. Returns TRUE for success, FALSE for a 
**	failure.
*/

BOOL	OpenGUI(void)
{
	struct	Screen	*screen;
	void						*vi;
	long						gadget_count = 0;
	long						win_width, win_height, win_x, win_y;
	long						button_width, button_height, button_start;
	long						list_width, list_height, list_start;
	long						window_top, window_inner;
	struct	NewGadget ng;

	/*
	**	Get public screen information and visual info:
	*/
	if (!(screen = LockPubScreen(NULL)))
		{
		printf("Unable to lock default public screen.\n");
		return FALSE;
		}
	if (!(vi = GetVisualInfo(screen, TAG_DONE)))
		{
		printf("Unable to get visual information\n");
		return FALSE;
		}

	/*
	**	Knock up some sensible window dimensions:
	*/
	win_width = screen->Width / 3;
	win_height = screen->Height / 3;
	if (win_width < MIN_WIDTH) 		win_width = MIN_WIDTH;
	if (win_height < MIN_HEIGHT)	win_height = MIN_HEIGHT;

	win_x = (screen->Width / 2) - (win_width / 2);
	win_y = (screen->Height / 2) - (win_height / 2);

	/*
	**	Open the window we are going to use:
	*/
	if (!(finder_window = OpenWindowTags(NULL,
			WA_Title,				"Finder",
			WA_Left,				win_x,
			WA_Top,					win_y,
			WA_Width,				win_width,
			WA_Height,			win_height,
			WA_RMBTrap,			TRUE,
			WA_NewLookMenus,TRUE,
			WA_Activate,		TRUE,
			WA_CloseGadget,	TRUE,
			WA_DepthGadget,	TRUE,
			WA_DragBar,			TRUE,
			WA_IDCMP,				IDCMP_REFRESHWINDOW |
											IDCMP_CLOSEWINDOW |
											LISTVIEWIDCMP |
											IDCMP_GADGETUP |
											BUTTONIDCMP |
											IDCMP_VANILLAKEY |
											IDCMP_RAWKEY,
				TAG_END)))
					{
					printf("Unable to open window!\n");
					return FALSE;
					}

	/*
	**	Create context gadget:
	*/
	first_gadget = NULL;
	context_gadget = CreateContext(&first_gadget);

	/*
	**	Set up defaults:
	*/
	memset(&ng, 0, sizeof(struct NewGadget));
	ng.ng_VisualInfo = vi;
	ng.ng_Flags = 0;
	ng.ng_UserData = NULL;
	ng.ng_TextAttr = screen->Font;
	ng.ng_GadgetID = 0;
	previous_gadget = context_gadget;

	window_top = finder_window->BorderTop + BORDER;
	window_inner = win_height - (window_top) - (finder_window->BorderBottom + BORDER);

	list_width = win_width - (BORDER * 2);
	button_width = (list_width / 3) - BORDER;

	button_height = screen->Font->ta_YSize + BORDER;
	list_height = window_inner - button_height - (BORDER * 2);

	list_start = window_top;
	button_start = window_top + list_height + BORDER;

	/*
	**	Create list-view first:
	*/
	ng.ng_TopEdge 		= list_start;
	ng.ng_LeftEdge 		=	BORDER;
	ng.ng_Width 			= list_width;
	ng.ng_Height 			= list_height;
	
	gadget_list[gadget_count] = CreateGadget(LISTVIEW_KIND, previous_gadget,
		&ng, GT_Underscore, '_', TAG_DONE);

	previous_gadget = gadget_list[gadget_count];
	gadget_count++;

	/*
	**	Now create our buttons:
	*/
	ng.ng_LeftEdge = BORDER;
	ng.ng_TopEdge = button_start;
	ng.ng_Width = button_width;
	ng.ng_Height = button_height;
	ng.ng_GadgetID = ng.ng_GadgetID + 1;

	while (button_text[gadget_count-1])
		{
		ng.ng_GadgetText = button_text[gadget_count-1];

		gadget_list[gadget_count] = CreateGadget(BUTTON_KIND, previous_gadget,
			&ng, GT_Underscore, '_', TAG_DONE);

		previous_gadget = gadget_list[gadget_count];
		gadget_count++;
	
		ng.ng_LeftEdge += (button_width + BORDER);
		ng.ng_GadgetID = ng.ng_GadgetID + 1;
		}

	/*
	**	Fall over if gadgets were not created right:
	*/
	if (first_gadget == NULL)	return FALSE;

	/*
	**	Add our buttons to the window:
	*/
	AddGList(finder_window, first_gadget, 0, ~0, NULL);
	RefreshGList(first_gadget, finder_window, NULL, ~0);

	GT_RefreshWindow(finder_window, NULL);

	return TRUE;
}

/**************************
**
**	void	CloseGUI(void)
**
**	Closes any GUI components we opened, such as the window or gadgets
**	for example.
*/

void	CloseGUI(void)
{
	if (finder_window)	CloseWindow(finder_window);
	if (first_gadget)		FreeGadgets(first_gadget);
}
