/**************************
**
**	$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.03 (05.01.95)"

#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 <rexx/errors.h>
#include <rexx/rexxio.h>
#include <rexx/rxslib.h>
#include <rexx/storage.h>

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

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

#define GID_LIST				0
#define GID_DRAWER			1
#define	GID_SEARCH			2
#define GID_QUIT				3
#define GID_FIND				4
#define GID_CANCEL			5

#define	AREXX_PORT_NAME	"Finder"

#define	AREXX_QUIT			0
#define AREXX_FIND			1
#define AREXX_SETSEARCH	2
#define AREXX_SETDRAWER	3					/* ARexx command equates */

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

/*
**	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 *sg_Search, *sg_Drawer;
struct Gadget *gadget_list[TOTAL_GADGETS];
struct Window *finder_window = NULL;

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

struct List find_list;

struct MsgPort	*arexx_port = NULL;

char 	*arexx_commands[] =
	{
	"QUIT", "FIND", "SETSEARCH", "SETDRAWER", NULL
	};

/*
**	Library bases:
*/
struct Library *GadToolsBase = NULL;
struct Library *IntuitionBase = NULL;
struct Library *RexxSysBase = NULL;

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

void	main(void)
{
	/*
	**	Title us and parse arguments:
	*/
	printf("%s\n", FINDER_VERSION);

	/*
	**	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 (!(RexxSysBase = OpenLibrary("rexxsyslib.library", 0L)))
		{
		printf("Can't open rexxsyslib.library\n");
		cleanexit(RETURN_FAIL);
		}

	/*
	**	Open our ARexx port:
	*/
	Forbid();
	if (FindPort(AREXX_PORT_NAME) == NULL)
		arexx_port = CreatePort(AREXX_PORT_NAME, 0);
	Permit();
	if (!arexx_port)
		{
		printf("Cannot create ARexx port. An application may already\n");
		printf("running with a port name of 'finder'\n");
		cleanexit(RETURN_FAIL);
		}

	/*
	**	Open our GUI (Window, buttons, etc)
	*/
	if (!(OpenGUI("", "")))
		{
		printf("Unable to open window\n");
		cleanexit(RETURN_FAIL);
		}

	/*
	**	Call window Event Handler:
	*/
	EventLoop("", "");

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

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

void	cleanexit(int returnvalue)
{
	struct	RexxMsg	*msg;

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

	/*
	**	Remove ARexx port, if open:
	*/
	if (arexx_port)	
		{
		/*
		**	We must remove all pending ARexx messages first, this
		**	must be done with no risk of more arriving, hence the Forbid and
		**	Permit:
		*/
		Forbid();
		while ((msg = (struct RexxMsg *) GetMsg(arexx_port)) != NULL)
			{
			msg->rm_Result1 = RC_FATAL;
			msg->rm_Result2 = NULL;
			ReplyMsg((struct Message *) msg);
			}
			
		DeletePort(arexx_port);
		Permit();
		}

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

/**************************
**
**	void	ClearGUI_List(void)
**
**	Clears out any memory occupied by the list we build of any matches
**	found during the find operation.
*/

void	ClearGUI_List(void)
{
	struct	Node	*node;

	/*
	**	Clear out list if anything in it:
	*/
	while (node = RemHead(&find_list))
		{
		free(node->ln_Name);
		free(node);
		}

	NewList(&find_list);		/* Re-initialise list in case it is used again */

	return;
}

/**************************
**
**	BOOL	OpenGUI(char *in_Drawer, char *in_Search)
**
**	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(char *in_Drawer, char *in_Search)
{
	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						string_start, string_width;
	long						window_top, window_inner;
	int							count = 0;
	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 / 2;
	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 * 3) - (BORDER * 2); 	/* CHG */

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

	string_width = (button_width * 2) + BORDER;
	string_start = button_width + (BORDER * 2);

	/*
	**	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 = string_start;
	ng.ng_TopEdge = button_start;
	ng.ng_Height = button_height;
	ng.ng_GadgetID = ng.ng_GadgetID + 1;
	ng.ng_Width = string_width;

	/*
	**	Put a string gadget in:
	*/
	ng.ng_GadgetText = "_Drawer";

	gadget_list[gadget_count] = CreateGadget(STRING_KIND, previous_gadget, &ng,
		GTST_MaxChars, 255,
		GTST_String, in_Drawer,
		GT_Underscore, '_', TAG_DONE);

	sg_Drawer = gadget_list[gadget_count];

	previous_gadget = gadget_list[gadget_count];
	gadget_count++;

	ng.ng_GadgetID = ng.ng_GadgetID + 1;
	ng.ng_TopEdge += button_height;

	ng.ng_GadgetText = "_Search";

	gadget_list[gadget_count] = CreateGadget(STRING_KIND, previous_gadget, &ng,
		GTST_MaxChars, 255,
		GTST_String, in_Search,
		GT_Underscore, '_', TAG_DONE);

	sg_Search = gadget_list[gadget_count];

	previous_gadget = gadget_list[gadget_count];
	gadget_count++;

	ng.ng_GadgetID = ng.ng_GadgetID + 1;

	ng.ng_LeftEdge = BORDER;
	ng.ng_TopEdge += (button_height + BORDER);
	ng.ng_Width = button_width;

	count = 0;
	while (button_text[count++])
		{
		ng.ng_GadgetText = button_text[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);

	return;
}

/**************************
**
**	void	GUI_Find(void)
**
**	This routine is responsible for triggering a find operation. It is
**	called if the FIND button, or its keyboard short-cut is pressed.
*/

void	GUI_Find(void)
{
	char		search_dir[64];
	char		search_string[64];
	char		search_pattern[128];
	char		work_string[128];

	/*
	**	Clear the current list, if any:
	*/
	ClearGUI_List();

	GT_SetGadgetAttrs(gadget_list[GID_LIST], finder_window, NULL,
		GTLV_Labels, NULL,
			TAG_DONE);
	files_matched = 0;
							
	/*
	**	Pull out search strings:
	*/
	NotifyFind("");
	NotifyFind("-------------------");

	strcpy(search_dir, ((struct StringInfo *)sg_Drawer->SpecialInfo)->Buffer);
	strcpy(search_string, ((struct StringInfo *)sg_Search->SpecialInfo)->Buffer);
	sprintf(work_string, "Searching '%s' for '%s'", search_dir, search_string);
	NotifyFind(work_string);

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

	/*
	**	Start the search:
	*/
	if (!(SearchDir(search_dir, search_pattern)))
		NotifyFind("-- Operation not totally successful.");
	else
		{
		sprintf(work_string, "-- %ld matches found", files_matched - 3);
		NotifyFind(work_string);
		}

	/*	
	**	Operation complete, return now.
	*/
	return;
}

/**************************
**
**	void	EventLoop(char *in_Drawer, char *in_Search)
**
**	Main Event Handler
*/

void	EventLoop(char *in_Drawer, char *in_Search)
{
	char		key_pressed;
	char		*arexx_command;
	char		*arexx_parameter;

	long		command_id;
	BOOL		command_found = FALSE;

	struct	RexxMsg				*msg;
	struct	IntuiMessage	*imsg;
	struct	Gadget				*gad;
	BOOL		quit_program	= FALSE;

	ULONG		arexx_signal, window_signal, signal_mask, signals;
	
	/*
	**	Build initial signal mask:
	*/
	arexx_signal 		= 1L << arexx_port->mp_SigBit;
	window_signal 	= 1L << finder_window->UserPort->mp_SigBit;
	signal_mask 		= arexx_signal + window_signal;

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

	/*
	**	Process events:
	*/
	while (!quit_program)
		{
		/*
		**	Wait for Message to Arrive from one of our ports:
		*/
		signals = Wait(signal_mask);

		/*
		**	Decide on which port triggered the signal:
		*/
		if (signals & arexx_signal)
			{
			/*************************************
			**
			**	ARexx Message(s) received:
			**
			**	Loop through processing events:
			*/
			while (msg = (struct RexxMsg *) GetMsg(arexx_port))
				{
				/*
				**	Check if it is an RXCOMM (Rexx Command) message:
				*/
				if ( (msg->rm_Action & RXCODEMASK) == RXCOMM)
					{
					/*
					**	Process this command, and grab parameter pointer
					**	also whilst we're at it:
					*/
					arexx_command = msg->rm_Args[0];
					if (arexx_parameter = strchr(arexx_command, ' '))
						{
						*arexx_parameter = 0;
						arexx_parameter++;
						}

					command_found = FALSE;
					command_id = 0;

					while (arexx_commands[command_id])
						{
						if ( !(strcmp(arexx_command, arexx_commands[command_id])) )
							{
							command_found = TRUE;
							break;
							}
							
						command_id++;
						}
					
					if (command_found)
						{
						/*
						**	Got a valid command, process it:
						*/
						switch(command_id)
							{
							case AREXX_QUIT:
								quit_program = TRUE;
								break;
								
							case AREXX_FIND:
								GUI_Find();
								break;
								
							case AREXX_SETSEARCH:
								GT_SetGadgetAttrs(gadget_list[GID_SEARCH], finder_window, NULL,
									GTST_String, arexx_parameter,
									TAG_DONE);
								break;
								
							case AREXX_SETDRAWER:
								GT_SetGadgetAttrs(gadget_list[GID_DRAWER], finder_window, NULL,
									GTST_String, arexx_parameter,
									TAG_DONE);
								break;
							}

						/*
						**	Set result string (none in our case currently, so we send
						**	a dummy message to show it is working)
						*/
						msg->rm_Result1 = RC_OK;
						
						if (msg->rm_Action & RXFF_RESULT)
							msg->rm_Result2 = (long) CreateArgstring("Hello from Finder!", 18);
						}
					else
						{
						/*
						**	Unknown ARexx command:
						*/
						msg->rm_Result1 = RC_FATAL;
						msg->rm_Result2 = NULL;
						}
					}
				
				ReplyMsg((struct Message *) msg);		/* Dealt with, reply to it now */
				}
			}

		if (signals & window_signal)
			{
			/*************************************
			**
			**	Window Message(s) received.
			**
			**	Loop through processing events:
			*/
			while (imsg = GT_GetIMsg(finder_window->UserPort))
				{
				gad = (struct Gadget *)imsg->IAddress;

				switch(imsg->Class)
					{
					/*
					**	Window Refresh Case:
					*/
					case IDCMP_REFRESHWINDOW:
						GT_BeginRefresh(finder_window);
						GT_EndRefresh(finder_window, TRUE);
						break;

					/*
					**	User clicked on close gadget:
					*/
					case IDCMP_CLOSEWINDOW:
						quit_program = TRUE;
						break;

					/*					
					**	User pressed a key:
					*/
					case IDCMP_VANILLAKEY:
						/*
						**	We don't care about case, so set this key to lower case
						**	if it is a capital.
						*/
						key_pressed = imsg->Code;
						if (key_pressed >= 'A' && key_pressed <= 'Z')
							key_pressed = key_pressed + 0x20;
					
						/*
						**	Act on key-press now:
						*/
						switch(imsg->Code)
							{
							case 'f':					/* The key for FIND has been pressed */
								GUI_Find();
								break;

							case 'q':					/* QUIT button short-cut */
								quit_program = TRUE;
								break;

							case 'd':					/* Activate the drawer gadget */
								ActivateGadget(gadget_list[GID_DRAWER], finder_window, NULL);
								break;

							case 's':					/* Activate the search string gadget */
								ActivateGadget(gadget_list[GID_SEARCH], finder_window, NULL);
								break;
							}

						break;

					/*
					**	Button Press:
					*/
					case IDCMP_GADGETUP:
						switch(gad->GadgetID)
							{
							case GID_QUIT:
								quit_program = TRUE;
								break;

							case GID_FIND:
								/*
								**	Re-Initialise List:
								*/
								GUI_Find();
								break;
							}
				
						break;
					}

				GT_ReplyIMsg(imsg);			/* Dealt with, reply to it now. */
				}
			}
		}

	/*
	**	Clear list (if any), and exit:
	*/
	ClearGUI_List();

	return;
}
