	//
	//	ItŐs just possible that the containment sequence would
	//	transform the adaptive plasma, but only if we signal
	//	the self-replicating rift and optimize the proto resonance!
	//
	//	This source code is freely distributable. Nevertheless, it
	//	is © Copyright 1995 Pete Gontier, All Rights Reserved.
	//	Kudos, complaints, and carts of cash to <gurgle@dnai.com>.
	//

#ifndef __QUICKDRAW__
#	include <Quickdraw.h>
#endif

#ifndef __TOOLUTILS__
#	include <ToolUtils.h>
#endif

#ifndef __SCRAP__
#	include <Scrap.h>
#endif

#ifndef __OSUTILS__
#	include <OSUtils.h>
#endif

#ifndef __TEXTUTILS__
#	include <TextUtils.h>
#endif

#ifndef __RESOURCES__
#	include <Resources.h>
#endif

#ifndef __DIALOGS__
#	include <Dialogs.h>
#endif

#ifndef __FONTS__
#	include <Fonts.h>
#endif

#ifndef __PLSTRINGFUNCS__
#	include <PLStringFuncs.h>
#endif

#ifndef __SPEECH__
#	include <Speech.h>
#endif

#ifndef __GESTALT__
#	include <Gestalt.h>
#endif

enum
{
	kResID_Dialog_TooLow = 127,
	kResID_Dialog_Main,
	kResID_Dialog_About
};

enum
{
	kDialogItem_TooLow,
	kDialogItem_Quit,
	kDialogItem_Refresh,
	kDialogItem_Copy,
	kDialogItem_Text,
	kDialogItem_Box,
	kDialogItem_About,
	kDialogItem_Speak
};

enum
{
	kResID_StringList_TooLow = 127,
	kResID_StringList_Adjectives,
	kResID_StringList_Nouns,
	kResID_StringList_Verbs,
	kResID_StringList_Materials,
	kResID_StringList_Templates
};

static pascal void InitMac (void)
{
	unsigned long dateTime;

	MaxApplZone ( );
	InitGraf (&(qd.thePort));
	InitFonts ( );
	InitWindows ( );
	InitMenus ( );
	TEInit ( );
	InitDialogs (nil);

	GetDateTime (&dateTime);
	qd.randSeed = dateTime;	
}

static pascal short GetRandomRange (short min, short max)
{
	const float minRand = -32767, maxRand = 32767;
	double x = (Random ( ) - minRand) / (maxRand + 1 - minRand);
	return (x * (max + 1 - min)) + min;
}

static pascal unsigned short Get1CountOfIndexedStrings (short resID)
{
	unsigned short result = 0;
	unsigned short **res = (unsigned short **) Get1Resource ('STR#',resID);
	if (!ResError ( ) && res)
		result = **res;
	return result;
}

static pascal void GetRandomString (short resID, Str255 string)
{
	unsigned short stringCount = Get1CountOfIndexedStrings (resID);
	if (!stringCount)
		*string = 0;
	else
		GetIndString (string, resID, GetRandomRange (1,stringCount));
}

static pascal void MungeBabbleWords (Handle h, StringPtr key, short resID)
{
	Str255 str;
	long offset = 0;

	do
	{
		GetRandomString (resID,str);
		if (-1 == Munger (h, 0, str + 1, *str, nil, 0))
			offset = Munger (h, offset, key + 1, *key, str + 1, *str);
	}
	while (offset > -1);
}

static pascal Handle GetBabbleTemplate (void)
{
	Handle templateH = nil;
	Str255 templateStr;

	GetRandomString (kResID_StringList_Templates, templateStr);
	if (*templateStr)
		if (PtrToHand (templateStr + 1, &templateH, *templateStr))
			templateH = nil;

	return templateH;
}

static pascal void FillDialogTextWithTreknobabble (DialogPtr dp)
{
	Handle babbleH = GetBabbleTemplate ( );
	if (babbleH)
	{
		short iType; Handle iHandle; Rect iRect;
		GrafPtr savedGP = qd.thePort;

		MungeBabbleWords (babbleH,"\p^a",kResID_StringList_Adjectives);
		MungeBabbleWords (babbleH,"\p^n",kResID_StringList_Nouns);
		MungeBabbleWords (babbleH,"\p^v",kResID_StringList_Verbs);
		MungeBabbleWords (babbleH,"\p^m",kResID_StringList_Materials);

		GetDItem (dp,kDialogItem_Text,&iType,&iHandle,&iRect);
		SetDItem (dp,kDialogItem_Text,iType,babbleH,&iRect);
		DisposeHandle (iHandle);
		SetPort (dp);
		InvalRect (&iRect);
		SetPort (savedGP);
	}
}

static pascal void CopyDialogText (DialogPtr dp)
{
	short iType; Handle iHandle; Rect iRect;

	ZeroScrap ( );
	GetDItem (dp,kDialogItem_Text,&iType,&iHandle,&iRect);
	HLockHi (iHandle);
	PutScrap (GetHandleSize (iHandle), 'TEXT', *iHandle);
	HUnlock (iHandle);
}

pascal OSErr SetDialogDefaultItem (DialogPtr theDialog, short newItem) 
	= { 0x303C, 0x0304, 0xAA68 };		

static pascal void About (void)
{
	DialogRef dp = GetNewDialog (kResID_Dialog_About, nil, (WindowRef) -1);
	if (dp)
	{
		short itemHit;

		SetDialogDefaultItem (dp,ok);
		ModalDialog (nil,&itemHit);
		DisposeDialog (dp);
	}
}

static pascal Boolean GotCommandPeriod (void)
{
	EventRecord event;

	if (WaitNextEvent (everyEvent,&event,-1,nil))
	{
		switch (event.what)
		{
			case osEvt		:

				if (((event.message >> 24) & 0xFF) == suspendResumeMessage)
					if (event.message & resumeFlag)
						SetCursor (*GetCursor (watchCursor));

				break;

			case updateEvt	:

				if (IsDialogEvent (&event))
				{
					DialogRef	dRef;
					short		itemHit;
	
					if (DialogSelect (&event,&dRef,&itemHit))
						;
				}
	
				break;

			case keyDown	:

				if (event.modifiers & cmdKey)
					if ((event.message & charCodeMask) == '.')
						return true;
	
				break;
		}
	}

	return false;
}

static pascal void Speak (DialogPtr dp)
{
	SpeechChannel	chan;
	OSErr			oe;

	SetCursor (*GetCursor (watchCursor));
	
	if (!(oe = NewSpeechChannel (nil,&chan)))
	{
		short iType; Handle iHandle; Rect iRect;

		GetDItem (dp,kDialogItem_Text,&iType,&iHandle,&iRect);
		HLockHi (iHandle);

		if (!(oe = SpeakText (chan,*iHandle,GetHandleSize (iHandle))))
		{
			while (SpeechBusy ( ))
			{
				if (GotCommandPeriod ( ))
				{
					StopSpeech (chan);
					break;
				}
			}
		}

		HUnlock (iHandle);
		DisposeSpeechChannel (chan);
	}
}

static pascal Boolean SpeechAvailable (void)
{
	long	result;
	OSErr	err = Gestalt (gestaltSpeechAttr, &result);

	return !err && (result & (1 << gestaltSpeechMgrPresent));
}

static void DisableSpeakButtonIfNoSpeechManager (DialogPtr dp)
{
	if (!SpeechAvailable ( ))
	{
		short iType; Handle iHandle; Rect iRect;
		
		GetDItem (dp,kDialogItem_Speak,&iType,&iHandle,&iRect);
		HiliteControl ((ControlHandle) iHandle, 255);
	}
}

static pascal void RespondToDialogEvent (const EventRecord *event, short *itemHit)
{
	DialogRef dRef;

	if (DialogSelect (event,&dRef,itemHit))
	{
		switch (*itemHit)
		{
			case kDialogItem_Refresh :
				FillDialogTextWithTreknobabble (dRef);
				break;
			case kDialogItem_Copy :
				CopyDialogText (dRef);
				break;
			case kDialogItem_About :
				About ( );
				break;
			case kDialogItem_Speak :
				Speak (dRef);
				break;
		}
	}
}

static pascal void RespondToNonDialogEvent (const EventRecord *event)
{
	WindowRef ref;
	short partCode = FindWindow (event->where,&ref);
	if (partCode == inDrag)
	{
		Rect dragBounds = qd.screenBits.bounds;
		InsetRect (&dragBounds,4,4);
		DragWindow (ref,event->where,&dragBounds);
	}
}

void main (void)
{
	DialogRef dp;
	InitMac ( );

	dp = GetNewDialog (kResID_Dialog_Main, nil, (WindowRef) -1);
	if (dp)
	{
		short itemHit = ~kDialogItem_Quit;

		DisableSpeakButtonIfNoSpeechManager (dp);
		FillDialogTextWithTreknobabble (dp);

		do
		{
			EventRecord event;

			InitCursor ( );

			WaitNextEvent (everyEvent,&event,-1,nil);

			if (!IsDialogEvent (&event))
				RespondToNonDialogEvent (&event);
			else
				RespondToDialogEvent (&event,&itemHit);
		}
		while (itemHit != kDialogItem_Quit);

		DisposeDialog (dp);
	}
}
