/* 
Copyright (c) 1991-2000 UserLand Software, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software and associated documentation files (the 
"Software"), to deal in the Software without restriction, including 
without limitation the rights to use, copy, modify, merge, publish, 
distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to the 
following conditions: 

The above copyright notice and this permission notice shall be 
included in all copies or substantial portions of the Software. 

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
*/ 


#include <iac.h>
#include "appletdefs.h"
#include "appletmemory.h"
#include "appletstrings.h"
#include "appletfiles.h"
#include "appletprocess.h"
#include "appletfrontier.h"


static OSType idFrontier = 'LAND';


typedef struct versionRecord {

	unsigned short majorRev: 8; 
	
	unsigned short minorRev: 4;	
	
	unsigned short bugFixRev: 4; 
	
	unsigned short reserved: 15;
	
	unsigned short flFrontier: 1; /*true if it's Frontier, false if it's Runtime*/
	} versionRecord;


tyembeddedinfo embeddedinfo;


#define frag1 "\pif not defined ("

#define frag2 "\p) {speaker.beep (); return;}; Frontier.bringToFront (); edit (@"

#define frag3 "\p)"


boolean FrontierOpenObject (bigstring objectaddress) {
	
	bigstring script, returns;
	
	setstringlength (script, 0);
	
	pushstring (frag1, script);
	
	pushstring (objectaddress, script);	
	
	pushstring (frag2, script);
	
	pushstring (objectaddress, script);
	
	pushstring (frag3, script);
	
	return (FrontierDoScript (script, returns));
	} /*FrontierOpenObject*/
	
	
boolean FrontierGetObject (Handle hscript, bigstring errorstring, Handle *hreturns, OSType *binarytype) {
	
	AppleEvent event, reply;
	boolean fl = true;
	Handle h;	
	
	setstringlength (errorstring, 0);
	
	if (!IACnewsystemverb ('fast', 'gobj', &event))
		return (false);
		
	IACglobals.event = &event;
	
	if (!IACpushtextparam (hscript, '----'))
		return (false);
		
	if (!IACsendverb (&event, &reply))
		return (false);
	
	IACglobals.reply = &reply;
	
	if (IACiserrorreply (errorstring)) {
		
		fl = false;
		
		goto exit;
		}
		
	IACglobals.event = &reply; /*get the string from the reply record*/
	
	fl = IACgetbinaryparam ('----', &h, binarytype);
	
	if (fl) { /*1st 4 bytes contain the object type*/
		
		OSType type;
		long headersize;
		
		moveleft (*h, &type, longsizeof (type));
		
		headersize = 4;
		
		switch (type) { /*header info varies according to type*/
			
			case 'pict':
				headersize = 60;
				
				break;
				
			case 'data':
				headersize += 4;
				
				break;
				
			} /*switch*/
		
		lockhandle (h);
		
		fl = newfilledhandle ((*h) + headersize, GetHandleSize (h) - headersize, hreturns);
		
		unlockhandle (h);
		}
	
	exit:
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (fl);
	} /*FrontierGetObject*/


boolean FrontierDoScript (bigstring script, bigstring returns) {
	
	/*
	Send a Do Script message to Frontier. The first parameter contains a short
	script to be run. The second parameter is the string that Frontier returned 
	as the value generated by running the script. 
	
	Returns true if we were able to send the message to Frontier, and Frontier
	was able to compile and run the script and Frontier replied with a returned
	value. FrontierDoScript returns false if Frontier isn't running, or if the
	script didn't compile or if there was a communications error.
	
	Frontier is not limited to running 255-character scripts or returning 
	255-character returned values. This routine can easily be enhanced to handle 
	larger scripts and returned values. 
	
	11/6/91 DW: Set return string to "IAC Error" if we failed to create an Apple
	event descriptor or if the send failed. We're not suggesting that your
	error messages should be so brief, rather they should be custom-fit to the
	appropriate audience. Here, we want to show you how to locate errors relating
	to the IAC channel -- either you're low on memory, or the Apple Event Manager
	isn't present, or Frontier isn't running. Watch for this string in FDS's 
	little window...
	
	11/7/91 DW: This code was cribbed from the Frontier Do-Script program and adapted
	to run on top of the IAC Tools library. Use this version of FDS if you're using
	the IAC Tools library, use the original version if you're writing code to run
	directly on top of the Apple Event Manager.
	*/

	Boolean flhavereply = false;
	AppleEvent event, reply;
	
	copystring ("\pIAC Error.", returns); /*default return string*/
	
	if (!IACnewverb (idFrontier, 'misc', 'dosc', &event))
		return (false);
	
	IACglobals.event = &event;
	
	if (!IACpushstringparam (script, '----'))
		return (false);
		
	if (!IACsendverb (&event, &reply))
		goto error;
	
	flhavereply = true;
	
	IACglobals.reply = &reply;
	
	if (IACiserrorreply (returns)) /*syntax error or runtime error*/
		goto error;
		
	IACglobals.event = &reply; /*get the string from the reply record*/
		
	if (!IACgetstringparam ('----', returns))
		goto error;
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (true);
	
	error:
	
	AEDisposeDesc (&event);	
	
	if (flhavereply)
		AEDisposeDesc (&reply);
	
	return (false);
	} /*FrontierDoScript*/
	
	
boolean FrontierDoHandleScript (Handle hscript, boolean flfast, boolean flgetreturn, bigstring errorstring, Handle *hreturns) {
	
	/*
	we run the script thru Frontier's interpreter, registered as a system-level
	Apple Event handler at fast.dosc.
	
	the script handle belongs to us, we dispose of it. the return handle belongs
	to you. when you're done with it, you must dispose of it.
	
	1/13/92 DW: check for a fast.dosc system-level handler, and return false if it
	isn't installed. this removes the requirement that Frontier be running while 
	Card Editor/Runner is running.
	*/
	
	static AEAddressDesc selfAddress; 
	static boolean inited = false;
	AppleEvent event, reply;
	boolean flhavereply = false;
	OSErr ec;
	
	*hreturns = nil;
	
	setstringlength (errorstring, 0);
	
	if (flfast) {
	
		if (!IAChandlerinstalled ('fast', 'dosc', true)) {
			
			/*copystring ("\pFrontier 2.0 or greater isnŐt running.", errorstring);*/
			
			return (true);
			}
		}
		
	if ((!inited) && flfast) {
	
		ProcessSerialNumber psn;
	
		psn.highLongOfPSN = 0;
	
		psn.lowLongOfPSN = kCurrentProcess;
	
		ec = AECreateDesc (typeProcessSerialNumber, (Ptr) &psn, sizeof (psn), &selfAddress);
		
		if (ec != noErr) {
		
			DebugStr ("\pAECreateDesc failed.");
			
			return (false);
			}
		
		inited = true;
		}
	
	/*create the Apple Event*/ {
		
		AEAddressDesc *pdesc;
		AEAddressDesc targetappdesc;
		
		if (flfast)
			pdesc = &selfAddress;
		else {
			ec = AECreateDesc (
				typeApplSignature, (Ptr) &idFrontier, 
				sizeof (idFrontier), &targetappdesc);
				
			if (ec != noErr) {
			
				DisposeHandle (hscript);
			
				return (false);
				}
		
			pdesc = &targetappdesc;
			}
			
		ec = AECreateAppleEvent (
			'fast', 'dosc', pdesc, 
			kAutoGenerateReturnID, 
			kAnyTransactionID, 
			&event);
	
		if (ec != noErr) {
			
			DisposeHandle (hscript);
			
			return (false);
			}
		}
	
	/*push the script on the event*/ {
	
		AEDesc desc;
		
		desc.descriptorType = typeChar;
	
		desc.dataHandle = hscript;
	
		ec = AEPutParamDesc (&event, keyDirectObject, &desc);
	
		if (ec != noErr)
			goto error;
		
		DisposeHandle (hscript); 
		
		hscript = nil; 
		}
	
	/*send the event*/ {
	
		ec = AESend (
			&event, &reply, 
			kAEWaitReply + kAENeverInteract, 
			kAENormalPriority, kNoTimeOut, 
			nil, nil);
		
		if (ec != noErr)
			goto error;
			
		flhavereply = true;
		}
		
	/*did Frontier report an error?*/ {
	
		AEDesc numDesc;
		
		ec = AEGetParamDesc (&reply, keyErrorNumber, typeSMInt, &numDesc);
		
		if (ec == noErr) { /*the reply is an error*/
			
			AEDesc strDesc;
			
			ec = AEGetParamDesc (&reply, keyErrorString, typeChar, &strDesc);
			
			if (ec == noErr) {
			
				texthandletostring (strDesc.dataHandle, errorstring);
				
				AEDisposeDesc (&strDesc);
				}
			else 
				copystring ("\pThe script generated an error, but no message was provided.", errorstring);
			
			AEDisposeDesc (&numDesc);
			
			goto error;
			}
		} 
	
	if (flgetreturn) {
		
		AEDesc desc;
		Handle h;
	
		ec = AEGetParamDesc (&reply, keyDirectObject, typeChar, &desc);
		
		if (ec != noErr)
			goto error;
			
		/*
		xxx = GetHandleSize (desc.dataHandle);
		*/
		
		if (!copyhandle (desc.dataHandle, &h))
			goto error;
			
		AEDisposeDesc (&desc);
		
		*hreturns = h; 
		}
	
	AEDisposeDesc (&event);	
	
	if (flhavereply)
		AEDisposeDesc (&reply);
	
	return (true);
	
	error:
	
	if (hscript != nil)
		DisposeHandle (hscript);
	
	AEDisposeDesc (&event);	
	
	if (flhavereply)
		AEDisposeDesc (&reply);
		
	if (stringlength (errorstring) == 0) {
		
		copystring ("\pApple Event Manager error. Its code number is ", errorstring);
		
		pushlong (ec, errorstring);
		
		pushstring ("\p.", errorstring);
		}
	
	return (false);
	} /*FrontierDoHandleScript*/

#if 0

static boolean oldFrontierDoHandleScript (Handle hscript, boolean flfast, boolean flgetreturn, bigstring errorstring, Handle *hreturns) {
	
	AppleEvent event, reply;
	boolean fl = true;
	
	setstringlength (errorstring, 0);
	
	if (flfast) {
	
		if (!IACnewsystemverb ('fast', 'dosc', &event))
			return (false);
		}
	else {
	
		if (!IACnewverb (idFrontier, 'misc', 'dosc', &event))
			return (false);
		}
	
	IACglobals.event = &event;
	
	if (!IACpushtextparam (hscript, '----'))
		return (false);
		
	if (!IACsendverb (&event, &reply))
		return (false);
	
	IACglobals.reply = &reply;
	
	if (IACiserrorreply (errorstring)) {
		
		fl = false;
		
		goto exit;
		}
		
	if (flgetreturn) {
		
		Handle htextresult;
	
		IACglobals.event = &reply; /*get the string from the reply record*/
		
		fl = IACgettextparam ('----', &htextresult);
		
		if (fl)
			fl = copyhandle (htextresult, hreturns); /*DW 12/26/92 looks like a memory leak*/
		}
	
	exit:
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (fl);
	} /*oldFrontierDoHandleScript*/

#endif

boolean FrontierFastDoScript (bigstring bsscript, boolean flgetreturn, bigstring errorstring, bigstring bsreply) {
	
	/*
	send a fast system-level script to Frontier and get back a value if flgetreturn is
	true. we only send the message the fast way if there is a system handler installed.
	
	use this for scripts that don't open windows or display things in Frontier. it's
	good for setting and getting string values in the object database.
	*/
	
	AppleEvent event, reply;
	boolean fl = true;
	
	setstringlength (errorstring, 0);
	
	if (IAChandlerinstalled ('fast', 'dosc', true)) { 
	
		if (!IACnewsystemverb ('fast', 'dosc', &event))
			return (false);
		}
	else {
	
		if (!IACnewverb (idFrontier, 'misc', 'dosc', &event))
			return (false);
		}
	
	IACglobals.event = &event;
	
	if (!IACpushstringparam (bsscript, '----'))
		return (false);
		
	if (!IACsendverb (&event, &reply))
		return (false);
	
	IACglobals.reply = &reply;
	
	if (IACiserrorreply (errorstring)) {
		
		fl = false;
		
		goto exit;
		}
		
	if (flgetreturn) {
	
		IACglobals.event = &reply; /*get the string from the reply record*/
		
		fl = IACgetstringparam ('----', bsreply);
		}
	
	exit:
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (fl);
	} /*FrontierFastDoScript*/
	
	
boolean FrontierIsRunning (void) {
	
	typrocessinfo info;
	
	return (findrunningapp (idFrontier, &info));
	} /*FrontierIsRunning*/
	
	
boolean getFrontierVersion (short *majorRev, short *minorRev, short *bugFixRev, boolean *flRuntime) {
	
	/*
	if Frontier isn't running, return false.
	
	if it is, return true with information about the version of Frontier that's running.
	
	About the Apple Event we send...
	
	We call a system event handler, so it's very fast. 
	
	It takes no parameters, and returns a long value. The high word of the long is the version 
	number, packed the same way as the system version is packed into the SysEnvirons record. 
	(8 bits major version, 4 bits minor version, 4 bits revision. The version 2.1.1 would 
	be 0x0211.) The low word contains attributes of the server program. At this point only 
	a single bit is defined: the low order bit is set if Frontier is the server; otherwise, 
	Runtime is the server.
	*/
	
	AppleEvent event, reply;
	Boolean flhavereply = false;
	versionRecord x;
	
	long z;
	
	z = sizeof (x);
	
	if (!FrontierIsRunning ())
		return (false);
	
	if (!IAChandlerinstalled (idFrontier, 'who?', true)) { /*it's Frontier 1.0, not Runtime*/
		
		*majorRev = 1;
		
		*minorRev = 0;
		
		*bugFixRev = 0;
		
		*flRuntime = false;
		
		return (true);
		}

	if (!IACnewsystemverb (idFrontier, 'who?', &event))
		return (false);
	
	if (!IACsendverb (&event, &reply))
		goto error;
	
	flhavereply = true;
	
	IACglobals.reply = &reply;
	
	IACglobals.event = &reply; /*get the string from the reply record*/
		
	if (!IACgetlongparam ('----', (long *) &x))
		goto error;
	
	*majorRev = x.majorRev;
	
	*minorRev = x.minorRev;
	
	*bugFixRev = x.bugFixRev;
	
	*flRuntime = !x.flFrontier;
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (true);
	
	error:
	
	AEDisposeDesc (&event);	
	
	if (flhavereply)
		AEDisposeDesc (&reply);
	
	return (false);
	} /*getFrontierVersion*/
	

static boolean pushStandardParams (void) {
	
	if (!IACpushfilespecparam (&embeddedinfo.filespec, 'prm2'))
		return (false);
		
	if (!IACpushlongparam ((long) embeddedinfo.creator, 'prm3'))
		return (false);
		
	if (!IACpushstringparam (embeddedinfo.windowtitle, 'prm4'))
		return (false);
	
	return (true);
	} /*pushStandardParams*/
	

tyalertcallback alertcallback;


static pascal void asynchReplyHandler (AppleEvent *reply) {
	
	bigstring bserror;
	
	IACglobals.reply = reply;
	
	if (IACiserrorreply (bserror)) { /*syntax error or runtime error*/
		
		if (alertcallback != nil)
			(*alertcallback) (bserror);
		}
	} /*asynchReplyHandler*/

	
boolean tableVerb (OSType command, tyalertcallback alert, boolean flsynchronous) {
	
	AppleEvent event;
	Handle htable = nil;
	
	alertcallback = alert;
		
	if (!copyhandle (embeddedinfo.h, &htable))
		return (false);
		
	if (!IACnewverb (idFrontier, embeddedinfo.creator, command, &event))
		goto error;
		
	IACglobals.event = &event;
	
	if (!IACpushtableparam (htable, 'prm1'))
		goto error;
		
	htable = nil;
	
	if (!pushStandardParams ())
		goto error;
	
	if (flsynchronous) {
	
		AppleEvent reply;
		
		if (!IACsendverb (&event, &reply))
			goto error;
		
		asynchReplyHandler (&reply);
		}
	else {
		if (!IACsendasynch (&event, &asynchReplyHandler))
			goto error;
		}
	
	AEDisposeDesc (&event);	
	
	return (true);
	
	error:
	
	disposehandle (htable);
	
	AEDisposeDesc (&event);	
	
	return (false);
	} /*tableVerb*/
	

boolean renameEmbeddedTable (bigstring oldname, bigstring newname, tyalertcallback alert) {

	AppleEvent event;
	
	alertcallback = alert;
	
	if (!IACnewverb (idFrontier, embeddedinfo.creator, 'rnam', &event))
		return (false);
	
	IACglobals.event = &event;
	
	if (!IACpushstringparam (oldname, 'prm1'))
		goto error;
		
	if (!IACpushstringparam (newname, 'prm2'))
		goto error;
		
	if (!IACsendasynch (&event, &asynchReplyHandler))
		goto error;
	
	AEDisposeDesc (&event);	
	
	return (true);
	
	error:
	
	AEDisposeDesc (&event);	
	
	return (false);
	} /*renameEmbeddedTable*/
	

boolean getEmbeddedTable (tyalertcallback alert) { 

	Handle htable;
	Boolean flhavereply = false;
	AppleEvent event, reply;
	bigstring bserror;
	
	if (!IACnewverb (idFrontier, embeddedinfo.creator, 'gett', &event))
		return (false);
	
	IACglobals.event = &event;
	
	if (!pushStandardParams ())
		goto error;
	
	if (!IACsendverb (&event, &reply))
		goto error;
	
	flhavereply = true;
	
	IACglobals.reply = &reply;
	
	if (IACiserrorreply (bserror)) { /*syntax error or runtime error*/
	
		if (alert != nil)
			(*alert) (bserror);
		
		goto error;
		}
	
	IACglobals.event = &reply; /*get the table from the reply record*/
	
	if (!IACgettableparam ('----', &htable))
		goto error;
		
	if (!copyhandle (htable, &htable))
		goto error;
		
	embeddedinfo.h = htable;
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (true);
	
	error:
	
	AEDisposeDesc (&event);	
	
	if (flhavereply)
		AEDisposeDesc (&reply);
	
	return (false);
	} /*getEmbeddedTable*/
	
	
boolean runEmbeddedScript (Handle hscript, Handle *hreturns) {
	
	/*
	run the script in the environment of the embedded table.
	*/
	
	AppleEvent event, reply;
	boolean fl = true;
	bigstring errorstring;
	
	*hreturns = nil;
	
	if (!IACnewverb (idFrontier, embeddedinfo.creator, 'dosc', &event))
		return (false);
	
	IACglobals.event = &event;
	
	if (!IACpushtextparam (hscript, '----'))
		return (false);
		
	if (!pushStandardParams ())
		return (false);
	
	if (!IACsendverb (&event, &reply))
		return (false);
	
	IACglobals.reply = &reply;
	
	if (IACiserrorreply (errorstring)) 
		DebugStr (errorstring);
	
	IACglobals.event = &reply; /*get the string from the reply record*/
	
	fl = IACgettextparam ('----', hreturns);
	
	if (fl)
		fl = copyhandle (*hreturns, hreturns);

	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (fl);
	} /*runEmbeddedScript*/
	

#if false
	
	#ifndef uisinternalinclude
	
		#include <uisinternal.h>
	
	#endif

#endif


boolean editFrontierObject (bigstring celladdress) {
	
	/*
	uses a new protocol in Frontier 3.0 to open the window
	in the client app's layer.
	*/
	
	#if false
	
		ComponentInstance savedwindowserver = wsGlobals.windowserver;
		ComponentDescription desc;
		Boolean fl = false;
		
		wsGlobals.windowserver = OpenDefaultComponent ('SHUI', 'LAND');
		
		if (wsGlobals.windowserver != 0) {
			
			Handle h;
			
			if (newtexthandle (celladdress, &h))
				fl = uisOpenHandle (h, false, 0, 0, nil);
			}
			
		CloseComponent (wsGlobals.windowserver);
		
		wsGlobals.windowserver = savedwindowserver;	
		
		return (fl);
	
	#else
		
		return (false);
	
	#endif
	} /*editFrontierObject*/