/*
 * This file demonstrates the use of the FSLX interface with BoxKite
 * Operating systems: TOS/MiNT/MultiTOS/MagiC/MagiC Mac/N.AES
 * (c) Harald Becker, 30.3.1998. Compiled using Pure C and DEFAULT.PRJ.
 * For further explanations of the 'fslx_xxx()' calls please refer to
 * the MagiC programming docs.
 */
#include <aes.h>
#include <vdi.h>
#include <tos.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/* sort modes */
#define SORTBYNAME	0
#define SORTBYDATE	1
#define SORTBYSIZE	2
#define SORTBYTYPE	3
#define SORTBYNONE	4

/* file selector flags */
#define DOSMODE		1
#define NFOLLOWSLKS	2
#define GETMULTI	8

/*
 * This struicture defines an AES event.
 */
typedef struct
{	int		mwhich;
	int		mx;
	int		my;
	int		mbutton;
	int		kstate;
	int		key;
	int		mclicks;
	int		reserved[9];
	int		msg[16];
}
EVNT;

/*
 * The 'HBFS' cookie's value points to this structure
 */
typedef struct
{	unsigned int	branch;		/* branch instruction to bypass this struct */
	unsigned long	magic;		/* constant value: 'BxKt' */
	unsigned int	version;	/* version number: 0x200 for 2.00 */
}
BXKT_STR;

typedef struct
{	long	id;
	long	value;
}
COOKIE;

typedef int cdecl XFSL_FILTER(char *path, char *name, XATTR *xa);

char path[256] = "";
char name[256] = "";
char alert[256];
char extlist[] = "*.*\0*.CFG\0*\0*.C\0*.H\0*.RSC\0\0";
char pathlist[] = "C:\\AUTO\\\0C:\\GEMINI2\\\0\0";

void *fsel = NULL;	/* file selector handle */
int window;			/* window handle */
OBJECT contents = 	/* window contents */
	{ -1, -1, -1, G_BOXCHAR, 0, 0, 0x2dff11f0, 1, 1, 1, 1 };
char animate[] = "-\\|/";
char *animate_p = &animate[1];

int xdesk, ydesk, wdesk, hdesk;

/*
 * Return the cookie jar's adress. Call this using Supexec().
 */
long get_cookiejar(void)
{	return *((long *)0x05a0l);
}

/*
 * Search the cookie jar for a given id and return the corresponding
 * cookie value.
 */
int get_cookie(long id, long *pval)
{	long sav;
	COOKIE *cookiejar;
	int	i = 0;

	cookiejar = (COOKIE *)Supexec(get_cookiejar);

	if ( cookiejar )
	{	while ( cookiejar[i].id )
		{	if ( cookiejar[i].id == id )
			{	*pval = cookiejar[i].value;
				return 1;
			}
			i++;
		}
	}
	return 0;
}

/*
 * Get the current disk and path.
 */
void getpath(char *p)
{	int drv;

	drv = Dgetdrv();
	*p++ = drv + 'A';
	*p++ = ':';
	Dgetpath(p, drv);
	p += strlen(p) - 1;
	if ( *p != '\\' )
	{	p++;
		*p++ = '\\';
		*p = 0;
	}
}

/* 
 * These functions redraw the window contents. I guess the method is
 * common knowledge by now.
 */
int min(int a, int b)
{	return ( a < b ? a : b );
}

int max(int a, int b)
{	return ( a > b ? a : b );
}

int rc_intersect(GRECT *p1, GRECT *p2)
{	int tx, ty, tw, th;

	tw = min(p2->g_x + p2->g_w, p1->g_x + p1->g_w);
	th = min(p2->g_y + p2->g_h, p1->g_y + p1->g_h);
	tx = max(p2->g_x, p1->g_x);
	ty = max(p2->g_y, p1->g_y);

	p2->g_x = tx;
	p2->g_y = ty;
	p2->g_w = tw - tx;
	p2->g_h = th - ty;

	return( (tw > tx) && (th > ty) );
}

void window_redraw(int handle, int x, int y, int w, int h)
{	GRECT r1, r2, world;

	wind_update(BEG_UPDATE);

	r2.g_x = x;
	r2.g_y = y;
	r2.g_w = w;
	r2.g_h = h;

	world.g_x = xdesk;
	world.g_y = ydesk;
	world.g_w = wdesk;
	world.g_h = hdesk;

	wind_get(handle, WF_FIRSTXYWH, &r1.g_x, &r1.g_y, &r1.g_w, &r1.g_h);
	while ( r1.g_w && r1.g_h )
	{	if ( rc_intersect(&world, &r1) && rc_intersect(&r2, &r1) )
			objc_draw(&contents, ROOT, MAX_DEPTH, r1.g_x, r1.g_y, r1.g_w, r1.g_h);
		wind_get(handle, WF_NEXTXYWH, &r1.g_x, &r1.g_y, &r1.g_w, &r1.g_h);
	}
	wind_update(END_UPDATE);
}

/*
 * Next come the bindings for the required AES calls (at least 
 * for those which are not contained in the standard libraries).
 * The AES parameter arrays are declared locally; the global[]
 * array is passed as an explicit function parameter. Thus these
 * functions can be multithreaded.
 * I know that this code could have been written in a more efficient
 * way even without resorting to assembly language, but that would 
 * have made it less readable for readers who prefer other languages.
 * The '#pragma' statement supresses compiler warnings which result
 * from a wrong declaration of the 'AESPB' struct: 'addrin[]'
 * and 'addrout[]' are delared as ponters to 'int' values.
 */
#pragma warn -sus

void *fslx_open(int *global,
				char *title,
				int x, int y,
				int	*handle,
				char *path, int pathlen,
				char *fname, int fnamelen,
				char *patterns,
				XFSL_FILTER *filter,
				char *paths,
				int sort_mode,
				int flags)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 190;
	contrl[1] = 6;
	contrl[2] = 1;
	contrl[3] = 6;
	contrl[4] = 1;

	intin[0] = x;
	intin[1] = y;
	intin[2] = pathlen;
	intin[3] = fnamelen;
	intin[4] = sort_mode;
	intin[5] = flags;

	addrin[0] = title;
	addrin[1] = path;
	addrin[2] = fname;
	addrin[3] = patterns;
	addrin[4] = filter;
	addrin[5] = paths;

	_crystal(&aespb);

	*handle = intout[0];
	return addrout[0];
}

int fslx_evnt(int *global,
			void *fsd,
			EVNT *events,
			char *path,
			char *fname,
			int *button,
			int *nfiles,
			int *sort_mode,
			char **pattern)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 193;
	contrl[1] = 0;
	contrl[2] = 4;
	contrl[3] = 4;
	contrl[4] = 1;

	addrin[0] = fsd;
	addrin[1] = events;
	addrin[2] = path;
	addrin[3] = fname;

	_crystal(&aespb);

	*button = intout[1];
	*nfiles = intout[2];
	*sort_mode = intout[3];

	*pattern = addrout[0];

	return intout[0];
}

int fslx_close(int *global, void *fsd)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 191;
	contrl[1] = 0;
	contrl[2] = 1;
	contrl[3] = 1;
	contrl[4] = 0;

	addrin[0] = fsd;

	_crystal(&aespb);

	return intout[0];
}

int fslx_getnxtfile(int *global, void *fsd,	char *fname)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 192;
	contrl[1] = 0;
	contrl[2] = 1;
	contrl[3] = 2;
	contrl[4] = 0;

	addrin[0] = fsd;
	addrin[1] = fname;

	_crystal(&aespb);

	return intout[0];
}

void *fslx_do(int *global,
			char *title,
			char *path, int pathlen,
			char *fname, int fnamelen,
			char *patterns,
			XFSL_FILTER *filter,
			char *paths,
			int *sort_mode,
			int flags,
			int *button,
			int *nfiles,
			char **pattern)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 194;
	contrl[1] = 4;
	contrl[2] = 4;
	contrl[3] = 6;
	contrl[4] = 2;

	intin[0] = pathlen;
	intin[1] = fnamelen;
	intin[2] = *sort_mode;
	intin[3] = flags;

	addrin[0] = title;
	addrin[1] = path;
	addrin[2] = fname;
	addrin[3] = patterns;
	addrin[4] = filter;
	addrin[5] = paths;

	_crystal(&aespb);

	*button = intout[1];
	*nfiles = intout[2];
	*sort_mode = intout[3];

	*pattern = addrout[1];
	return addrout[0];
}

/*
 * This function ought to be contained in the more recent 
 * GEM libraries.
 */
int appl_getinfo(int *global, int ap_gtype, int *ap_gout1, int *ap_gout2, int *ap_gout3, int *ap_gout4)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 130;
	contrl[1] = 1;
	contrl[2] = 5;
	contrl[3] = 0;
	contrl[4] = 0;

	intin[0] = ap_gtype;

	_crystal(&aespb);

	*ap_gout1 = intout[1];
	*ap_gout2 = intout[2];
	*ap_gout3 = intout[3];
	*ap_gout4 = intout[4];

	return intout[0];
}

/*
 * The next function is not used in this demo; it is provided to
 * make the 'fslx_xxx()' bindings complete.
 */
int fslx_set_flags(int *global, int flags, int *oldval)
{	AESPB aespb;
	void *addrin[6], *addrout[6];
	int contrl[5], intin[16], intout[7];

	aespb.contrl  = contrl;
	aespb.global  = global;
	aespb.intin   = intin;
	aespb.intout  = intout;
	aespb.addrin  = addrin;
	aespb.addrout = addrout;

	contrl[0] = 195;
	contrl[1] = 2;
	contrl[2] = 2;
	contrl[3] = 0;
	contrl[4] = 0;

	intin[0] = 0;
	intin[1] = flags;

	_crystal(&aespb);

	*oldval = intout[1];
	return intout[0];
}
#pragma warn .sus

/*
 * This function calls 'appl_getinfo()' after checking if this call
 * is implemented by the current AES version. It requires the 'global[]' 
 * array to be initialized correctly.
 */
int appl_xgetinfo(int *global, int code, int *rv1, int *rv2, int *rv3, int *rv4)
{	int magx, j, aes_version;
	int **value;

#define WF_WINX	22360

	aes_version = global[0];
	magx = ( get_cookie('MagX', (long *)&value) && value[2][24] >= 0x200 );

	if ( code > 3 && !magx && aes_version <= 0x400 )
		return 0;

	if ( aes_version >= 0x400 || magx || wind_get(0, WF_WINX, &j, &j, &j, &j) == WF_WINX || appl_find("?AGI") == 0 )
		return appl_getinfo(global, code, rv1, rv2, rv3, rv4);

	return 0;
}

/*
 * This call checks whether the current AES version supports
 * the 'fslx'-calls.
 * Sole parameter is a 'global[]' pointer which is used to determine
 * the AES version number.
 */
int system_has_fslx(int *global)
{	int gout1, gout2, gout3, gout4;
	BXKT_STR *value;

	if ( appl_xgetinfo(global, 7, &gout1, &gout2, &gout3, &gout4) )
	{	/*
		 * Call 'appl_getinfo()' mode 7 and check the result.
		 */
		if ( gout1 & 8 )
			return 1;
		else
			return 0;
	}
	else
	{	/*
	 	 * If 'appl_getinfo()' is not present, look at the 'HBFS' cookie 
	 	 */
		if ( get_cookie('HBFS', (long *)&value) )
			if ( value->magic == 'BxKt' && value->version >= 0x200 )
				return 1;
	}
	return 0;
}

/* 
 * Process the events ignored by the file selector. These refer to
 * the background window.
 */
void message_handler(int *msg)
{	switch ( msg[0] )
	{	case WM_REDRAW:
			wind_get(msg[3], WF_WORKXYWH, &contents.ob_x, &contents.ob_y, &contents.ob_width, &contents.ob_height);
			window_redraw(msg[3], msg[4], msg[5], msg[6], msg[7]);
			break;
		case WM_MOVED:
			wind_set(msg[3], WF_CURRXYWH, msg[4], msg[5], msg[6], msg[7]);
			break;
		case WM_TOPPED:
			wind_set(msg[3], WF_TOP, 0, 0, 0, 0);
			break;
		case WM_CLOSED:
			/*
			 * Demo window is closed: Free all resources and terminate.
			 */
			if ( fsel != NULL )
				fslx_close(_GemParBlk.global, fsel);
			wind_close(msg[3]);
			wind_delete(msg[3]);
			appl_exit();
			exit(0);
	}
}

/*
 * This function processes timer events and draws some sort of
 * animation in the background window.
 */
void timer_handler(int window)
{	contents.ob_spec.obspec.character = *animate_p++;
	if ( *animate_p == 0 )
		animate_p = animate;
	wind_get(window, WF_WORKXYWH, &contents.ob_x, &contents.ob_y, &contents.ob_width, &contents.ob_height);
	window_redraw(window, contents.ob_x, contents.ob_y, contents.ob_width, contents.ob_height);
}

int main(void)
{	int rv, button, window, f_window, result, nfiles, sortmode, boxmode;
	int msg[8], which, mx, my, kreturn, breturn, kstate;
	EVNT evnt;
	char *pattern;

	appl_init();
	wind_get(0, WF_WORKXYWH, &xdesk, &ydesk, &wdesk, &hdesk);
	graf_mouse(ARROW, NULL);

	Pdomain(1);		/* Use long filenames */
	getpath(path);

	/*
	 * Are the FSLX calls supported?.
	 */
	if ( system_has_fslx(_GemParBlk.global) == 0 )
	{	form_alert(1, "[1][The FSLX calls are|not supported.][   Ok   ]");
		appl_exit();
		return 0;
	}

	boxmode = form_alert(1, "[2][How shall I open the|file selector?][ Window | Dialog ]");
	if ( boxmode == 1 )
	{	/*
		 * Open a window next to the file selector. This is to prove that the file 
		 * selector is a true application window instead of a 'mode'.
		 */
		window = wind_create(NAME | MOVER | CLOSER, xdesk, ydesk, wdesk, hdesk);
		if ( window < 0 )
		{	form_alert(1, "[1][Can't open the window!][   Ok   ]");
			appl_exit();
			return 0;
		}
		wind_set(window, WF_NAME, "Noch'n Fenster");
		wind_open(window, xdesk + 50, ydesk + 50, 300, 200);
		window_redraw(window, xdesk, ydesk, wdesk, hdesk);

		fsel = fslx_open(
				_GemParBlk.global,
				"BoxKite",
				-1,-1,
				&f_window,
				path, 258,
				name, 258,
				extlist,		/* ext list */
				0L,				/* no filter */
				pathlist,		/* path list */
				SORTBYDATE,
				GETMULTI);

		if ( fsel == NULL )
			form_alert(1, "[3][Can't open the file|selector!][   Ok   ]");

		/* 
		 * This is the main loop. It is entered whether the file selector
		 * is open or not. The program terminates if the background window 
		 * is closed.
		 */
		do
		{	which = evnt_multi(MU_KEYBD | MU_BUTTON | MU_MESAG | MU_TIMER,
					2, 1, 1,
					0, 0, 0, 0, 0,
					0, 0, 0, 0, 0,
					msg,
					500, 0,
					&mx, &my, &button,
					&kstate, &kreturn, &breturn);

			if ( fsel != NULL )
			{	/*
				 * If the file selector is open, it gets all
				 * events first.
				 */
				evnt.mwhich  = which;
				evnt.mx 	 = mx;
				evnt.my 	 = my;
				evnt.mbutton = button;
				evnt.kstate  = kstate;
				evnt.key 	 = kreturn;
				evnt.mclicks = breturn;
				memcpy(evnt.msg, msg, 16);
	
				result = fslx_evnt(
					_GemParBlk.global,
					fsel,
					&evnt,
					path,
					name,
					&button,
					&nfiles,
					&sortmode,
					&pattern);

				if ( result == 0 )
				{	/*
					 * If we get 0 as return code, the file selector has been 
					 * closed. Check if we have to process selected files.
					 */
					if ( button == 0 )
						form_alert(1, "[3][ The dialog was cancelled.][   Ok   ]");
					else
					{	sprintf(alert, "[2][ Count: %d | Sort mode: %d | Pattern: %s | Name: %s ][   Ok   ]", nfiles, sortmode, pattern, name);
						form_alert(1, alert);
						while ( --nfiles )
						{	fslx_getnxtfile(_GemParBlk.global, fsel, name);
							sprintf(alert, "[2][ Name: %s ][   Ok   ]", name);
							form_alert(1, alert);
						}
					}
					fslx_close(_GemParBlk.global, fsel);
					fsel = NULL;
				}
				/*
				 * Since 'fslx_evnt()' deletes a bit from 'evnt.mwhich' if
				 * it could process an event, this modified value has to be 
				 * analyzed further.
				 */
				which = evnt.mwhich;
			}
			/*
			 * Here we process all events left over by the file selector.
			 */
			if ( which & MU_MESAG )
				message_handler(msg);

			if ( which & MU_TIMER )
				timer_handler(window);
		}
		while ( 1 );
	}
	else
	{	/*
		 * This calls the file selector as a modal dialog
		 * using 'fslx_do()'.
		 */
		sortmode = SORTBYDATE;
		fsel = fslx_do(
				_GemParBlk.global,
				"BoxKite",
				path, 258,
				name, 258,
				extlist,		/* ext list */
				0L,				/* no filter */
				pathlist,		/* path list */
				&sortmode,
				GETMULTI,
				&button,
				&nfiles,
				&pattern
			);
		if ( fsel == NULL )
			form_alert(1, "[3][Can't open the|file selector!][   Ok   ]");
		else
		{	if ( button == 0 )
				form_alert(1, "[3][ The dialog was cancelled.][   Ok   ]");
			else
			{	sprintf(alert, "[2][ Count: %d | Sort mode: %d | Pattern: %s | Name: %s ][   Ok   ]", nfiles, sortmode, pattern, name);
				form_alert(1, alert);
				while ( --nfiles )
				{	fslx_getnxtfile(_GemParBlk.global, fsel, name);
					sprintf(alert, "[2][ Name: %s ][   Ok   ]", name);
					form_alert(1, alert);
				}
			}
			fslx_close(_GemParBlk.global, fsel);
		}
	}
	appl_exit();
	return 0;
}
