/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

#ifdef NAG
#include "nag.h"
#define FILE_NUM 120

#pragma segment Nagging

#define	kUpdateNagScheduleDuringBeta		0x55555555		// Update checks every other day
#define	kUpdateNagIntervalDuringBeta		2

#define	kDelayBeforeSwitchingRepayUsersToSponsored	(60 * 60 * 60)	// 60 ticks x 60 seconds x 60 minutes
long	gTimeAtWhichRepayUsersBecomeSponsored = 0;

extern ModalFilterUPP DlgFilterUPP;
OSErr PutFeaturesInto (DialogPtr theDialog, short theItem);

#define NAG_INTRO_DLOG_OK	1
#define NAG_INTRO_INFO_BTN	3
#define NAG_INTRO_CODE_BTN	2

void CheckAdQT(void);

//
//	InitNagging
//
//		This is not a real function.  It exists right now mostly to initialize the user state
//		for our early betas.  It should be replaced by a function that erifies the user's
//		registration state.  For now, the user is a newUser if the 'NagS' resource is not
//		present.  If it is present, we automatically make them an Ad User.
//				

InitNagResultType InitNagging (void)

{
	InitNagResultType		result;
	UserStateType				newState;
	uLong								regDate;
	int									pnPolicyCode;
	int									regMonth;
	Boolean							foundRegFile,
											needsRegistration;
	
	needsRegistration	= false;
	
	// Grab the nag state information from the settings file, or create a new
	// resource if we didn't fint what we wanted
	
	nagState = GetResourceFromFile (NAG_STATE_TYPE, NAG_STATE_ID, SettingsRefN);
	if (!nagState)
		if (nagState = NuHandleClear (sizeof (NagStateRec))) {
			(*nagState)->version = NAG_STATE_VERS;
#ifdef I_HATE_THE_BOX
			(*nagState)->state	 = boxUser;
#else
			if (ValidRegCode(paidUser,&pnPolicyCode,&regMonth) && PolicyCheck (pnPolicyCode, regMonth))
				(*nagState)->state	 = paidUser;
			else
				(*nagState)->state	 = newUser;
#endif
			(*nagState)->regDate = 0;
			SettingsHandle (NAG_STATE_TYPE, nil, NAG_STATE_ID, nagState);
		}
		else
			DieWithError (MEM_ERR, MemError ());

#ifdef DEATH_BUILD
	newState = paidUser;
	result	 = noDialogPending;
#else
	// If we somehow happened upon an old EP 4 user, they will become adware users for 5.0
	if (IsEP4User ())
#ifdef I_HATE_THE_BOX
		(*nagState)->state = boxUser;
#else
		(*nagState)->state = adwareUser;
#endif
	

#ifdef I_HATE_THE_BOX
	if (!PrefIsSet (PREF_BOX_NO_LONGER_VIRGIN)) {
		(*nagState)->state = boxUser;
		SetPref(PREF_BOX_NO_LONGER_VIRGIN,"\py");
	}
#else
	// If someone ran as a box user, then switches to a non-box build, make them a paid user
	// (which we eventually reg validate)
	if ((*nagState)->state == boxUser)
		(*nagState)->state = paidUser;
#endif
#endif

	// Look for a RegCode file next to the Eudora application
	regDate = (*nagState)->regDate;
#ifndef DEATH_BUILD
	foundRegFile = CheckForRegCodeFile (&needsRegistration, &regDate, &pnPolicyCode);
	(*nagState)->regDate = regDate;
#endif

	ChangedResource ((Handle) nagState);
	MyUpdateResFile	(SettingsRefN);
	DetachResource ((Handle) nagState);
	
	// Choose the user's initial state from all we know of the world
#ifndef DEATH_BUILD
	newState = ChooseInitialUserState ((*nagState)->state, foundRegFile, needsRegistration, pnPolicyCode, &result);
#endif

	gCanPayMode = UserHasValidPaidModeRegcode();
	
	if (newState != (*nagState)->state)
		TransitionState (newState);

	CheckAdQT();
	return (result);
}

void DoPendingNagDialog (InitNagResultType pendingNagResult)

{
	switch (pendingNagResult) {
		case paymentRegPending:
			OpenPayWin ();
			break;
		case codeEntryPending:
			(void) CodeEntryForProduct (paidUser, invalidRegCodeEntryVariant);
			break;
		case repayPending:
		case gracelessRepayPending:
			RepayDialog (pendingNagResult);
			break;
	}
}


//
//	CheckNagging
//
//
//		It's time to nag if there is a nag day greater than the last nag, and
// 		less than or equal to the current day.  Nag days are stored in the 'Nag '
// 		resource as a bit mask corresponding to days 0 thru 31 of the nag
// 		schedule.  We can accomplish this by a strange and non-obvious formula
// 		(yet somehow occurred to me one evening while watching Stephen King's
//		"Storm Of The Century"):
//
//				[((1 << (currentDay + 1)) - 1) - ((1 << (lastDay + 1)) - 1)] & schedule
//
// 				(Note that lastDay MUST be <= currentDay...)
//
//		Here's how it works:
//
//				Since each day since the nagBase is represented by a single bit, we
//				can left shift a normalized day to represent that particular day.
//				Likewise, subtracting 1 from a single shifted bit turns on all bits
//				less than the set bit.  Subtracting one such one-subtracted bit value
//				from a second will result in setting all bits between one bit and another.
//
// 		For example:
//
//				NagBase  = 0 (always)
//				lastDay  = 6
//		    Current  = 11
//	  		Schedule = [0, 4, 9, 12, 3]    Mask is  0000 0000 0000 0000 0001 0010 0001 0001
//
//				Using the formula above...
//
//						currentDay + 1						:  12
//						left shift 1 by 12 bits		:  0000 0000 0000 0000 0001 0000 0000 0000
//			  		subtract 1								:  0000 0000 0000 0000 0000 1111 1111 1111   1st value
//
//						lastDay + 1							  :  7
//						left shift 1 by 7 bits    :  0000 0000 0000 0000 0000 0000 1000 0000
//						subtract 1								:	 0000 0000 0000 0000 0000 0000 0111 1111   2nd value
//
//						subtract the 2nd from 1st :  0000 0000 0000 0000 0000 1111 1000 0000
//
//						Which means that we'll nag if there is a nag scheduled for any of the
//						days set in the results, so... let's AND it with the schedule.
//
//																				 0000 0000 0000 0000 0000 1111 1000 0000
//																		AND  0000 0000 0000 0000 0001 0010 0001 0001
//																		--------------------------------------------
//																				 0000 0000 0000 0000 0000 0010 0000 0000  NAG!!!
//
//		Of course, the above only works during the days covered by the nag schedule itself.
//		Once we go past this date we have to look at the nag interval to make the determination
//		of whether or no we will nag.
//

void CheckNagging (UserStateType state)

{
	NagRec								nag;
	NagUsageHandle				nagUsage;
	NagDialogInitProcPtr	initProc;
	NagDialogHitProcPtr		hitProc;
	ModalFilterUPP				theFilterProc;
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
	AdFailureRec					adFailure;
#endif
	OSErr									theError;
	long									refcon;
	uLong									nagBaseSeconds,			// In seconds
												lastNagDay,					// In days
												currentDay,					// In days
												lastScheduledDay,		// In days
												nextNagDay,					// In days
												currentTime,				// In seconds
												currentDayMask,
												lastDayMask;
	short									dialogID,
												nagID,
												index;
	Boolean								nagMe,
												saveMe,
												ignoreDefaultItem;

	nagMe		 = false;
	saveMe	 = false;
	index		 = 1;
	theError = noErr;
	
	// Check to see if it's time to change repay users to sponsored mode
	if (IsRepayUser () && gTimeAtWhichRepayUsersBecomeSponsored)
		if (TickCount () >= gTimeAtWhichRepayUsersBecomeSponsored) {
			TransitionState (RegisteredUser (paidUser) ? regAdwareUser : adwareUser);
			gTimeAtWhichRepayUsersBecomeSponsored = 0;
			return;
		}
	
	// Check each of the possible nag scedules for this state -- until we find it's time to nag
	while (!theError && !GetIndNagState (&nag, &nagID, state, index++)) {
		// Get the nag usage record for the particular nag dialog we're thinking about (as saved in settings)
		nagUsage = GetResourceFromFile (NAG_USAGE_TYPE, nagID, SettingsRefN);
			
		// If the nag usage record is empty, create one and store it in settings
		currentTime = LocalDateTime ();
		if (!nagUsage)
			if (nagUsage = NuHandleClear (sizeof (NagUsageRec))) {
				(*nagUsage)->version = NAG_USAGE_VERS;
				(*nagUsage)->nagBase = currentTime;
				(*nagUsage)->lastNag = 0;
				(*nagUsage)->aux		 = 0;
				if (!SettingsHandle (NAG_USAGE_TYPE, nil, nagID, nagUsage))
					MyUpdateResFile	(SettingsRefN);
				else
					ZapHandle (nagUsage);
			}
#ifdef BETA_UPDATE_SCHEDULE
		if (nag.flags & nagFlagSilentUpdate) {
			nag.schedule = kUpdateNagScheduleDuringBeta;
			nag.interval = kUpdateNagIntervalDuringBeta;
		}
#endif

		if (nagUsage) {
			// Normalize the dates for the nag base, last nag and the current
			// day in order to calculate the days-since-nagBase
			nagBaseSeconds = SecondsToWholeDay ((*nagUsage)->nagBase);
			lastNagDay		 = (*nagUsage)->lastNag ? (SecondsToWholeDay ((*nagUsage)->lastNag) - nagBaseSeconds) / kSecondsPerDay : 0;
			currentDay		 = (SecondsToWholeDay (currentTime) - nagBaseSeconds) / kSecondsPerDay;

			// Figure out the last scheduled day (it's the most significant set bit of the schedule)
			lastScheduledDay = MostSignificantSetBit (nag.schedule);
			
			// The dialog that we _might_ nag with
			dialogID = nag.dialogID;
			
			// If the last nag day falls within the schedule, we can use our spiffy formula,
			// otherwise we'll have to more laboriously test to see if the nag day falls into
			// the correct range using the nag interval
			// (jp) 2-5-00  Previously, we were checking to see if the last day we nagged fell within the
			//							range of the nag schedule.  For example, in a schedule like 7,14,3 we'd be checking
			//							to see if the last day we nagged was less than or equal to 14 -- the last day in the
			//							schedule before the nag interval kicked in.  This was wrong, however, and caused
			//							us to never again check in the current day was past the final day of the range (as was
			//							the case for the ad failure check).  The correct algorithm is to check the current
			//							day against the range, use the bit mask if we're still running within the range, otherwise
			//							we take into account the nag interval.
//			if (lastNagDay <= lastScheduledDay) {
			if (currentDay <= lastScheduledDay) {
				currentDayMask = (1 << (currentDay + 1)) - 1;
				lastDayMask		 = (*nagUsage)->lastNag ? (1 << (lastNagDay + 1)) - 1 : 0;
				nagMe = (currentDayMask - lastDayMask) & nag.schedule ? true : false;
			}
			else {
				nextNagDay = lastScheduledDay + nag.interval * ((lastNagDay - lastScheduledDay) / nag.interval) + nag.interval;
				nagMe = nextNagDay > lastNagDay && nextNagDay <= currentDay;
			}
			// If it is time to nag the user in whichever manner is appropriate -- this might be a dialog, or
			// it might be a request to perform some operation.  We should also update the 'NagU' resource.
			if (nagMe)
				if (nag.flags & nagFlagSilentUpdate && !PrefIsSet (PREF_DISABLE_AUTO_UPDATE_CHECK) && !Offline) {
#ifndef DO_NOT_UPDATE_CHECK_IN_DEATH_BUILDS
					theError = UpdateCheck (true, false);
					(*nagUsage)->lastNag = currentTime;
					saveMe = true;
#endif
					nagMe  = false;
				} else
				if (nag.flags & nagFlagUpdate && !PrefIsSet (PREF_DISABLE_AUTO_UPDATE_CHECK) && !Offline) {
#ifndef DO_NOT_UPDATE_CHECK_IN_DEATH_BUILDS
					theError = UpdateCheck (false, false);
					(*nagUsage)->lastNag = currentTime;
					saveMe = true;
#endif
					nagMe	 = false;
				}
				else
				if (nag.flags & nagFlagAdFailureCheck) {
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
					// Check for ad failures and deadbeatness
					LDRef (nagUsage);
					adFailure.error = AdFailureCheck (nagUsage, currentTime, &nagMe, &dialogID, adFailure.text);
					UL (nagUsage);
					(*nagUsage)->lastNag = currentTime;
					saveMe = true;
#else
					nagMe = false;
#endif
				}

#ifdef DEATH_BUILD
			if (nagMe && dialogID == NAG_PLEASE_REGISTER_DLOG)
				nagMe = false;
#endif

			// If we're still nagging (no error and a flag didn't handle things), nag away!
			if (!theError && nagMe) {
				initProc			= nil;
				hitProc				= nil;
				theFilterProc	= nil;
				refcon				= 0;
				ignoreDefaultItem	= false;
				switch (dialogID) {
					case NAG_INTRO_DLOG:
						hitProc = IntroDlogHit;
						theFilterProc = DlgFilterUPP;
						break;
					case NAG_PLEASE_REGISTER_DLOG:
						hitProc = AdUserHit;
						break;
					case NAG_DOWNGRADE_DLOG:
						initProc = DowngradeDlogInit;
						hitProc	 = DowngradeDlogHit;
						break;
					case NAG_FEATURES_DLOG:
						initProc = FeaturesDlogInit;
						hitProc	 = FeaturesDlogHit;
						break;
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
					case NAG_NOT_GETTING_ADS_DLOG:
						initProc = NotGettingAdsDlogInit;
						hitProc	 = NotGettingAdsDlogHit;
						refcon	 = (long) &adFailure;
						break;
					case NAG_FRIGGING_HACKER_DLOG:
						hitProc						= NoAdsRevertToFreeDlogHit;
						theFilterProc			= DlgFilterUPP;
						ignoreDefaultItem	= true;
						break;
#endif
				}
				theError = Nag (dialogID, initProc, hitProc, theFilterProc, ignoreDefaultItem, refcon);
				if (!theError) {
					(*nagUsage)->lastNag = currentTime;
					saveMe = true;
				}
			}
			if (!theError || saveMe) {
				ChangedResource ((Handle) nagUsage);
				if (!ResError ()) 
					MyUpdateResFile	(SettingsRefN);
			}
			ReleaseResource (nagUsage);
		}
	}
}




//
//	Nag
//
//		Displays a Nag dialog to the user.  The parameters look like so:
//
//			dialogID					-		Resource ID of the 'DLOG' to be displayed
//			initProc					-		NagDialogInitProcPtr callback issued after the dialog is successfully
//														created.  This allows you to perform any dialog special initialization
//														before modeless dialog handling kicks in (for instance, so we can setup
//														the feature list in the downgrade dialog).
//			hitProc						-		NagDialogHitProcPtr that is called whenever an item in the dialog is hit.
//														The dialog hit proc accepts a pointer to an event, a dialog pointer, and
//														the item that was hit.
//			theFilterProc			-		An optional modal filter proc which -- if present -- turns this nag into
//														a movable modal dialog.  Pass in nil if you want the Nag to behave in a
//														modeless manner.
//			ignoreDefaultItem	-		'true' if you don't want item 1 to be a default item
//
//		Quick nag dialog note...  Whenever a new nag dialog is added to the resource fork
//		don't forget:
//
//				1. Add the DLOG resID to the 'hlist' in AddDlgx
//				2. Add a 'dftb' resource with the same id to Common.r
//
//		(There, it helps to write things down he said after spending an hour trying to figure
//		out why GetDialogItemAsControl had broken and all of the dialog text was in the system font).
//
//		Sort of, kind of important note.... Do not define a Nag with a dialog ID of zero!!
//
//stuvoid DebugDialogItems (DialogPtr theDialog);

OSErr Nag (short dialogID, NagDialogInitProcPtr initProc, NagDialogHitProcPtr hitProc, ModalFilterUPP theFilterProc, Boolean ignoreDefaultItem, long dialogRefcon)

{
	MyWindowPtr	dlogWin,
							win;
	DialogPtr		dlog;
	WindowPtr		theWindow;
	OSErr				theError;
	short				dItem;
	Boolean			done;

	if (!dialogID)
		return (noErr);
		
	// Is this nag already open?
	if (win = FindNag (dialogID)) {
		UserSelectWindow (GetMyWindowWindowPtr(win));
		return (noErr);
	}

	dlogWin = GetNewMyDialog (dialogID, nil, nil, InFront);
	theError = ResError ();

	if (dlogWin) {
		dlog = GetMyWindowDialogPtr (dlogWin);
		theWindow = GetDialogWindow(dlog);
		dlogWin->hit							 = hitProc;
		dlogWin->hideme						 = true;
		dlogWin->ignoreDefaultItem = ignoreDefaultItem;
		dlogWin->centerAsDefault	 = true;
		dlogWin->isNag						 = true;
		dlogWin->dialogRefcon			 = dialogRefcon;

		if (initProc)
			theError = (*initProc) (dlog, dialogRefcon);
		
		if (!theError) {
			HiliteButtonOne (dlog);
			theError = SubstituteLongStaticTextItems (dlog);
			if (AppearanceVersion() < 0x0110)
				;	// Eventually we need to write our own AutoSizeDialog hack for Appearance 1.0.3
			else {
				AutoSizeDialog(dlog);
				GrowGroupItems(theWindow,nil);
			}
//DebugDialogItems (theDialog);
		}
		
		if (!theError) {
			if (theFilterProc) {
				StartMovableModal (dlog);
				ShowWindow (theWindow);
				AuditWindowOpen(dlogWin->windex,dialogID,0);
				done = false;
				while (!done) {
					MovableModalDialog (dlog, theFilterProc, &dItem);
					if (dItem == CANCEL_ITEM)		// For some reason, CANCEL_ITEM is -1É huh?
						done = true;
					else
						if (hitProc)
							done = (*hitProc) (nil, dlog, dItem, dialogRefcon);
				}
				EndMovableModal (dlog);
				CloseMyWindow (theWindow);
				AuditWindowClose(dlogWin->windex);
			}
			else
				ShowMyWindow (theWindow);
		}
		else
			DisposeDialog_ (dlog);
	}
	return (theError);
}	


MyWindowPtr FindNag (short dialogID)

{
	WindowPtr	winWP;
	MyWindowPtr	win;
		
	for (winWP = GetWindowList (); winWP; winWP = GetNextWindow (winWP)) {
		win = GetWindowMyWindowPtr(winWP);
		if (IsKnownWindowMyWindow(winWP) && win->dialogID == dialogID)
			return (win);
	}
	return (nil);
}




void DownGradeDialog (void)

{
	(void) Nag (NAG_DOWNGRADE_DLOG, DowngradeDlogInit, DowngradeDlogHit, nil, true, nil);
}

void NotifyDownGradeDialog (void)

{
	FeatureCellHandle	newFeatureList;
	GrafPtr						oldPort;
	ControlHandle			theControl;
	ListHandle				theList;
	MyWindowPtr				theDialogWin;
	OSErr							theError;
	Rect							contrlRect;
	Size							actualSize;

	if (theDialogWin = FindNag (NAG_DOWNGRADE_DLOG)) {
		DialogPtr	theDialog = GetMyWindowDialogPtr (theDialogWin);
		theError = GetDialogItemAsControl (theDialog, NAG_DOWNGRADE_FEATURE_LIST, &theControl);
		if (!theError)
			theError = GetControlData (theControl, kControlEntireControl, kControlListBoxListHandleTag, sizeof (theList), (Ptr) &theList, &actualSize);
		if (!theError && theList)
			if (newFeatureList = BuildFeatureList (false)) {
				if (memcmp (LDRef (newFeatureList), LDRef((*theList)->userHandle), GetHandleSize (newFeatureList))) {
					GetControlBounds(theControl,&contrlRect);
					GetPort (&oldPort);
					SetPort (GetWindowPort(GetDialogWindow(theDialog)));
					InvalWindowRect(GetDialogWindow(theDialog),&contrlRect);
					SetPort (oldPort);
				}
				UL (newFeatureList);
				ZapHandle ((*theList)->userHandle);
				(*theList)->userHandle = newFeatureList;
			}
	}
}


Boolean RepayHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon);
Boolean JunkDownHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon);
extern Boolean PreRegHitProc (EventRecord *event, DialogPtr theDialog, short itemHit, long refcon);

void RepayDialog (InitNagResultType pendingNagResult)

{
	UserStateType	newState;
	short					itemHit;
	
	if (!Nag (REPAY_DLOG, nil, RepayHitProc, DlgFilterUPP, true, (long) &itemHit)) {
		switch (itemHit) {
			case REPAY_DITL_PAY_NOW:
				if (pendingNagResult != gracelessRepayPending)
					ComposeStdAlert (kAlertNoteAlert, GRACE_PERIOD_PAY_NOW_ALRT);
				OpenAdwareURL (paidUser, REG_SITE, actionPay, paymentQuery, nil);
				// If the user is trying to pay, we'll temporarily make them a 'repay' user.  This effectively
				// leaves them in paid mode for some length of time (currently an hour), or until they restart
				// Eudora.
				newState = repayUser;
				break;
			case REPAY_DITL_SPONSORED:
				// Profile deadbeat users are subject to the same profile requirements
				// as if they clicked 'Sponsored' in Payment & Registration
				if (IsProfileDeadbeatUser()) {
					// User must profile
					if (SendUserToProfile())
						if (kAlertStdAlertOKButton==ComposeStdAlert(Note,-PROFILING_NOW))
							// user has indicated he has profiled.  Make a playlist request
							ForcePlaylistRequest();
					return;
				}
				else
					newState = IsRegisteredUser () ? regAdwareUser : adwareUser;
				break;
		}
	}
	// If no grace time is allowed, shoo them immediately into sponsored mode
	if (pendingNagResult == gracelessRepayPending)
		newState = IsRegisteredUser () ? regAdwareUser : adwareUser;
	TransitionState (newState);
}

void JunkDownDialog (void)
{
	short					itemHit;
	
	SetPrefBit(PREF_JUNK_MAILBOX,bJunkPrefDeadPluginsWarning);
	if (!Nag (JUNKDOWN_DLOG, nil, JunkDownHitProc, DlgFilterUPP, true, (long) &itemHit)) {
		switch (itemHit) {
			case JUNKDOWN_DITL_PAY_NOW:
				OpenAdwareURL (paidUser, REG_SITE, actionPay, paymentQuery, nil);
				break;
		}
	}
}

Boolean RepayHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon)
{
	short	*dItem;
	
	dItem = (short *) refcon;
	*dItem = itemHit;
	if (itemHit==REPAY_DITL_SHOW_VERSIONS)
	{
		OpenAdwareURL (paidUser, UPDATE_SITE, actionArchived, updateQuery, nil);
		return false;
	}
	return (true);
}

Boolean JunkDownHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon)

{
	short	*dItem;
	
	dItem = (short *) refcon;
	*dItem = itemHit;
	if (itemHit==JUNKDOWN_DITL_MORE)
	{
    OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, junkDownQuery, topicJunkDown);
		return false;
	}
	return (true);
}

Boolean AdUserHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)

{
	short	action;
	
	switch (itemHit) {
		case NAG_PLEASE_REGISTER_REGISTER_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
#ifdef I_HATE_THE_BOX
			action = IsAdwareMode () ? actionRegisterAd : (IsFreeMode () ? actionRegisterFree : actionRegister50box);
#else
			action = IsAdwareMode () ? actionRegisterAd : actionRegisterFree;
#endif
			OpenAdwareURL (GetNagState (), REG_SITE, action, registrationQuery, nil);
			break;
		case NAG_PLEASE_REGISTER_LATER_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
			break;
	}
	return (true);
}

Boolean IntroDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
{
        // Transition to Adware no matter what
        TransitionState (adwareUser);
        switch (itemHit) {
                case NAG_INTRO_DLOG_OK:
												CloseMyWindow (GetDialogWindow(theDialog));
                        break;
                case NAG_INTRO_INFO_BTN:
                				CloseMyWindow (GetDialogWindow(theDialog));
                        OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionIntro, introQuery, nil);
                        break;
                case NAG_INTRO_CODE_BTN:
                				CloseMyWindow(GetDialogWindow(theDialog));
                        CodeEntryDialog(nil);
                        break;
        }                
        return (true);
}


// Vacuuous hit proc
Boolean HitMeHitMeHitMe (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
{
	return (true);
}



OSErr DowngradeDlogInit (DialogPtr theDialog, long refcon)

{
	ControlHandle			theControl;
	ListHandle				theList;
	OSErr							theError;
	Size							actualSize;
	
	theError = GetDialogItemAsControl (theDialog, NAG_DOWNGRADE_FEATURE_LIST, &theControl);
	if (!theError)
		theError = GetControlData (theControl, kControlEntireControl, kControlListBoxListHandleTag, sizeof (theList), (Ptr) &theList, &actualSize);
	if (!theError)
		if ((*theList)->userHandle = BuildFeatureList (false)) {
			LSetDrawingMode (false, theList);
			LAddRow (GetHandleSize ((*theList)->userHandle) / sizeof (FeatureCellRec), 0, theList);
			LSetDrawingMode (true, theList);
		}
		
	return (theError);
}


FeatureCellHandle BuildFeatureList (Boolean ignoreUsage)

{
	FeatureRecPtr			featurePtr,
										subFeaturePtr;
	FeatureCellHandle	featureCells;
	FeatureCellPtr		featureCellsPtr,
										primaryCellPtr;
	OSErr							theError;
	short							numFeatures,
										subCount,
										featureCount;
	FeatureID					id;
	
	featureCells = nil;
	
	// Figure out how many feature we actually need to display in the list
	numFeatures	 = GetHandleSize (gFeatureList) / sizeof (FeatureRec);
	featureCount = 0;
	featurePtr	 = LDRef (gFeatureList);
	for (id = 0; id < numFeatures; ++id) {
		if (IsFreeMode ()) {
			if (featurePtr->resID != featNoResource && featurePtr->resID != featSimultaneousDirService)
				++featureCount;
		}
		else
			if (featurePtr->resID != featNoResource && HasFeature (id))
				++featureCount;
		++featurePtr;
	}
			
	if (featureCount) {
		// Allocate space for the list data and store it in the useHandle (it will get released by the LDEF)
		featureCells = NuHandleClear (featureCount * sizeof (FeatureCellRec) * 2);
		theError = MemError ();
		if (!theError) {
			// Build the list data
			featureCount		= 0;
			featurePtr			= *gFeatureList;
			featureCellsPtr	= LDRef (featureCells);
			for (id = 0; !theError && id < numFeatures; ++id) {
				if ((IsFreeMode () && featurePtr->resID != featNoResource && featurePtr->resID != featSimultaneousDirService) ||
						(!IsFreeMode () && featurePtr->resID != featNoResource && HasFeature (id))) {
					// If the feature is NOT a sub feature, we'll add and process it
					if (featurePtr->sub == '????') {
						primaryCellPtr = featureCellsPtr;
						theError = SetFeatureCell (&featureCellsPtr, featurePtr, ignoreUsage);
						++featureCellsPtr;
						++featureCount;
						// Now, if this feature has sub features, look for them!
						if (featurePtr->hasSubFeatures) {
							subFeaturePtr = *gFeatureList;
							subCount			= numFeatures;
							while (!theError && subCount--) {
								if (subFeaturePtr->primary == featurePtr->primary && subFeaturePtr->sub != '????') {
									if (featureCellsPtr->used = ((subFeaturePtr->flags & featurePresent) && subFeaturePtr->lastUsed && !ignoreUsage))
										primaryCellPtr->used = true;
									theError = SetFeatureCell (&featureCellsPtr, subFeaturePtr, ignoreUsage);
									++featureCellsPtr;
									++featureCount;
								}
								++subFeaturePtr;
							}
						}
					}
				}
				++featurePtr;
			}
			UL (featureCells);
			if (!theError)
				SetHandleBig (featureCells, featureCount * sizeof (FeatureCellRec) * 2);
		}
	}
	UL (gFeatureList);
	if (theError && featureCells)
		ZapHandle (featureCells);
	return (featureCells);
}


//
//	SetFeatureCell
//
//		Set a particular feature cell to reflect a given feature
//

OSErr SetFeatureCell (FeatureCellPtr *featureCellsPtr, FeatureRecPtr featurePtr, Boolean ignoreUsage)

{
	FeatureResHandle	resFeature;
	char							*nameAndDesc;
	
	if (resFeature = GetResource (featureResourceType, (featurePtr)->resID)) {
		nameAndDesc = (char *) &(*resFeature)->nameAndDesc;
		// First, the name...
		BlockMoveData (&nameAndDesc[0], (*featureCellsPtr)->description, nameAndDesc[0] + 1);
		(*featureCellsPtr)->type = featurePtr->primary;
		(*featureCellsPtr)->isName = true;
		(*featureCellsPtr)->isSubFeature = (featurePtr->sub != '????');
		(*featureCellsPtr)->used = ((featurePtr->flags & featurePresent) && featurePtr->lastUsed && !ignoreUsage);
		
		// Next, the description...
		++(*featureCellsPtr);
		(*featureCellsPtr)->isSubFeature = (featurePtr->sub != '????');
		BlockMoveData (&nameAndDesc[nameAndDesc[0] + 1], (*featureCellsPtr)->description, nameAndDesc[nameAndDesc[0] + 1] + 1);
		ReleaseResource (resFeature);
	}
	return (ResError ());
}

Boolean DowngradeDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)

{
	switch (itemHit) {
		case NAG_DOWNGRADE_YES_I_MEAN_IT_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
			TransitionState (ValidRegCode (regFreeUser, nil, nil) ? regFreeUser : freeUser);
			break;
		case NAG_DOWNGRADE_CANCEL_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
			break;
	}
	return (true);
}

OSErr FeaturesDlogInit (DialogPtr theDialog, long refcon)
{
	return PutFeaturesInto(theDialog,NAG_FEATURES_FEATURE_LIST);
}

OSErr PutFeaturesInto (DialogPtr theDialog, short theItem)
{
	ControlHandle			theControl;
	ListHandle				theList;
	OSErr							theError;
	Size							actualSize;
	
	theError = GetDialogItemAsControl (theDialog, theItem, &theControl);
	if (!theError)
		theError = GetControlData (theControl, kControlEntireControl, kControlListBoxListHandleTag, sizeof (theList), (Ptr) &theList, &actualSize);
	if (!theError)
		if ((*theList)->userHandle = BuildFeatureList (true)) {
			LSetDrawingMode (false, theList);
			LAddRow (GetHandleSize ((*theList)->userHandle) / sizeof (FeatureCellRec), 0, theList);
			LSetDrawingMode (true, theList);
		}
		
	return (theError);
}


Boolean FeaturesDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)

{
	switch (itemHit) {
		case NAG_FEATURES_YES_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
			TransitionState (adwareUser);
			break;
		case NAG_FEATURES_CANCEL_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
			break;
	}
	return (true);
}


OSErr NotGettingAdsDlogInit (DialogPtr dlog, AdFailurePtr adFailurePtr)

{
	SetDIText (dlog, NAG_NOT_GETTING_ADS_ERROR_TEXT, adFailurePtr->text);
	return (noErr);
}


Boolean NotGettingAdsDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)

{
	switch (itemHit) {
		case NAG_NOT_GETTING_ADS_MORE_INFO_BTN:
			CloseMyWindow (GetDialogWindow(theDialog));
			OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, helpQuery, topicAdFailure);
			break;
	}
	return (true);
}


Boolean NoAdsRevertToFreeDlogHit (EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
{
	switch (itemHit) {
		case NAG_NO_ADS_AT_ALL_OK_BTN:
			TransitionState (deadbeatUser);
			return (true);
			break;
		case NAG_NO_ADS_AT_ALL_MORE_INFO_BTN:
			OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, helpQuery, topicAdFailure);
			return (false);
			break;
	}
	return (true);
}

/*
void DebugDialogItems (DialogPtr theDialog)

{
	ControlHandle	theControl;
	Str255				string;
	Rect					itemRect;
	short					count,
								itemNo,
								itemType;
	ComposeString (string,"\p[%d, %d, %d, %d]", theDialog->portRect.left, theDialog->portRect.top, theDialog->portRect.right, theDialog->portRect.bottom);
	DebugStr (string);
	count = CountDITL (theDialog);
	for (itemNo = 1; itemNo <= count; ++itemNo) {
		GetDialogItemAsControl (theDialog, itemNo, &theControl);
//		GetDialogItem (theDialog, itemNo, &itemType, (ControlHandle *) &theControl, &itemRect);
		ComposeString (string,"\p[%d, %d, %d, %d]", (*theControl)->contrlRect.left, (*theControl)->contrlRect.top, (*theControl)->contrlRect.right, (*theControl)->contrlRect.bottom);
		DebugStr (string);
	}
}
*/

//
//	SubstituteLongStaticTextItems
//

OSErr SubstituteLongStaticTextItems (DialogPtr theDialog)

{
	ControlHandle	theControl;
	Str255				text;
	Handle				textHandle;
	OSErr					theError;
	Rect					itemRect;
	long					resID;
	short					count,
								itemNo,
								itemType;
	
	theError = noErr;
	
	count = CountDITL (theDialog);
	for (itemNo = 1; !theError && itemNo <= count; ++itemNo) {
		GetDialogItem (theDialog, itemNo, &itemType, (ControlHandle *) &theControl, &itemRect);
		if (itemType & kStaticTextDialogItem) {
			GetDialogItemText (theControl, text);
			if (text[0] && text[1] == kTEXTResIDFollowsChar) {
				text[1] = text[0] - 1;
				StringToNum (&text[1], &resID);
				if (textHandle = GetResource ('TEXT', resID)) {
					theError = GetDialogItemAsControl (theDialog, itemNo, &theControl);
					if (!theError)
						theError = SetControlData (theControl, kControlEntireControl, kControlStaticTextTextTag, GetHandleSize (textHandle), LDRef (textHandle));
					UL (textHandle);
					ReleaseResource (textHandle);
				}
			}
		}
	}
	return (theError);
}

/************************************************************************
 * GrowGroupItems - grow the group boxes in a window to contain their subcontrols
 ************************************************************************/
short GrowGroupItems(WindowPtr win,ControlHandle parentCntl)
{
	ControlHandle cntl;
	short maxV = 0, maxSubV;
	short subI;
	Rect	rParent;

	// Start off with the root control
	if (!parentCntl)
	if (GetRootControl(win,&parentCntl) || !parentCntl)
		return 0;	// no embedding
	
	// First, figure out the size of the subcontrols
	for (CountSubControls(parentCntl,&subI);subI;subI--)
		if (!GetIndexedSubControl(parentCntl,subI,&cntl) && IsControlVisible(cntl))
		{
			maxSubV = GrowGroupItems(win,cntl);
			maxV = MAX(maxV,maxSubV);
		}
	
	// Do we need to change anything?
	GetControlBounds(parentCntl,&rParent);
	if (maxV+MAX_APPEAR_RIM > rParent.bottom)
	if (IsGroupControl(parentCntl))
	{
		SizeControl(parentCntl,ControlWi(parentCntl),maxV+MAX_APPEAR_RIM-rParent.top);
	}
	
	// Return final size
	GetControlBounds(parentCntl,&rParent);
	maxV = rParent.bottom;	

	return(maxV);
}

/************************************************************************
 * IsGroupControl - is a control a group control?
 ************************************************************************/
Boolean IsGroupControl(ControlHandle cntl)
{
	return GetControlReference(cntl)=='gpbx';
}

//
//	GetIndNagID
//
//		Grab an indexed nagID from Eudora itself.
//

OSErr GetIndNagID (short *nagID, short nagListID, short index)

{
	NagListHandle	nagList;
	OSErr					theError;

	theError = resNotFound;
#ifdef I_HATE_THE_BOX
	if (nagList = GetResource (NAG_LIST_TYPE, nagListID)) {
#else
	if (nagList = GetResourceFromFile (NAG_LIST_TYPE, nagListID, AppResFile)) {
#endif
		if (index > 0 && index <= (*nagList)->count) {
			*nagID = (*nagList)->nagID[index - 1];
			theError = noErr;
		}
		ReleaseResource (nagList);
	}
	return (theError);
}

//
//	GetIndNagState
//
//		Grab an indexed 'Nag ' resource from Eudora itself, depending on
//		a particular user state.
//

OSErr GetIndNagState (NagPtr theNag, short *nagID, UserStateType state, short index)

{
	NagHandle	nag;
	OSErr			theError;
	
	// First, get the indexed nag ID for the current state
	theError = GetIndNagID (nagID, (short) state, index);
	if (!theError) {
		theError = resNotFound;
#ifdef I_HATE_THE_BOX
		if (nag = GetResource (NAG_TYPE, *nagID)) {
#else
		if (nag = GetResourceFromFile (NAG_TYPE, *nagID, AppResFile)) {
#endif
			BlockMoveData (*nag, theNag, sizeof (NagRec));
			theError = noErr;
		}
		ReleaseResource (nag);
	}
	return (theError);
}



//
//	TransitionState
//

void TransitionState (UserStateType newState)

{
	WindowPtr		winWP;
	MyWindowPtr	win;
	UserStateType	oldState;
	short oldJunkCount = ETLCountTranslators(EMSF_JUNK_MAIL);
	
	if (ValidUser (newState)) {
		oldState = (*nagState)->state;
		(*nagState)->state = newState;
		if (!SettingsHandle (NAG_STATE_TYPE, nil, NAG_STATE_ID, nagState)) {
			MyUpdateResFile	(SettingsRefN);
			DetachResource ((Handle) nagState);
		}
		
		CheckAdQT();
		
		// Anything special we need to do when transitioning to a new state?
		if (RepayUser (newState))
			gTimeAtWhichRepayUsersBecomeSponsored = TickCount () + kDelayBeforeSwitchingRepayUsersToSponsored;
			
		if ((FreeUser (oldState) && !FreeUser (newState)) ||
				(!FreeUser (oldState) && FreeUser (newState))) {
			// We need to defer making the state switch until we have torn down the
			// current environment (in particular, closing windows have to know we
			// are closing in Light as opposed to Sponsored mode).  The state switch
			// itself will occur in OpenNewSettings
			(*nagState)->state = oldState;
			RelaunchEudora (newState);
			
			// Notify any plugins that care (Peanut) of our state change.
			ETLEudoraModeNotification(EMS_ModeChanged, GetCurrentPayMode ());
		}
		else {
			WindowPtr	nextWP;
			// 'OpenAdWindow' actually _does_ need to be called here regardless of
			// mode so that it can perform various ad switching stuff itself.
			// if (AdwareUser (newState))
			OpenAdWindow ();
			winWP = GetWindowList ();
			while (winWP) {
				nextWP = GetNextWindow (winWP);				// Just in case we close 'winWP' (like we do w/the ad window)
				win = GetWindowMyWindowPtr (winWP);
				if (IsKnownWindowMyWindow (winWP) && win->transition)
					(*win->transition) (win, oldState, newState);
				winWP = nextWP;
			}
			// Notify any plugins that care (Peanut, SpamWatch suite) of our state change.
			ETLEudoraModeNotification(EMS_ModeChanged, GetCurrentPayMode ());
		}
		
		// plug-ins may have become active
		if (JunkPrefNeedIntro()) JunkIntro();
		
		// on the other hand, they may not...
		if (oldJunkCount>ETLCountTranslators(EMSF_JUNK_MAIL)) JunkDownDialog();			
	}
	gCanPayMode = UserHasValidPaidModeRegcode();
}

/************************************************************************
 * UserHasValidPaidModeRegcode - does the user have a valid paid-mode code?
 ************************************************************************/
Boolean UserHasValidPaidModeRegcode(void)
{
	int policy, month;

	return ValidRegCode(paidUser,&policy,&month) && PolicyCheck(policy,month);
}

//
//	DaysSinceNagBase
//
//		Returns the number of days since the nag base for the specified nag dialog.
//		If the nag base is zero (indicating that we've never been nagged), 
//

uLong DaysSinceNagBase (short dialogID)

{
	NagUsageHandle	nagUsage;
	uLong						daysSince;
									
	daysSince = 0;
	nagUsage = GetResourceFromFile (NAG_USAGE_TYPE, dialogID, SettingsRefN);
	if (nagUsage)
		daysSince = (SecondsToWholeDay (LocalDateTime ()) - SecondsToWholeDay ((*nagUsage)->nagBase)) / kSecondsPerDay;
	return (daysSince);
}


uLong DurationOfSchedule (UserStateType state, short whichSchedule)

{
	NagRec	nag;
	uLong		duration;
	short		lastScheduledDay,
					nagID;

	duration = 0;
	if (!GetIndNagState (&nag, &nagID, state, whichSchedule))
		if ((lastScheduledDay = MostSignificantSetBit (nag.schedule)) >= 0)
			duration = lastScheduledDay + 1;
	return (duration);
}


//
//	SecondsToWholeDay
//
//		Takes a time expressed in seconds since 1/1/04 and returns the
//		time expressed as midnight on that day since 1/1/04 (if that
//		makes any sense whatsoever...).  The purpose of this routine is
//		normalize internal times to more easily perform date calculation
//		in absolute day increments without rounding problems.  For example,
//		the number of days between 11:59 PM on 3/3/99, and 12:00 AM on
//		3/4/99 would be 1.  This is much easier to accomplish across day,
//		month and year boundaries if we're always dealing with days timed
//		at midnight.
//

uLong SecondsToWholeDay (uLong secs)

{
	DateTimeRec	dateTime;

	SecondsToDate (secs, &dateTime);
	dateTime.hour		= 0;
	dateTime.minute	= 0;
	dateTime.second	= 0;
	DateToSeconds (&dateTime, &secs);
	return (secs);
}


//
//	MostSignificantSetBit
//
//		Find the most significant set bit of an unsigned 32 bit value
//		using an inefficient algorithm but oh well you've been warned
//		and you're not going to call this 10 million times in a tight
//		loop everytime through the main event loop anyway, right?
//
//		Returns -1 if no bits are set.
//

short MostSignificantSetBit (UInt32 value)

{
	short	bit;
	
	bit = 31;
	while (value)
		if (value & 0x80000000)
			return (bit);
		else {
			--bit;
			value <<= 1;
		}
	return (-1);
}




void RelaunchEudora (UserStateType newState)

{
	OpenNewSettings (&SettingsFileSpec, true, newState);
}


//
//	UpdateCheck
//
//		Contact eudora.com and check for application updates.
//		If there are updates write the results into a file
//		Open a window displaying this information.
//
//		silently - Indicates that this check was not initiated by the user
//

OSErr UpdateCheck (Boolean silently, Boolean archives)

{
	FSSpec	updateSpec;
	Handle	url;
	OSErr 	theError;
	long		reference;
	
	theError = noErr;
		
	// First, we need a file -- but make sure this is a temporary file because the server might be
	// handing us an empty update to signify "no change"
	NewTempSpec (Root.vRef, Root.dirId, nil, &updateSpec);

// (old way...)
//	theError = FSMakeFSSpec (Root.vRef, Root.dirId, GetRString (filename, UPDATE_FILE), &updateSpec);
//	if (theError == fnfErr)
//		theError = FSpCreate (&updateSpec, CREATOR, 'TEXT', smSystemScript);
	
	// Send a GET to the server to request updates
	if (url = GenerateAdwareURL (GetNagState (), UPDATE_SITE, archives ? actionArchived : actionUpdate, updateQuery, nil)) {
		theError = DownloadURL (LDRef (url), &updateSpec, silently, FinishedUpdateCheck, &reference, nil);			
		ZapHandle (url);
	}
	return (theError);
}


//
//	FinishedUpdateCheck
//
//		Completion routine for DownloadURL.  If 'silently' is true, the update check has been
//		performed without a user action.  If no error's were found we need to check to see if
//		the checksum on the file has changed.  If so, display the window.  If not, do
//		nothing.
//

void FinishedUpdateCheck (long silently, OSErr theError, DownloadInfo *info)

{
	MyWindowPtr 		win;
	NagUsageHandle	nagUsage;
	FSSpec					updateSpec;
	Str255					filename;
	uLong						hash;
	long						fileSize;
	short						oldResFile;
	Boolean					differentChecksum;

	differentChecksum = false;
	if (!silently)
		TellPayWindowTheUpdateCheckIsDone (theError);
	
	// Now that the update is complete, check to see if the server gave us anything.
	// If the update file is empty, no updates are available and we should not
	// automatically display the update page
	fileSize = theError ? 0 : FSpDFSize (&info->spec);

	// Make the real update file spec
	if (!theError) {
		theError = FSMakeFSSpec (Root.vRef, Root.dirId, GetRString (filename, UPDATE_FILE), &updateSpec);
		if (theError == fnfErr)
			theError = FSpCreate (&updateSpec, CREATOR, 'TEXT', smSystemScript);
	}
	
	// Exchange it with the new one -- but only if this file is not empty!
	if (!theError && fileSize) {
		theError = FSpExchangeFiles (&updateSpec, &info->spec);
		if (!theError)
			FSpDelete (&info->spec);
	}
	
	// Compare the checksum to what we've snuck in the update check's nag aux field but only if this is
	// NOT the first time we've performed an update check.  If the checksum has changed (or if this is
	// not a silent check) we'll display the update information to the user.  We also have to save the
	// checksum back to the settings file in the update nag's aux field.
	if (!theError && fileSize) {
		hash = HashFile (&updateSpec);
		oldResFile = CurResFile ();
		if (nagUsage = GetResourceFromFile (NAG_USAGE_TYPE, UPDATE_CHECK, SettingsRefN)) {
			differentChecksum = (*nagUsage)->aux && ((*nagUsage)->aux != hash);
			if (!(*nagUsage)->aux || differentChecksum) {
				(*nagUsage)->aux = hash;
				ChangedResource ((Handle) nagUsage);
				if (!ResError ()) 
					MyUpdateResFile	(SettingsRefN);
			}
			ReleaseResource (nagUsage);
		}
		UseResFile (oldResFile);
	}

	if (!theError && (!silently || differentChecksum))
		win = OpenText (&updateSpec, nil, nil, nil, true, nil, true, true);
}

#define KRHashPrime (2147483629) 

uLong HashFile (FSSpec *spec)

{
	Handle	text;
	Ptr			textPtr;
	Size		len;
	int 		Bit;
	uLong		sum; 
	
	sum = 0;
	if (!Snarf (spec, &text, 0)) {
		len = GetHandleSize (text);
		textPtr = *text;
		while (len--) { 
			for (Bit = 0x80; Bit != 0; Bit >>= 1) { 
				sum += sum; 
				if (sum >= KRHashPrime) sum -= KRHashPrime; 
				if ((*textPtr) & Bit) ++sum; 
				if (sum >= KRHashPrime) sum -= KRHashPrime; 
			}
			++textPtr;
		}
	} 
	return (sum + 1); 
}


/************************************************************************
 * CheckAdQT - if we're in adware mode, make sure QuickTime is installed
 ************************************************************************/
void CheckAdQT(void)
{
	if (IsAdwareMode() && !HaveQuickTime(0x0300))
	{
		//	No QuickTime. Must revert to freeware.
		switch (ComposeStdAlert(kAlertNoteAlert,CANT_AD)) {
			case 1:
				OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, helpQuery, topicNoQuickTime);
				EjectBuckaroo = true;
				break;
			case 2:
				TransitionState ((*nagState)->state==regAdwareUser ? regFreeUser : freeUser);
				break;
		}
	}
}


//
//	AdFailureCheck
//
//		Nag the user if they refuse to get ads (whether they want to or not).
//		We overload the nagBaseTime filed of the nagUsageRec to indicate the
//		last time we found that we checked for ad failure.
//
//		Note that we only look at the state of the 'NoAdsAuxRec' when we enter
//		into a new day.  At this point, the contents of the data structure
//		reflect the ad fetching state for the entirety of the previous day
//		which means we don't have to worry too much about mid day transitions.
//
//			If we did not get ads on the previous day we checked for ads...
//					...bump the deadbeat counter
//					...check to see if we're due for a nag
//
//			If we _did_ get ads...
//					...bump the consecutive day counter
//					...check to see if we should adjust the deadbeat counter and
//						 set the consecutive day counter back to zero
//
//			Regardless, at the beginning of a new day we have to initialize 'adsAreFailing'
//

OSErr AdFailureCheck (NagUsageHandle nagUsage, uLong currentTime, Boolean *nagMe, short *dialogID, PStr errString)

{
	OSErr				theError;

	theError = noErr;
	*nagMe	 = false;
	*errString = 0;
	
	// Are we checking on a new day?  If so, what happened yesterday?
	if (SecondsToWholeDay ((*nagUsage)->nagBase) != SecondsToWholeDay (currentTime)) {
		if (NoAdsRec.adsAreFailing) {
			if (NoAdsRec.deadbeatCounter >= kDaysUntilNoAdsNag) {
				*nagMe = true;
				if (NoAdsRec.deadbeatCounter >= kDeadbeatDays) {
					*dialogID = NAG_FRIGGING_HACKER_DLOG;
					NoAdsRec.deadbeatCounter = kDeadbeatDays-1;	// in case we ever get back to sponsored mode
				}
				else
					*dialogID = NAG_NOT_GETTING_ADS_DLOG;
			}
		}
	}
	
	// Keep track of the last time we checked for ad failures
	(*nagUsage)->nagBase = currentTime;
	
	return (theError);
}

#endif


#ifdef DEBUG
void DebugNagging (void)

{
	NagDialogInitProcPtr	initProc;
	NagDialogHitProcPtr		hitProc;
	ModalFilterUPP				theFilterProc;
	AdFailureRec					adFailure;
	UserStateType					oldState;
	MyWindowPtr						theDialogWin;
	DialogPtr							theDialog;
	Str255								string;
	Str255								sErr;
	short									dItem;
	long									dialogID;
	Boolean								ignoreDefaultItem;
	
	if (theDialogWin = GetNewMyDialog (NAG_DEBUG, nil, nil, InFront)) {
		theDialog = GetMyWindowDialogPtr (theDialogWin);
		oldState = GetNagState ();
		SetDItemState (theDialog, 8,  oldState == newUser);
		SetDItemState (theDialog, 9,  oldState == paidUser);
		SetDItemState (theDialog, 10, oldState == freeUser);
		SetDItemState (theDialog, 11, oldState == adwareUser);
		SetDItemState (theDialog, 12, oldState == regFreeUser);
		SetDItemState (theDialog, 13, oldState == regAdwareUser);
		SetDItemState (theDialog, 14, oldState == deadbeatUser);
		StartMovableModal (theDialog);
		ShowWindow (GetDialogWindow(theDialog));
		do {
			MovableModalDialog (theDialog, DlgFilterUPP, &dItem);
			if (dItem >= 6 && dItem <= 14) {
				SetDItemState (theDialog, 6,  dItem == 6);
				SetDItemState (theDialog, 7,  dItem == 7);
				SetDItemState (theDialog, 8,  dItem == 8);
				SetDItemState (theDialog, 9,  dItem == 9);
				SetDItemState (theDialog, 10, dItem == 10);
				SetDItemState (theDialog, 11, dItem == 11);
				SetDItemState (theDialog, 12, dItem == 12);
				SetDItemState (theDialog, 13, dItem == 13);
				SetDItemState (theDialog, 14, dItem == 14);
			}
		} while (dItem != ok && dItem != cancel);
		EndMovableModal (theDialog);
		if (dItem == ok) {
			GetDIText (theDialog, 4, string);
			dItem = ok;
		}
		DisposeDialog_ (theDialog);
		if (dItem == ok) {
				if (string[0]) {
					StringToNum (string, &dialogID);
					if (dialogID) {
						initProc					= nil;
						hitProc						= nil;
						theFilterProc			= nil;
						ignoreDefaultItem	= false;
						adFailure.text[0] = 0;
						adFailure.error		= noErr;
						switch (dialogID) {
							case NAG_INTRO_DLOG:
								hitProc = IntroDlogHit;
								theFilterProc = DlgFilterUPP;
								break;
							case JUNKDOWN_DLOG:
								hitProc = JunkDownHitProc;
								theFilterProc = DlgFilterUPP;
								break;
							case REPAY_DLOG:
								hitProc = RepayHitProc;
								theFilterProc = DlgFilterUPP;
								break;
							case NAG_PLEASE_REGISTER_DLOG:
								hitProc = AdUserHit;
								break;
							case NAG_DOWNGRADE_DLOG:
								initProc = DowngradeDlogInit;
								hitProc  = DowngradeDlogHit;
								break;
							case NAG_FEATURES_DLOG:
								initProc = FeaturesDlogInit;
								hitProc	 = FeaturesDlogHit;
								break;
							case NAG_NOT_GETTING_ADS_DLOG:
								initProc = NotGettingAdsDlogInit;
								hitProc	 = NotGettingAdsDlogHit;
								adFailure.error		= 503;
								GetRString(sErr, HTTP_ERR_STRN + 3);
								ComposeRString (adFailure.text,HTTP_ERR_FORMAT,sErr,adFailure.error);
								break;
							case NAG_FRIGGING_HACKER_DLOG:
								hitProc						= NoAdsRevertToFreeDlogHit;
								theFilterProc			= DlgFilterUPP;
								ignoreDefaultItem	= true;
								break;
						}
						Nag (dialogID, initProc, hitProc, theFilterProc, ignoreDefaultItem, (long) &adFailure.text);
					}
				}
		}
	}
}
#endif

void AutoSwitchClientMode(void)
{
	if (NewClientModePlusOne)
	{
		switch (NewClientModePlusOne-1)
		{
			case 0: TransitionState(adwareUser); break;
			case 1: 
			case 3: 
				// User is being beaten dead
				if (kAlertStdAlertOtherButton==ComposeStdAlert(Note,PROFILE_FAILURE))
				{
					if (SendUserToProfile())
					if (kAlertStdAlertOKButton==ComposeStdAlert(Note,-PROFILING_NOW))
					{
						// user has indicated he has profiled.  Make a playlist request
						// if he has profiled, it won't switch him to light after all
						// if he has failed to profile, it will switch him to light again
						ForcePlaylistRequest();
						break;
					}
				}
				// Hasta la vista, bebe's kids
#ifndef THEY_STUPIDLY_KILLED_EUDORA_SO_LETS_AT_LEAST_GIVE_THE_FAITHFUL_USERS_A_BREAK
				TransitionState(NewClientModePlusOne-1==3 ? profileDeadbeatUser : deadbeatUser);
#endif
				break;
			case 2: TransitionState(paidUser); break;
		}
		NewClientModePlusOne = 0;
	}
}

extern ModalFilterUPP DlgFilterUPP;
Boolean PlaylistNagHitProc (EventRecord *event, DialogPtr theDialog, short itemHit,long refcon);
OSErr PlaylistNagInitProc(DialogPtr theDialog,long refcon);
/************************************************************************
 * DoPlaylistNag - Ask the user if it's ok to send the audit stats
 ************************************************************************/
Boolean DoPlaylistNag(short nagID)
{
	Boolean retVal = userCanceledErr != Nag(nagID,PlaylistNagInitProc,PlaylistNagHitProc,nil,false, nagID);
	ZapResource(NAG_REQUEST_TYPE,nagID);
	return retVal;
}

#define kPlNagOK 2
#define kPlNagProfile 1
#define kPlNagExplain 3
#define kPlNagParam 5
#define kPlNagFeatures 8
/************************************************************************
 * PlaylistNagHitProc - handle item hits
 ************************************************************************/
Boolean PlaylistNagHitProc(EventRecord *event, DialogPtr theDialog, short itemHit, long refcon)
{
	switch (itemHit) {
		case kPlNagExplain:
			OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionProfileidReqd, profileidReqdQuery , nil);
			CloseMyWindow(GetDialogWindow(theDialog));
			return true;
		case kPlNagProfile:
			SendUserToProfile();
			CloseMyWindow (GetDialogWindow(theDialog));
			return true;
			break;
		default:
			CloseMyWindow (GetDialogWindow(theDialog));
			return true;
			break;
	}
	return (false);
}

/************************************************************************
 * PlaylistNagInitProc - init dialog by filling in the fields
 ************************************************************************/
OSErr PlaylistNagInitProc(DialogPtr theDialog,long refcon)
{
	Handle text = GetResource(NAG_REQUEST_TYPE,refcon);
	Str255 s;
	
	if (text)
	{
		MakePStr(s,*text,GetHandleSize(text));
		SetDIText(theDialog,kPlNagParam,s);
	}
	
	// the feature list
	if (refcon==PLNAG_LEVEL1_DLOG)
		PutFeaturesInto(theDialog,kPlNagFeatures);
	
	return noErr;
}
