/*********************************************************/
/* This source code copyright (c) 1991-2001, Aaron Giles */
/* See the Read Me file for licensing information.       */
/* Contact email: mac@aarongiles.com                     */
/*********************************************************/

//=====================================================================================
// Generic includes for Macintosh headers
//=====================================================================================

#if THINK_C
#include "THINK.Header"
#elif applec
#pragma load ":Headers:MPW.Header"
#elif __MWERKS__
//#include "MW.Header"
#else
#include "JPEGView.h"
#endif

//=====================================================================================
// VBLRecord: structure containing information about cursors that we set during VBL
//=====================================================================================

typedef struct VBLRecord
{
	VBLTask theTask;
	CursHandle spin[kNumCursors];
	ulong curSpin, lastSpin;
	Boolean spinning;
} VBLRecord, *VBLRecordPtr;

//=====================================================================================
// Global variables local to this module
//=====================================================================================

static VBLRecord gVBLTask;
static VBLUPP gSpinCursor;

//=====================================================================================
// Prototypes for functions local to this module
//=====================================================================================

static void MemoryInit(void);
static void UPPInit(void);
static void ToolBoxInit(void);
static void CheckSystem(void);
static Boolean TrapAvailable(short theTrap);
static void PaletteInit(void);
static void GeneralInit(void);

#if USESROUTINEDESCRIPTORS
static pascal void SpinCursor(VBLTaskPtr theTask);
#else
static pascal void SpinCursor(void);
#endif

static void ErrorHandler(short theErr, short resID, StringPtr addString);

//=====================================================================================
// Set compiler up to turn off 68020 code generation for these first few functions,
// until we're sure we're in a safe environment.
//=====================================================================================

#if THINK_C
#pragma options(!mc68020)
#elif applec
#pragma processor 68000
#elif __MWERKS__
#pragma code68020 off
#endif

//=====================================================================================
// void main(void)
//=====================================================================================
// Initialize the application, open any previously open floating windows, then start
// the main event loop and wait until it's done before cleaning up.
//=====================================================================================
// NOTE: This needs to be compiled compatible for 68000-based machines, since we can't
// be sure yet that we're on a 68020.
//=====================================================================================

extern void main(void) 
{
	Boolean statsOpen, commentsOpen, colorsOpen;
	
	// initialize everything
	gTitleBarHeight = kTitleBarHeight;
	MemoryInit();
	AppleEventInit();
	MonitorInit();
	MenuBarInit();
	DrawingInit();
	PaletteInit();
	GeneralInit();
	gThePrefs.reminderCount++;

	// open any windoids that were left open from last run
	if (gThePrefs.statsOpen) 
		if (OpenStats() == noErr && !gInBackground) ChangeActive(GetStatWindow());
	if (gThePrefs.commentsOpen) {
		gCommentsOpen = true;
		if (OpenComments() == noErr && !gInBackground) ChangeActive(GetCommentsWindow());
	}
	if (gThePrefs.colorsOpen)
		if (OpenColors() == noErr && !gInBackground) ChangeActive(GetColorsWindow());

	// set up the menus and take our events
	AdjustMenus();
	MainEventLoop();

	// close all the open windows, one at a time
	statsOpen = gThePrefs.statsOpen;
	commentsOpen = gThePrefs.commentsOpen;
	colorsOpen = gThePrefs.colorsOpen;
	while (FrontWindow()) DoCloseWindow(FrontWindow(), false);

	// save the preferences, reset devices, and exit
	gThePrefs.statsOpen = statsOpen;
	gThePrefs.commentsOpen = commentsOpen;
	gThePrefs.colorsOpen = colorsOpen;
	SaveGeneralPrefs();
	ResetDevices();
	if (GetFirstWindow()) CalcVisBehind((WindowPeek)GetFirstWindow(), GetGrayRgn());
	RemoveQDxDispatchPatch();
	MySetPort(nil);
	HiliteMenu(0);
	ExitToShell();
}

//=====================================================================================
// void MemoryInit(void)
//=====================================================================================
// Set up the application, call the main event loop, then clean up when done.
//=====================================================================================
// NOTE: This needs to be compiled compatible for 68000-based machines, since we can't
// be sure yet that we're on a 68020.
//=====================================================================================

static void MemoryInit(void) 
{
	Handle tempHandle;
	Str255 theString;
	short i;
	
	// increase the stack size
	SetApplLimit(GetApplLimit() - kStackSize);
	MaxApplZone();
	for (i = 0; i < 24; i++) MoreMasters();
	
	// set up all our callback function UPP's
	UPPInit();
	
	// start up the Toolbox now
	ToolBoxInit();

	// verify the system configuration
	CheckSystem();

	// set the grow zone procedure
	SetGrowZone(NewGrowZoneProc((ProcPtr)GrowZone));
	
	// allocate memory for windows & ports
	if (!(tempHandle = NewHandleClear(kMaxPorts * sizeof(DialogRecord))))
		FatalError(errNoMemory);
	MoveHHi(tempHandle), HLock(tempHandle);
	gPortPool = (DialogRecord *)StripAddress(*tempHandle);

	// load up all the JPEGView strings
	for (i = 1; i <= strLast; i++) {
		GetIndString(theString, rStrings, i);
		if (!theString[0]) FatalError(errNoLoadStrings);
		if (!(gString[i - 1] = (StringPtr)NewPtr(theString[0] + 1))) FatalError(errNoMemory);
		BlockMove(theString, gString[i - 1], theString[0] + 1);
	}
	for (i = 1; i <= kFileFormats; i++) {
		GetIndString(gFormat[i - 1]->formatName, rFormatNames, i);
		if (!gFormat[i - 1]->formatName) FatalError(errNoLoadStrings);
	}

	// load in all our custom cursors
	for (i = 0; i < kNumCursors; i++) {
		if (!(gVBLTask.spin[i] = GetCursor(rFirstSpin + i))) FatalError(errNoLoadCursor);
		MoveHHi((Handle)gVBLTask.spin[i]), HLock((Handle)gVBLTask.spin[i]);
	}
	StartSpinning();
	if (!(gCross = GetCursor(crossCursor))) FatalError(errNoLoadCursor);
	MoveHHi((Handle)gCross), HLock((Handle)gCross);
	if (!(gHand = GetCursor(rHandCursor))) FatalError(errNoLoadCursor);
	MoveHHi((Handle)gCross), HLock((Handle)gHand);
	if (!(gSize = GetCursor(rSizeCursor))) FatalError(errNoLoadCursor);
	MoveHHi((Handle)gCross), HLock((Handle)gSize);
}

//=====================================================================================
// void UPPInit(void)
//=====================================================================================
// Initializes all the global Universal Proc Pointers, for PowerPC compatibility.
//=====================================================================================
// NOTE: This needs to be compiled compatible for 68000-based machines, since we can't
// be sure yet that we're on a 68020.
//=====================================================================================

static void UPPInit(void)
{
	// from AppleEventUtils.c
	gAEIdleProc = NewAEIdleProc((ProcPtr)AEIdleProc);
	
	// from DialogUtils.c
	gGenericFilter = NewModalFilterProc((ProcPtr)GenericFilter);
	gOutlineOK = NewUserItemProc((ProcPtr)OutlineOK);
	gPopUpMenuBox = NewUserItemProc((ProcPtr)PopUpMenuBox);
	gLineItem = NewUserItemProc((ProcPtr)LineItem);
	
	// from FileMenu.c
	gOpenSaveModalFilter = NewModalFilterYDProc((ProcPtr)OpenSaveModalFilter);
	
	// from JPEGView.c
	gSpinCursor = NewVBLProc((ProcPtr)SpinCursor);
	
	// from ProgressProcs.c
	gDummyProg.prog.progressProc = NewICMProgressProc((ProcPtr)DummyProgress);
	gDummyProg.prog.progressRefCon = (long)&gDummyProg;
	gGenericProgress = NewICMProgressProc((ProcPtr)GenericProgress);
	gSlideProgress = NewICMProgressProc((ProcPtr)SlideProgress);
	gDrawProgress = NewICMProgressProc((ProcPtr)DrawProgress);
}

//=====================================================================================
// void ToolBoxInit(void)
//=====================================================================================
// Initialize the toolbox manager and get the cursors.
//=====================================================================================
// NOTE: This needs to be compiled compatible for 68000-based machines, since we can't
// be sure yet that we're on a 68020.
//=====================================================================================

static void ToolBoxInit(void) 
{
	// initialize the Toolbox here
	InitGraf(&qd.thePort);
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();
	InitDialogs(nil);
	InitCursor();

#if !defined(powerc) && !defined(__powerc)
	{
		Handle h;
		short i;

		SetResLoad(false);
		for (i = Count1Resources('CODE'); i > 0; i--) {
			h = Get1IndResource('CODE', i);
			if (*h) HLock(h);
			else {
				LoadResource(h);
				if (ResError()) {
					SetResLoad(1);
					FatalError(errNoMemory);
				} else {
					HUnlock(h);
					MoveHHi(h);
					HLock(h);
				}
			}
		}
		SetResLoad (true) ;
	}
#endif
}

//=====================================================================================
// Boolean TrapAvailable(short theTrap)
//=====================================================================================
// Verifies if a trap is available to be called; swiped from THINK Reference with minor
// changes.
//=====================================================================================
// NOTE: This needs to be compiled compatible for 68000-based machines, since we can't
// be sure yet that we're on a 68020.
//=====================================================================================

static Boolean TrapAvailable(short theTrap)
{
	short numToolboxTraps = 0x400;
	TrapType tType;
	
	tType = ((theTrap & 0x800) > 0) ? ToolTrap : OSTrap;
	if (tType == ToolTrap) theTrap &= 0x7ff;
	if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xaa6e, ToolTrap))
		numToolboxTraps = 0x200;
	if (theTrap >= numToolboxTraps) return false;
	return (NGetTrapAddress(theTrap, tType) != NGetTrapAddress(_Unimplemented, ToolTrap));
}

//=====================================================================================
// void CheckSystem(void)
//=====================================================================================
// Check gestalt to be sure we have everything we need.
//=====================================================================================
// NOTE: This needs to be compiled compatible for 68000-based machines, since we can't
// be sure yet that we're on a 68020.
//=====================================================================================

static void CheckSystem(void)
{
	OSErr theErr;
	long resp;

	// die a quick death if not on 68020
	if (!TrapAvailable(_Gestalt) ||
		Gestalt(gestaltProcessorType, &resp) != noErr || resp < 3) {
		StopAlert(rNeeds68020Alert, nil);
		ExitToShell();
	}
	
	// do the same if we're not running System 7
	if (Gestalt(gestaltSystemVersion, &resp) != noErr || resp < 0x700) {
		StopAlert(rNeedsSystem7Alert, nil);
		ExitToShell();
	}
		
	// verify QuickTime and image compression manager versions
	if (Gestalt(gestaltCompressionMgr, &resp) == noErr && resp >= 15) {
		if (Gestalt(gestaltQuickTime, &resp) == noErr) {
			if ((gQTVersion = resp >> 16) < 0x100) MinorError(errFunnyQTVersion);
			if (GetCodecInfo(&gJPEGInfo, kJPEGCompression, anyCodec) != noErr) gQTVersion = 0;
#if defined(powerc) || defined(__powerc)
			if ((Ptr)DecompressImage == kUnresolvedSymbolAddress) gQTVersion = 0;
#endif
		}
	}
	KeepSpinning();
	MySetPort(nil);

	// see if the Drag Manager is present
	if (Gestalt(gestaltDragMgrAttr, &resp) == noErr && (resp & (1L << gestaltDragMgrPresent))) {
		gDragMgrPresent = true;
#if defined(powerc) || defined(__powerc)
		if ((Ptr)InstallTrackingHandler == kUnresolvedSymbolAddress) gDragMgrPresent = false;
#endif
	}

	// load the JPEGView icon family
	theErr = GetIconSuite(&gIconSuite, rAPPLType, svAllAvailableData);
	if (theErr != noErr) FatalError(errNoMemory);
	HNoPurge(gIconSuite);
	
	// set up our notification mgr item
	gTheNotification.qType = nmType;
	gTheNotification.nmMark = 1;			// put diamond in application menu
	gTheNotification.nmIcon = gIconSuite;	// flashing icon handle
	gTheNotification.nmSound = (Handle)-1;	// system beep sound
	gTheNotification.nmStr = nil;			// no alert box
	gTheNotification.nmResp = nil;			// no response procedure
	gTheNotification.nmRefCon = 0;			// no ref con
}

//=====================================================================================
// Reset compiler to generate 68020 code again.
//=====================================================================================

#if THINK_C
#pragma options(mc68020)
#elif applec
#pragma processor 68020
#elif __MWERKS__
#pragma code68020 off
#endif

//=====================================================================================
// void PaletteInit(void)
//=====================================================================================
// Load up all the system color tables and convert them to palettes.
//=====================================================================================

static void PaletteInit(void)
{
	CTabHandle theColors;
	short i;

	for (i = 1; i <= 8; i <<= 1) {
		if (theColors = GetCTable(64 + i)) {
			if (gColorPalette[i] = NewPalette(1 << i, theColors, pmTolerant, 0))
				DisposCTable(theColors);
			else FatalError(errNoMemory);
		} else FatalError(errNoLoadCLUT);
		if (theColors = GetCTable(32 + i)) {
			if (gGreyPalette[i] = NewPalette(1 << i, theColors, pmTolerant, 0))
				DisposCTable(theColors);
			else FatalError(errNoMemory);
		} else FatalError(errNoLoadCLUT);
	}
	KeepSpinning();
}

//=====================================================================================
// void GeneralInit(void)
//=====================================================================================
// Do the rest of the initialization.
//=====================================================================================

static void GeneralInit(void) 
{
	Rect prevRect = { 0, 0, 80, 80 }, iconRect = { 0, 0, 32, 32 };
	ProcessSerialNumber thePSN, selfPSN = { 0, kCurrentProcess };
	short refNum, oldRefNum = CurResFile();
	ProcessInfoRec theInfo;
	EventRecord theEvent;
	Intl0Hndl intlData;
	FSSpec theSpec;
	OSErr theErr;
	ushort i;

	// find the JPEGView help file and open its resource fork
	theInfo.processAppSpec = &theSpec;
	theInfo.processInfoLength = sizeof(ProcessInfoRec);
	theInfo.processName = nil;
	theErr = GetProcessInformation(&selfPSN, &theInfo);
	if (theErr == noErr) {
		theErr = FSMakeFSSpec(theSpec.vRefNum, theSpec.parID, gString[strHelpFile], &theSpec);
		if (theErr == noErr) {
			refNum = FSpOpenResFile(&theSpec, fsRdPerm);
			UseResFile(oldRefNum);
			gHelpFound = true;
		}
	}

	// install and remove our QDxDispatch patch to force it to load
	InstallQDxDispatchPatch(false);
	RemoveQDxDispatchPatch();

	// take a few events to let our background status become known
	for (i = 0; i < 4; i++)
		WaitNextEvent(mDownMask + keyDownMask + keyUpMask + autoKeyMask +
					updateMask + activMask, &theEvent, kSleepTime, nil);

	// determine our background status
	GetFrontProcess(&thePSN);
	SameProcess(&selfPSN, &thePSN, &gInBackground);
	gInBackground = !gInBackground;

	// initialize the slide show options
	if (LoadPrefsHandle(kPrefsSlideType, 0, (Handle *)&gSlideOptions) != noErr)
		if (PtrToHand((Ptr)&gDefaultSlideOptions, (Handle *)&gSlideOptions, sizeof(SlideOptions)) != noErr)
			FatalError(errNoMemory);

	// load the preferences
	LoadGeneralPrefs();

	// create the preview and icon offscreen worlds
	if (!(gGenericGWorld = gPreviewGWorld = MyNewGWorld(&prevRect, 32, nil, nil, false, false)) ||
		!(gIconGWorld = MyNewGWorld(&iconRect, 32, nil, nil, false, false))) 
		FatalError(errNoMemory);

	// seed the random number generator
	qd.randSeed = TickCount();
	KeepSpinning();

	// get some international number info
	if (intlData = (Intl0Hndl)IUGetIntl(0)) {
		gThousandsSep = (*intlData)->thousSep;
		gDecimal = (*intlData)->decimalPt;
		gListSep = (*intlData)->listSep;
	}
}

//=====================================================================================
// void StartSpinning(void)
//=====================================================================================
// Initialize the VBL task to begin spinning the cursor.
//=====================================================================================

extern void StartSpinning(void)
{
	if (gVBLTask.spinning) return;	
	gVBLTask.spinning = true;
	gVBLTask.curSpin %= kNumCursors;
	KeepSpinning();
	gVBLTask.theTask.qType = vType;
	gVBLTask.theTask.vblAddr = gSpinCursor;
	gVBLTask.theTask.vblCount = kSpinContinue;
	gVBLTask.theTask.vblPhase = 0;
	SetCursor(*gVBLTask.spin[gVBLTask.curSpin]);
	VInstall((QElemPtr)&gVBLTask);
}

//=====================================================================================
// void StopSpinning(CursPtr newCursor)
//=====================================================================================
// Remove the spinning task from the VBL queue.
//=====================================================================================

extern void StopSpinning(CursPtr newCursor)
{
	if (gVBLTask.spinning) VRemove((QElemPtr)&gVBLTask);
	gVBLTask.spinning = false;
	SetCursor(newCursor);
}

//=====================================================================================
// void KeepSpinning(void)
//=====================================================================================
// Keep the cursor spinning for another two cycles.
//=====================================================================================

extern void KeepSpinning(void)
{
	gVBLTask.lastSpin = ((gVBLTask.curSpin / kNumCursors) + 2) * kNumCursors;
}

//=====================================================================================
// void SpinIndef(void)
//=====================================================================================
// Keep the cursor spinning indefinitely.
//=====================================================================================

extern void SpinIndef(void)
{
	gVBLTask.lastSpin = 0x7fffffff;
}

//=====================================================================================
// pascal long GetVBLRec(void)
//=====================================================================================
// Some inline assembly to return the value in A0, which is a pointer to the VBLTask
// record.
//=====================================================================================
// NOTE: This is only needed for the 680x0 version of the application; PowerPC versions
// get the address of the VBLTask passed as a parameter, thanks to Apple's trickery.
//=====================================================================================

#if !USESROUTINEDESCRIPTORS
static pascal long GetVBLRec(void) = 0x2e88;
#endif

//=====================================================================================
// pascal void SpinCursor(void)
//=====================================================================================
// Actually spin the cursor; this is the VBL task function we point to.  Note that we
// absolutely must not move memory here.
//=====================================================================================
// NOTE: We use some conditional macros here to accomodate the nicer PowerPC version of
// this function.
//=====================================================================================

#if USESROUTINEDESCRIPTORS
static pascal void SpinCursor(VBLTaskPtr theTask)
{
	VBLRecordPtr theData = (VBLRecordPtr)theTask;
#else
static pascal void SpinCursor(void)
{
	VBLRecordPtr theData = (VBLRecordPtr)GetVBLRec();
#endif
	if (!LMGetCrsrBusy() && theData->curSpin < theData->lastSpin) 
		SetCursor(*theData->spin[++theData->curSpin % kNumCursors]);
	theData->theTask.vblCount = kSpinFrequency;
}

//=====================================================================================
// void HandleAEError(OSErr theErr, short memErr, StringPtr theString)
//=====================================================================================
// Handle an error returned from an apple event.
//=====================================================================================

extern void HandleAEError(OSErr theErr, short memErr, StringPtr theString)
{
	if (gIntError) StringError(gIntError, theString);
	else switch (theErr) {
		case noErr:
			break;
		case errNoHelpFound:
			MinorError(theErr);
			break;
		case mFulErr:
		case memFullErr:
			if (memErr) StringError(memErr, theString);
			else MinorError(errNoMemory);
			break;
		case codecAbortErr:
			break;
		case codecBadDataErr:
			StringError(errCorruptImage, theString);
			break;
		case -4101:
			MinorError(errNoPrinterSelected);
			break;
		case iPrSavPFil:
			StringError(errCantPrint, theString);
			break;
		default:
			ErrorHandler(theErr, 0, gNullString);
			break;
	}
	gIntError = 0;
}

//=====================================================================================
// void MinorError(short theErr)
//=====================================================================================
// Display an alert for a minor error.
//=====================================================================================

extern void MinorError(short theErr)
{
	ErrorHandler(theErr, rMinorErrors, nil);
}

//=====================================================================================
// void FatalError(short theErr)
//=====================================================================================
// Display an alert for a fatal error.
//=====================================================================================

extern void FatalError(short theErr)
{
	ErrorHandler(theErr, rFatalErrors, nil);
}

//=====================================================================================
// void StringError(short theErr, StringPtr origString)
//=====================================================================================
// Display an alert for a minor error that includes an inserted string.
//=====================================================================================

extern void StringError(short theErr, StringPtr origString)
{
	Str255 theString;
	
	if (origString) {
		BlockMove(origString, theString, *origString + 1);
		ErrorHandler(theErr, rStringErrors, theString);
	} else ErrorHandler(theErr, rGenericStringErrors, nil);
}

//=====================================================================================
// void ErrorHandler(short theErr, short resID, StringPtr addString)
//=====================================================================================
// Generic error handler.  Note that we should post a notification manager event if
// we're in the background.
//=====================================================================================

static void ErrorHandler(short theErr, short resID, StringPtr addString) 
{
	Boolean isFatal = (resID == rFatalErrors);
	Str255 theString;
	OSErr AEErr;
	
	// non-fatal errors in slide shows might need to be ignored
	if (!isFatal && gSlideShow && (*gSlideOptions)->noErrors) return;

	// any error during a screen saver slide show should return and abort
	if (gScreenSaver) {
		SendQuitApplication();
		return;
	}

	// if we're in an Apple event, be polite and request to move to the front
	if (gInAppleEvent) {
		AEErr = AEInteractWithUser(kAEDefaultTimeout, &gTheNotification, gAEIdleProc);
		if (AEErr == errAENoUserInteraction) {
			if (isFatal) ExitToShell();
			else return;
		}
	}

	// if we're in the background and it's not fatal, post a notification manager event
	if (gInBackground) RequestForeground();
	
	// otherwise, display the appropriate alert with the appropriate text, if any
	MySetPort(nil);
	StopSpinning(&qd.arrow);
	if (resID) {
		GetIndString(theString, resID, theErr);
		if (theString[0]) {
			if (addString) ParamText(theString, addString, gNullString, gNullString);
			else ParamText(theString, gNullString, gNullString, gNullString);
		} else {
			ParamText((StringPtr)"\pCouldnŐt load the error messages!", gNullString, gNullString, gNullString);
			isFatal = true;
		}
		if (isFatal) {
			FWStopAlert(CenterAlert(rFatalAlert), nil);
			ExitToShell();
		} else FWNoteAlert(CenterAlert(rMinorAlert), gGenericFilter);
	} else {
		NumToString(theErr, theString);
		ParamText(theString, gNullString, gNullString, gNullString);
		FWNoteAlert(CenterAlert(rUnknownAlert), gGenericFilter);
	}
}

//=====================================================================================
// void RequestForeground(void)
//=====================================================================================
// Post a notifcation then sit in a loop, similar to the AppleEvent idle proc, and wait
// until we get bumped to the foreground.
//=====================================================================================

extern void RequestForeground(void)
{
	if (!gInBackground) return;
	NMInstall(&gTheNotification);
	while (gInBackground) {
		RgnHandle mouseRgn = nil;
		long sleep = kSleepTime;
		EventRecord theEvent;
	
		MySetPort((CGrafPtr)FWFrontWindow());
		WaitNextEvent(activMask + updateMask + osMask + diskMask, &theEvent, sleep, 
					mouseRgn);
		AEIdleProc(&theEvent, &sleep, &mouseRgn);
	}
	NMRemove(&gTheNotification);
}
