/* 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. */

//depot/projects/eudora/mac/Current/adwin.c#127 - edit change 17326 (text)
#ifdef AD_WINDOW
#include "adwin.h"
#include "buildversion.h"
/* Window for ads */

#define FILE_NUM 122
#pragma segment ADWIN

#ifdef DEBUG
#define LOGADS
#define kAdScheduleAccelerator 60	//	show ads 60 times faster
#endif
//#define LOGINFO	//	Sends ads server date/time and user id

#ifdef LOGADS
#define LogAd(s) { Str255 sLog; DoLogAds(s); }
#else
#define LogAd(s)
#endif

#define kMaxDownloads	4	//	Don't request more than this many ads at one time
#define kPlayListVersion 22	//	Playlist version we support
#define kReqInverval 1	//	Check for a new playlist after this many days
#define kFaceTimeQuota	60 // default facetime quota minutes
#define kRerunInterval 7	// default return interval, days
#define kPurgeAdDays 90	//	Purge cached ads after this many days
#define kCheckCoverAd 5	//	Check every 5 seconds to see if ad has been covered
#define kCoveredAdDelay 60	//	After we've discovered that the ad window is covered, wait before alerting user
#define kMaxObscuredPixels 500	//	Nag user if this many pixels are obscured in ad window
#define kSaveStateSecs 15*60	//	Save playlist state every 15 minutes
#define kMaxReqInterval 14*24	//	Don't wait more than 14 days before downloading a new playlist
#define kMinRetryInterval (4*3600)	// Wait at least 4 hrs between playlist requests
#define kAdWidth 144
#define kAdHeight 144
#define kHistLength	30	//	For how many do we report info on ads?
#define kReqInterval 24
#define kSchedType kLinear
#define kPlayListServerId -1	//	Pseudo server id indicating playlist, not ad
#define kAdDragBarHeight 8
#define kNewAdTypingTicks 300	//	Don't display a new ad unless the user hasn't typed for this many ticks
#define kAdDownloadAttemptTicks 7200	// Don't attempt to download ads if we've tried to recently
#define kCheckSumSize 16
#define kQuotaSlop	(60)
typedef long StrOffset;	//	For accessing strings at end of playlist data

typedef enum { vaCheckNormalAdOnly=1<<0, vaCheckDayMax=1<<1 ,vaCheckShowForMax=1<<2, vaCheckRerun=1<<3 } ValidAdFlags;

//	PlayList data. Saved in one resource for each playlist
#define kPlayListVers 4
#define kPlayListResType	'Eupl'
enum SchedType { kLinear, kRandom };
typedef enum	{ kIsButton=1,kIsSponsor,kIsRunout} AdType;
enum MixType	{ kMix,kBlock};

//	Increment kPlayListVers if AdInfo or PlayList is changed
typedef struct
{
	AdType	type;
	long	showFor;	//	How many seconds to display?
	long	dayMax;		//	Maximum times to display per day
	long	showForMax;	//	Maximum seconds to show total
	StrOffset	srcURL;	//	URL to ad (c-string)
	long	blackBefore;	//	Seconds of black before ad
	long	blackAfter;	//	Seconds of black before ad
	uLong	startDate;	//	Don't start before this time
	uLong	endDate;	//	Don't display after this time
	AdId	adId;
	StrOffset	title;	//	Title (used in link window)
	StrOffset	clickURL;	//	URL to where we go when we click
	unsigned char checksum[kCheckSumSize];
} AdInfo, *AdInfoPtr;

typedef struct PlayListStruct PlayList, *PlayListPtr, **PlayListHandle;
struct PlayListStruct
{
	long	version;
	PlayListHandle	next;
	long	strOffset;	//	Offset to beginning of strings
	StrOffset	playListIDString;
	StrOffset	clickBase;
	long	playListId;	//	hash of playlist id string
	enum MixType	mixType;
	enum SchedType	blockScheduleType;
	long	totalShowMax;	//	Sum of showMax values for all ads
	short	numAds;
	AdInfo	ads[];
};

//	Play state data - what is the state of ad display, saved in a resource
//	Increment kStateVersion if AdState or PlayState is changed
#define kStateVersion 12
#define kStateResType	'Euas'
#define kStateResId	1000
#define kPastryResId 1001

//	State info for ads. Use for playlist state also.
typedef struct
{
	AdId	adId;	//	Ad Id, or playlist ID if adId.server == -1
	long	shownToday;	//	How many times displayed today
	long	shownTotal;	//	How many total seconds displayed for this ad
	uLong	lastDisplayTime;	//	When did we last display the ad? Used for aging.
	Boolean	unused;		//	was: flushed
	Boolean deleted;	//	Toolbar button deleted?
} AdState, *AdStatePtr;

typedef struct
{
	short	version;	//	version # for this data (kStateVersion)
	long	showCur;	//	How long will current ad be shown
	short	numAds;

	//	Client info
	long	adWidth;
	long	adHeight;
	long	reqInterval;
	long	faceTimeQuota;
	enum SchedType schedType;
	short	histLength;
	
	long	curPlayList;
	AdId	curAd;
	
	DateTimeRec	today;
	DateTimeRec	getNextPlaylist;
	short	unused;
	short	adsNotFetched;	//	How many ads have we been unable to fetch into cache?
	short	errCode;	//	Are we unable to get ads?
	Boolean	outOfAds;
	Boolean	dirty;
	long	faceTimeToday;	//	Today's total facetime
	long	faceTimeWeek[7];	//	Facetime for past 7 days
	AdState	stateAds[];
} PlayState11, *PlayState11Ptr, **PlayState11Handle;

typedef struct
{
	short	version;	//	version # for this data (kStateVersion)
	long	showCur;	//	How long will current ad be shown
	short	numAds;

	//	Client info
	long	adWidth;
	long	adHeight;
	long	reqInterval;
	long	faceTimeQuota;
	enum SchedType schedType;
	short	histLength;
	
	long	curPlayList;
	AdId	curAd;
	
	DateTimeRec	today;
	DateTimeRec	getNextPlaylist;
	short	unused;
	short	adsNotFetched;	//	How many ads have we been unable to fetch into cache?
	short	errCode;	//	Are we unable to get ads?
	Boolean	outOfAds;
	Boolean	dirty;
	long	faceTimeToday;	//	Today's total facetime
	long	faceTimeWeek[7];	//	Facetime for past 7 days
	long	adFaceTimeToday;	// Today's facetime for ads
	long	rerunInterval;
	long  spare[19];				// Leave some room, fer goodness' sake!
	AdState	stateAds[];
} PlayState, *PlayStatePtr, **PlayStateHandle;

//	List of ads yet to be downloaded
typedef struct DownloadList **DownloadListHandle;
typedef struct DownloadList
{
	DownloadListHandle	next;
	Handle	url;
	AdId	adId;
	Boolean	display;
	AdType	adType;
} DownloadList;

#define REMOVE_AND_DESTROY(hPlayList) \
	do { \
		LL_Remove(gPlayListQueue,hPlayList,(PlayListHandle)); \
		RemoveResource((Handle)hPlayList); \
		if (gPlayList==hPlayList) gPlayList = gPlayListQueue; \
		ZapHandle(hPlayList); \
		UpdateResFile(SettingsRefN); \
	} while (0)

/************************************************************************
 * globals
 ************************************************************************/
long	Secret3 = 0x65B9DE33;
char PlayListURL[] = "\phttp://adserver.eudora.com/adjoin/playlists";
long	Secret1 = 0x6867B92E;
StringPtr PlayListKeywords[] =
{
	//	Client update response parameters
	"\pclientInfo",
	"\preqInterval","\phistLength","\ppastry","\pflush","\pscheduleAlgorithm",
	"\pwidth","\pheight","\pfaceTimeQuota","\prerunInterval",
	"\pfault","\pfaultCode","\pfaultString",
	// Nag
	"\pnag","\plevel",
	//	Reset
	"\preset",
	//	Client update request parameters
	"\pclientUpdate",
	"\puserAgent","\plocale","\pclientMode","\pfaceTime","\pfaceTimeLeft","\pdistributorID","\pscreen","\pprofile",
	"\pfaceTimeUsedToday","\pplayListVersion",
#ifdef LOGINFO
	"\puserID","\pdateAndTime",
#endif
	//	Playlist parameters
	"\pplaylist",
	"\pplaylistID","\pmixAlgorighm","\pblockScheduleAlgorithm","\pclickBase",
	//	Ad entry parameters
	"\pentry",
	"\pdayMax","\pblackBefore","\pblackAfter",
	"\pshowFor","\pstartDT","\pendDT",
	"\pshowForMax","\pstartTime","\pendTime",
	"\psrc","\padID",
	"\ptitle","\purl",
	//	Attributes
	"\plinear","\prandom",						// scheduleAlgorithm
	"\pmix","\pblock",							// mixAlgorithm
	"\pisButton","\pisSponsor","\pisRunout",	// entry type
	"\pentity","\pid",							// for flush
	"\pchecksum",								// checksum for entry.src
	""
};

enum {
	kClientInfoTkn,
		kReqIntervalTkn,kHistLengthTkn,kPastryTkn,kFlushTkn,kScheduleTkn,
		kWidthTkn,kHeightTkn,kFaceTimeQuotaTkn,kRerunIntervalTkn,
		kFaultTkn,kFaultCodeTkn,kFaultStringTkn,
	kNagTkn,kLevelTkn,
	kResetTkn,	
	kClientUpdateTkn,
		kUserAgentTkn,kLocaleTkn,kClientModeTkn,kFaceTimeTkn,kFaceTimeLeftTkn,kDistIDTkn,kScreenTkn,kUserProfileTkn,
		kFaceTimeUsedTodayTkn,kPlayListVersionTkn,
#ifdef LOGINFO
		kUserId,kDateAndTime,
#endif
	kPlayListTkn,
		kPlayListIdTkn,kMixAlgorithmTkn,kBlockScheduleAlgorithmTkn,kClickBaseTkn,
	kEntryTkn,
		kDayMaxTkn,kBlackBeforeTkn,kBlackAfterTkn,
		kShowForTkn,kStartDateTkn,kEndDateTkn,
		kShowForMax,kStartTime,kEndTime,
		kSrcTkn,kAdIdTkn,
		kTitleTkn,kClickTkn,
	//	Attributes
		kLinearTkn,kRandomTkn,
		kMixTkn,kBlockTkn,
		kIsButtonTkn,kIsSponsorTkn,kIsRunoutTkn,
		kEntityTkn,kIdTkn,
		kCheckSum,
};

//	Global variables
MyWindowPtr	AdWin;
PETEHandle	ADpte;
PlayListHandle	gPlayList;
PlayStateHandle	gPlayState;
PlayListHandle	gPlayListQueue;
uLong	gNextAdSecs;
long	Secret2 = 0xA30580D2;
short	gDisplayMinutes = 1;
long	gBlackTime,gNextBlackTime;
FMBHandle	gFaceMeasure;
FMBHandle gGlobalFaceMeasure;
Boolean	gAdWinCanClose,gPLDownloadInProgress;
OSErr	gDownloadErrCode;
AdId	gWasCurAd;
Boolean	gDoNextAd;
Boolean	gPlayListDownloadFail;
DownloadListHandle	gAdDownloadList;
short	gDownloadCount;
StringHandle	gDownloadErrString;
short gAdsMenuAdsStart;
TBAdHandle	gTBAds;
uLong gPlayListFetchGMT;
PicHandle	gSponsorAdPic;	//	"co-branding spot"
AdId		gSponsorAdId;
AdId		gInsertAdId;
Boolean		gInsertAd;
short		gAdFetchCount;
#ifdef DEBUG
enum { dbNewPlaylist=1,dbReloadPlaylist,dbDumpPlaylists,dbDiv,dbDefaultPLServer };
char sPlayListFileName[] = "\peudora ads";
#endif

/************************************************************************
 * prototypes
 ************************************************************************/
void AdClick(MyWindowPtr win,EventRecord *event);
void NextAd(long secsDisplayed);
void NewAd(short adIdx);
void InsertAd(short adIdx);
static OSErr BuildPlayList(UHandle text);
static void DisposePlaylists(void);
static void CleanupAdCache(Boolean withPrejuidice);
#ifdef LOGADS
static void DoLogAds(StringPtr sLog);
#endif
static Boolean AdClose(MyWindowPtr win);
Boolean AdPosition(Boolean save,MyWindowPtr win);
static void AdTransition (MyWindowPtr win, UserStateType oldState, UserStateType newState);
static AdInfoPtr FindAd(AdId adId,short *idx,PlayListHandle *playList);
static AdInfoPtr FindCurrentAd(void);
static AdInfoPtr FindCurrentAdIdx(short *idx);
static void SavePlayStatus(void);
static void SavePastry(Ptr pPastry,long len);
static void AdCursor(Point mouse);
static void NewDayCheck(Boolean withPrejuidice);
static void PreFetchAds(void);
static OSErr GetAdSpec(FSSpecPtr spec, AdId adId);
static void ParseAdId(StringPtr sToken, AdId *pAdId);
static void AdUpdate(MyWindowPtr win);
static void ShowBlackTimeImage(void);
static Boolean ValidAd(PlayListHandle hPlayList,short adIdx,uLong curTime,ValidAdFlags flags);
static OSErr LoadPlaylists(void);
void RequestPlaylist(void);
void NewPlaylist(FSSpecPtr spec,StringPtr checkSum);
#ifdef DEBUG
static void SetupAdDebugMenu();
void DumpPlaylists(Boolean fullDisplay);
static Boolean DumpClose(MyWindowPtr win);
static void AccuDump(StringPtr s);
static void AccuDumpTime(StringPtr sName, uLong seconds);
#endif
static StringPtr GetPlayListString(PlayListHandle hPlayList,StrOffset offset,StringPtr s);
static Handle GetPlayListURL(PlayListHandle hPlayList,StrOffset offset);
static StringPtr GetPlayListURLString(PlayListHandle hPlayList,StrOffset offset,StringPtr s);
static AdStatePtr FindAdState(AdId adId,short *idx);
static AdStatePtr FindCurrentAdState(void);
static OSErr AddStringToHandle(StrOffset *offset,AccuPtr a,StringPtr s);
static PlayListHandle FindPlayList(long playListId);
static void AddStates(PlayListHandle hPlayList, AccuPtr aState,Boolean init);
static AdStatePtr FindPlayListState(PlayListHandle hPlayList);
static void SetCurrentPlayList(void);
static AdInfoPtr FindAdType(AdType adType,short *idx,PlayListHandle *playList);
static AdInfoPtr FindAdTypeLo(AdType adType,short startIdx,PlayListHandle startPlayList,short *idx,PlayListHandle *playList);
static void Flush(TokenInfo	*pInfo);
static Boolean QueueAdDownload(Handle url, AdId adId, AdType adType);
static void CheckDownloadList(void);
static void FinishedAdDownload(long refCon,OSErr err,DownloadInfo *info);
static void PrunePlayState(Handle hKeepAdList);
static OSErr AdDragHandler(MyWindowPtr win,DragTrackingMessage which,DragReference drag);
Boolean AdCoveredRect(Rect *r);
static void SetupToolbarAds(void);
static void MakeAdIconFromFile(FSSpecPtr spec, Handle iconSuite);
static void MakeAdIcon(PlayListHandle hPlayList,StrOffset srcURL,AdId adId,Handle iconSuite,Boolean downloadOK);
static void SetTBAdIcon(AdId adId);
static void DeleteAd(AdId adId);
long TotalFaceTimeLeft(void);
void RollGlobalFacetime(void);
OSErr PastryIsStale(Handle pastry);
static OSErr SetupAdState(Boolean reinit);
static void ResetAdState(void);
void LogPlayStatus(void);
OSErr SetupForDisplay(Ptr data,long dataLen,unsigned char checksum[kCheckSumSize],Boolean check);
static void ZapSponsorAd(void);
static OSErr CheckAd(AdId adId,Handle hData,FSSpec *spec);
static void InitAdPte(void);
static void ResetAdSchedule(void);
static Boolean IsChecksumFailAgain(AdId adId);
OSErr GetAdFolderSpec(FSSpecPtr spec);
OSErr HandleNag(long level,PStr text);
OSErr HandleUserProfile(PStr text);
OSErr HandleClientMode(PStr text);
OSErr ReadNoAdsRec(NoAdsAuxPtr noAds);
OSErr SaveNoAdsRec(NoAdsAuxPtr noAds);
OSErr TestWriteToAdFolder(void);

/************************************************************************
 * OpenAdWindow - open ad window
 ************************************************************************/
void OpenAdWindow(void)
{
	if (IsAdwareMode())
	{
		if (!AdWin)
		{
			//	Make sure we have an ad cache folder
			FSSpec spec;
			
			GetAdFolderSpec(&spec);	//	Make sure we have an ad folder

			if (AdWin=GetNewMyWindowWithClass(AD_WIND,nil,nil,BehindModal,False,False,AD_WIN,
				// Give the ad window a very high float level under OS X so that
				// nothing can obscure it
				// Don't float so high for OS 9. It causes problems with dialogs
				HaveOSX() ? kHelpWindowClass : kToolbarWindowClass))	
			{
				WindowPtr	AdWinWP;
				
				if (AdWinWP = GetMyWindowWindowPtr (AdWin)) {
					SetPort_(GetWindowPort(AdWinWP));
					MySetThemeWindowBackground(AdWin,kThemeActiveModelessDialogBackgroundBrush,False);
		 
					AdWin->position = AdPosition;
					AdWin->isRunt = true;
					AdWin->windowType = kDockable;
					AdWin->click = AdClick;
					AdWin->close = AdClose;
					AdWin->transition = AdTransition;
					AdWin->cursor = AdCursor;
					AdWin->update = AdUpdate;
					AdWin->drag = AdDragHandler;
					
					InitAdPte();

					gFaceMeasure = NewFaceMeasure();
					FaceMeasureBegin(gFaceMeasure);

					gBlackTime = -1;	//	Negative value means display black until an ad shows up
					ShowMyWindow(AdWinWP);
					PositionDockedWindow(AdWinWP);
					
		#ifdef DEBUG
					SetupAdDebugMenu();
		#endif
					ReadNoAdsRec(&NoAdsRec);
					
					//	Get playlists
					if (!LoadPlaylists())
					{
						if (!gPlayListQueue && !TCPWillDial(true))	// Don't connect the net on first launch!
							RequestPlaylist();
						SizeAdWin();
					}
					else
						//	Can't set up playlists
						CloseAdWindow();
					NewDayCheck(false);	//	Check for new day before displaying first ad
				}
			}
		}
		if (!AdWin || !ADpte) DieWithError(INIT_ADWIN_ERR,MemError()?MemError():ResError()?ResError():1);
	}
	else if (IsFreeMode())
	{
		LoadPlaylists();	//	Load playlists so we can check for sponsor ad
	}
	SetupToolbarAds();	//	Make sure toolbar ads are installed (or removed)
	SetupSponsorAd();
}

/************************************************************************
 * CloseAdWindow - close ad window
 ************************************************************************/
void CloseAdWindow(void)
{
	if (AdWin)
	{
		// if an ad was up, record that it's now gone.
		if (gPlayList && (gWasCurAd.server || gWasCurAd.ad))
		{
			AuditAdClose(gWasCurAd.server,gWasCurAd.ad);
			gWasCurAd.server = gWasCurAd.ad = 0;
		}
		gAdWinCanClose = true;
		CloseMyWindow(GetMyWindowWindowPtr(AdWin));
		gAdWinCanClose = false;
		AdWin = nil;
	}
	RollGlobalFacetime();// save faceTimeToday
	
	gNextAdSecs = 0;
	gBlackTime = gNextBlackTime = 0;
	SavePlayStatus();
	DisposePlaylists();
	DisposeFaceMeasure(gFaceMeasure);
	gFaceMeasure = nil;
	if (gPlayState)
	{
		ReleaseResource((Handle)gPlayState);
		gPlayState = nil;
	}
}

/************************************************************************
 * AdShutdown - Eudora is shutting down
 ************************************************************************/
void AdShutdown(void)
{
	SavePlayStatus();
}

/************************************************************************
 * AdPosition - position the ad window
 ************************************************************************/
Boolean AdPosition(Boolean save,MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	Boolean did = PositionPrefsTitle(save,win);
	short spot;

	if (!save && !did && winWP)
	{
		// we have no idea where the ad window should go.  Position
		// it in the lower-right
		GDHandle gd = MyGetMainDevice();
		Rect r;
		Rect rCovered;
		
		SizeWindow(winWP,GetRLong(AD_WINDOW_SIZE_GUESS_X),GetRLong(AD_WINDOW_SIZE_GUESS_Y),false);
		
		for (spot=0;spot<5;spot++)
		{
			r = (*gd)->gdRect;
			if (gd==GetMainDevice()) r.top += GetMBarHeight();
			
			switch (spot)
			{
				case 0:
				case 4:
					// Bottom left
					r.right = r.left + WindowWi(winWP);
					r.top = r.bottom - WindowHi(winWP);
					break;
				case 1:
					// Bottom right, left of trash
					r.left = r.right - WindowWi(winWP) - 48;
					r.right -= 48;
					r.top = r.bottom - WindowHi(winWP);
					break;
				case 2:
					// Bottom right, above trash
					r.left = r.right - WindowWi(winWP);
					r.top = r.bottom - WindowHi(winWP) - 48;
					r.bottom -= 48;
					break;
				case 3:
					// Upper right, left of disk
					r.left = r.right - WindowWi(winWP) - 48;
					r.right -= 48;
					r.bottom = r.top + WindowHi(winWP);
					break;
			}
		
			utl_RestoreWindowPos(winWP,&r,false,1,0,LeftRimWidth(winWP),(void*)FigureZoom,(void*)DefPosition);
			PositionDockedWindow(winWP);	//	Make sure positioned correctly if docked
			
			// Show it now so we can see if it's covered
			ShowHide(winWP,true);
			StashStructure(win);
			if (AdCoveredRect(&rCovered))
			{
				if (rCovered.top > (r.top+r.bottom)/2 && (spot==0 || spot==2))
				{
					// Rat doot; try moving it up
					r.bottom = rCovered.top;
					r.top = r.bottom - WindowHi(winWP);
					utl_RestoreWindowPos(winWP,&r,false,1,0,LeftRimWidth(winWP),(void*)FigureZoom,(void*)DefPosition);
					PositionDockedWindow(winWP);	//	Make sure positioned correctly if docked
				}
			}
			if (!AdCoveredRect(&rCovered)) break;
		}
		if (AdCoveredRect(&rCovered))
			ComposeStdAlert(Note,SORRY_AD_COVERED);
		did = true;
	}
	return did;
}

/************************************************************************
 * AdClose - close ad window?
 ************************************************************************/
static Boolean AdClose(MyWindowPtr win)
{
	//	Normally we won't want anything to close the ad window
	//	It is allowed only when CloseAdWindow is called
	return gAdWinCanClose;
}

/************************************************************************
 * AdTransition - transition the window between user states
 ************************************************************************/
static void AdTransition (MyWindowPtr win, UserStateType oldState, UserStateType newState)

{
	if (!AdwareMode (newState))
		CloseAdWindow ();
	else
		if (!AdwareMode (oldState) && AdwareMode (newState))
			OpenAdWindow ();
}

/************************************************************************
 * AdClick - click in ad window
 ************************************************************************/
void AdClick(MyWindowPtr win,EventRecord *event)
{
	Point	pt;
	
	pt = event->where;
	GlobalToLocal(&pt);
	//	Drag window if no drag bar with click and drag, or inside dragbar
	if ((PrefIsSet(PREF_NO_ADWIN_DRAG_BAR) && MyWaitMouseMoved(event->where,True)) ||
		(!PtInRect(pt,&win->contR)))
	{
		//	In drag bar	
		DragMyWindow(GetMyWindowWindowPtr(win),event->where);
	}	
	else if (!gBlackTime)
		AdUserClick((*gPlayState)->curAd);
}

/************************************************************************
 * AdUserClick - handle a click on an ad
 ************************************************************************/
void AdUserClick(AdId adId)
{
	Str255	s;
	AdInfoPtr	pAd;
	PlayListHandle	hPlayList;
		
	if ((pAd = FindAd(adId,nil,&hPlayList)) && hPlayList)
	{
		OSErr err = noErr;
		StrOffset clickURL = pAd->clickURL;

		if ((*hPlayList)->clickBase)
		{
			//	Need to append ad URL to this click base URL
			Str255	sURL;
			AdURLStringsRec	adStrings;
			Str32			sAdId;
			
			GetPlayListURLString(hPlayList,clickURL,sURL);
			ComposeString(sAdId,"\p%d.%d",adId.server,adId.ad);
			adStrings.adID = sAdId;
			adStrings.destination = sURL;
			GetPlayListURLString(hPlayList,(*hPlayList)->clickBase,s);
			err = OpenAdwareURLStr(GetNagState(),s,actionClickThrough,clickThruQuery,(long)&adStrings);
		}
		else
		{		
			Handle	url = GetPlayListURL(hPlayList,clickURL);
			if (url)
			{
				char *urlCStr = LDRef(url);
				short	urlLen = strlen(urlCStr);
				
				if (fnfErr==(err=OpenLocalURLPtr(urlCStr,urlLen,nil,nil,false)))
					if ((err=ParseProtocolFromURLPtr(urlCStr,urlLen,s))==noErr)
						err = OpenOtherURLPtr(s,urlCStr,urlLen);
				ZapHandle(url);
			}
			AdWasClicked(adId,err);
		}
	}
	AuditAdHit(adId.server,adId.ad);
}

/************************************************************************
 * AdWinIdle - idle time handler for ad window
 ************************************************************************/
void AdWinIdle(void)
{
	static uLong	nextIdle,nextCheckCover,nextReportCovered,nextSaveStatus;
	static uLong lastAdAttempt = 0;
	static Boolean	sponsorAdInited = false;
	WindowPtr	AdWinWP;
	uLong	faceTime, totalTime, seconds;
	DateTimeRec		dt;
	
	if (IsPayMode()) return;	//	Not in adware or free mode
	
	if (gInsertAd)
	{
		short	adIdx;
		if (FindAd(gInsertAdId,&adIdx,&gPlayList))
			InsertAd(adIdx);							
		gInsertAd = false;
	}

	if (nextIdle && TickCount()<nextIdle) return;	//	Once a second is sufficient
	nextIdle = TickCount() + 60;	
	
	if (!gPlayState) SetupAdState(false);

	NewDayCheck(false);	//	Have we gone to a new day?

	if (gActiveConnections>0) // are we connected to the network right now somehow?
	{
		// have we tried downloading ads recently?
		if (TickCount()-lastAdAttempt>kAdDownloadAttemptTicks)
		{
			// go fetch the ads that we need
			AdCheckingMail();
			lastAdAttempt = TickCount();
		}
	}
	
	CheckDownloadList();	//	Any ads to download?

	seconds = LocalDateTime();

	if (nextSaveStatus)
	{
		if (seconds >= nextSaveStatus)
		{
			SavePlayStatus();	
			nextSaveStatus = seconds + kSaveStateSecs;
		}
	}
	else
		nextSaveStatus = seconds + kSaveStateSecs;
	
	if (IsFreeMode() && !sponsorAdInited)
	{
		SetupToolbarAds();	//	We haven't set up the sponsor ad yet
		sponsorAdInited = true;
	}

	//	Has playlist expired?
	GetTime(&dt);
	if (TimeCompare(&dt,&(*gPlayState)->getNextPlaylist)>=0)
	{
		//	Need a new playlist
		DateTimeRec	nextPlaylist;
		
		//	Set next time for download in an hour so we don't
		//	continually request playlists
		SecondsToDate(LocalDateTime()+60L*60L,&nextPlaylist);
		(*gPlayState)->getNextPlaylist = nextPlaylist;
		RequestPlaylist();
	}

	if (!AdWin) return;	//	Ad window is not currently open. We may be in free mode.
	
	if (InBG || FaceMeasureReport(gFaceMeasure,&faceTime,nil,nil,&totalTime)) return;

	//	End of face-to-black time? Don't start ad display unless user is looking.
	if (gBlackTime>0)
	{
		if (totalTime >= gBlackTime)
		{		
			//	Fade-to-black time is over. Can draw again
			LogAd(ComposeString(sLog,"\pShow for: %d",gNextAdSecs));
			gBlackTime = 0;
			FaceMeasureReset(gFaceMeasure);
			InvalContent(AdWin);
		}
	}

	//	Time for a new ad?
	else if ((TickCount()-TypingTicks>kNewAdTypingTicks) && ((gNextAdSecs && faceTime >= gNextAdSecs) || gDoNextAd))
	{
		gDoNextAd = false;
		NextAd(gNextAdSecs);
	}

	if (!AdWin) return;	//	Ad window has been closed
	
	if (AdWinWP = GetMyWindowWindowPtr (AdWin))
		if (!IsWindowVisible (AdWinWP))
		{
			// Someone's trying to pull a fast one. Make sure ad window stays visible.
			ShowHide(AdWinWP,true);
			StashStructure(AdWin);
		}
	
#ifndef I_HATE_THE_BOX
	if (seconds >= nextCheckCover)
	{
		WindowPtr	theWindow;
		Rect	rSect;
		
		//	Search for any windows in front of ad window that overlap
		//	If so, bring ad window in front
		for (theWindow = FrontWindow (); theWindow && theWindow != AdWinWP; theWindow = GetNextWindow (theWindow))
		{
			Rect	rStrucWin,rStrucAdWin;
			if (SectRect(GetStructureRgnBounds(theWindow,&rStrucWin),GetStructureRgnBounds(AdWinWP,&rStrucAdWin),&rSect))
			{
				if (!ModalWindow)
					BringToFront(AdWinWP);
				break;
			}
		}
		
		//	Make sure there aren't any system floating windows in front		
		if (AdCoveredRect(nil))
		{
			//	Ad window is obscured. Start time to ask user to uncover.
			if (!nextReportCovered)
				nextReportCovered = seconds + kCoveredAdDelay;
			else if (seconds >= nextReportCovered)
			{
				extern ModalFilterUPP DlgFilterUPP;
				//	Time to tell the user to uncover the ad window
				nextReportCovered = 0;
				Nag (AD_OBSCURED_DLOG, nil, HitMeHitMeHitMe, DlgFilterUPP, false, 0);
			}
		}
		else
			nextReportCovered = 0;
		nextCheckCover = seconds + kCheckCoverAd;
	}
#endif
	
#ifdef DEBUG
	{
		static short	fastAds;
		
		if (BUG4 != fastAds)
		{
			if (BUG4)
			{
				//	Catch on to change to fast ads quickly
				if (gNextAdSecs) gDoNextAd = true;
				gBlackTime = 0;
			}
			fastAds = BUG4;
		}
	}
#endif

}

/************************************************************************
 * AdCoveredRect - Find the covered part of the ad
 ************************************************************************/
Boolean AdCoveredRect(Rect *r)
{
	CGrafPtr	AdWinPort = GetMyWindowCGrafPtr (AdWin);
	RgnHandle rgn = NewRgn();
	Rect rCovered;
	
	Zero(rCovered);
	
	if (rgn && AdWinPort && !ModalWindow)	// don't check if modal window up!
	{
		Rect	rPortRect,rRgnBounds;
		
		//	The visRgn should cover this entire region
		RectRgn(rgn,GetPortBounds(AdWinPort,&rPortRect));
		//	It's OK if a few pixels around the edges are obscured
		InsetRgn(rgn,5,5);
		//	Subtract out the visRgn. Anything left over is an obscured area
		DiffRgn(rgn,MyGetPortVisibleRegion(AdWinPort),rgn);
		//  Globalize it
		PushGWorld();
		SetPort(AdWinPort);
		GlobalizeRgn(rgn);
		PopGWorld();
		//  Record rectangle
		rCovered = *GetRegionBounds(rgn,&rRgnBounds);
		DisposeRgn(rgn);
	}
	
	if (r) *r = rCovered;
	
	return (RectHi(rCovered) * RectWi(rCovered) > kMaxObscuredPixels);
}


/************************************************************************
 * AdCursor - set the cursor
 ************************************************************************/
static void AdCursor(Point mouse)
{
#ifdef DEBUG
	if (CurrentModifiers()&controlKey)
	{
		SetMyCursor(MENU_CURS);
		return;
	}
#endif
	if (!gBlackTime && PtInRect(mouse,&AdWin->contR))
		SetMyCursor(POINTER_FINGER);
	else
		//	In drag bar	
		SetMyCursor(arrowCursor);
}

/************************************************************************
 * AdUpdate - update ad window
 ************************************************************************/
static void AdUpdate(MyWindowPtr win)
{
	CGrafPtr	AdWinPort = GetMyWindowCGrafPtr (AdWin);
	Rect	r,rPortBounds;
	PixPatHandle	hPixPat;

	if (!PrefIsSet(PREF_NO_ADWIN_DRAG_BAR))
	{
		//	Draw the drag bar
		//	Start with some borders
		r = *GetPortBounds(AdWinPort,&rPortBounds);
		SetForeGrey(0);	//	black
		if (RectHi(r)*2 <= RectWi(r))
		{
			// Put title bar on the side of this short, wide window
			r.right = kAdDragBarHeight-1;
			MoveTo(kAdDragBarHeight-1,0);
			LineTo(kAdDragBarHeight-1,r.bottom);
		}
		else
		{
			// Put title bar on top of normal window, 
			r.bottom = kAdDragBarHeight-1;
			MoveTo(0,kAdDragBarHeight-1);
			LineTo(r.right,kAdDragBarHeight-1);
		}

		//	Now the pattern
		if (hPixPat = GetPixPat(DRAG_BAR_PIXPAT))
		{
			//InsetRect(&r,1,1);
			//r.bottom--;
			FillCRect(&r,hPixPat);
			DisposePixPat(hPixPat);
		}
	}
	
	if (gBlackTime || !gNextAdSecs)
	{
		ShowBlackTimeImage();
		SetEmptyVisRgn(AdWinPort);	//	Don't allow pete handle to be updated
	}
}

/************************************************************************
 * AdDragHandler - we don't accept any drags
 ************************************************************************/
static OSErr AdDragHandler(MyWindowPtr win,DragTrackingMessage which,DragReference drag)
{
	//	We don't wany any window highlighting during a drag to the ad window
	//	Return an error, but not handlerNotFoundErr because PantyTrack will
	//	still do a PeteDrag
	return -1;
}

/************************************************************************
 * SavePlayStatus - save play status resource
 ************************************************************************/
static void SavePlayStatus(void)
{
	if (gPlayState && (*gPlayState)->dirty)
	{
		(*gPlayState)->dirty = false;
		ChangedResource((Handle)gPlayState);
		MyUpdateResFile(SettingsRefN);
#ifdef DEBUG
		LogPlayStatus();
#endif
	}
}

/************************************************************************
 * LogPlayStatus - log gPlayState
 ************************************************************************/
void LogPlayStatus(void)
{
	if (gPlayState)
	{
		ComposeLogS(LOG_PLIST,nil,"\pversion %d showCur %d numAds %d",
			(*gPlayState)->version,
			(*gPlayState)->showCur,
			(*gPlayState)->numAds);

		ComposeLogS(LOG_PLIST,nil,"\padWidth %d adHeight %d reqInterval %d faceTimeQuota %dm",
			(*gPlayState)->adWidth,
			(*gPlayState)->adHeight,
			(*gPlayState)->reqInterval,
			(*gPlayState)->faceTimeQuota/60);

		ComposeLogS(LOG_PLIST,nil,"\pschedType %d histLength %d curPlayList %x curAd %d.%d",
			(*gPlayState)->schedType,
			(*gPlayState)->histLength,
			(*gPlayState)->curPlayList,
			(*gPlayState)->curAd.server,(*gPlayState)->curAd.ad);
			
		ComposeLogS(LOG_PLIST,nil,"\padFetchCount %d adsNotFetched %d errCode %d outOfAds %d",
			gAdFetchCount,
			(*gPlayState)->adsNotFetched,
			(*gPlayState)->errCode,
			(*gPlayState)->outOfAds);
			
		ComposeLogS(LOG_PLIST,nil,"\pdirty %d adDay %d totalDay %d  S %d  M %d  T %d  W %d  R %d  F %d  S %d (mins)",
			(*gPlayState)->dirty,
			(*gPlayState)->adFaceTimeToday/60,
			(*gPlayState)->faceTimeToday/60,
			(*gPlayState)->faceTimeWeek[0]/60,
			(*gPlayState)->faceTimeWeek[1]/60,
			(*gPlayState)->faceTimeWeek[2]/60,
			(*gPlayState)->faceTimeWeek[3]/60,
			(*gPlayState)->faceTimeWeek[4]/60,
			(*gPlayState)->faceTimeWeek[5]/60,
			(*gPlayState)->faceTimeWeek[6]/60);
	}
}

/************************************************************************
 * SizeAdWin - resize ad window
 ************************************************************************/
void SizeAdWin(void)
{
	WindowPtr	AdWinWP = GetMyWindowWindowPtr (AdWin);
	if (AdWin && AdWinWP && gPlayState)
	{
		Rect	r;
		short	dragBarHt=0,dragBarWd=0;
		
		if (!PrefIsSet(PREF_NO_ADWIN_DRAG_BAR))
		{
			if ((*gPlayState)->adHeight*2 > (*gPlayState)->adWidth)
				dragBarHt = kAdDragBarHeight;
			else
				// Title bar on left side of window
				dragBarWd = kAdDragBarHeight;
		}		
		SetRect(&r,0,0,(*gPlayState)->adWidth+dragBarWd,(*gPlayState)->adHeight+dragBarHt);
		SizeWindow(AdWinWP,r.right,r.bottom,true);
		SetPrefLong(AD_WINDOW_SIZE_GUESS_X,RectWi(r));
		SetPrefLong(AD_WINDOW_SIZE_GUESS_Y,RectHi(r));
		r.left=dragBarWd;
		r.top=dragBarHt;
		AdWin->contR = r;
		PeteDidResize(ADpte,&r);
		PositionDockedWindow(AdWinWP);	//	Make sure positioned correctly if docked
	}
}


/**********************************************************************
 * FinishedPlayListDownload - we have finished downloading a play list
 **********************************************************************/
static void FinishedPlayListDownload(long refCon,OSErr err,DownloadInfo *info)
{
	FSSpec	spec;

	gPLDownloadInProgress = false;
#ifdef DEBUG
	ComposeLogS(LOG_PLIST,nil,"\pPlayList Fetch Done: %d",err);
#endif
	if (!err)
	{
		//	Use new playlist
		NewPlaylist(&info->spec,info->checksum);
		gPlayListDownloadFail = false;
	}
	else
	{
		//	Download failed.
		DateTimeRec	nextPlaylist;
		
		gPlayListDownloadFail = true;
		//	Let's try again in an hour. Will also try on each mail check
		if (gPlayState)
		{
			SecondsToDate(LocalDateTime()+60L*60L,&nextPlaylist);
			(*gPlayState)->getNextPlaylist = nextPlaylist;
		}
	}

	spec = info->spec;

#ifdef DEBUG
	if (RunType!=Production)
	{
		FSSpec	keepSpec;
		
		//	Let's keep playlist file for debugging
				
		//	Move file from temporary folder to ad folder
		GetAdFolderSpec(&keepSpec);
		FSpCatMove(&spec,&keepSpec);
		SubFolderSpec(AD_FOLDER_NAME,&keepSpec);
		PCopy(keepSpec.name,sPlayListFileName);
		FSpDelete(&keepSpec);
		PCopy(keepSpec.name,spec.name);
		FSpRename(&keepSpec,sPlayListFileName);
	}
	else
#endif
		FSpDelete(&info->spec);
	gDownloadErrCode = err;
	if (gPlayState)
	{
		(*gPlayState)->errCode = err;
	}
}

/**********************************************************************
 * FinishedAdDownload - we have finished downloading an ad
 **********************************************************************/
static void FinishedAdDownload(long refCon,OSErr err,DownloadInfo *info)
{
	FSSpec	spec,tempSpec;
	OSErr	getAdErr;
	DownloadListHandle	hDownload = (DownloadListHandle)refCon;
	Handle	url;
	
	if (!hDownload) return;
	
	if (gPlayState) 
	{	
		gAdFetchCount--;
		getAdErr = GetAdSpec(&tempSpec, (*hDownload)->adId);
		if (!err) err = FindTemporaryFolder(Root.vRef,Root.dirId,&tempSpec.parID,&tempSpec.vRefNum);
		if (!err)
		{
			if (!getAdErr)
			{
				if (FileTypeOf(&tempSpec)=='TEXT')
				{
					//	Is HTML. Download images.
				}
				
				//	Move file from temporary folder to ad folder
				if (!(err = GetAdFolderSpec(&spec)))
					err = FSpCatMove(&tempSpec,&spec);
				
				if (!err)
				{
					switch ((*hDownload)->adType)
					{
						case kIsButton:	SetTBAdIcon((*hDownload)->adId); break;
						case kIsSponsor:	SetupSponsorAd(); break;
						default:
							if (AdWin && (*hDownload)->display)
							{
								//	This ad should be displayed right away
								gInsertAd = true;
								gInsertAdId = (*hDownload)->adId;
							}
							break;
					}
				}
			}
		}
		
		if (err)
		{
			//	Unable to download/install this ad
			(*gPlayState)->adsNotFetched++;
			if (!getAdErr) FSpDelete(&tempSpec);

			if ((*hDownload)->display)
			{
				//	We were supposed to display this ad
				gDoNextAd = true;	//	Set up to display next ad
			}
		}

		(*gPlayState)->errCode = err;
	}
	gDownloadErrCode = err;
	gDownloadCount--;

	url = (*hDownload)->url;
	ZapHandle(url);
	ZapHandle(hDownload);
}


/**********************************************************************
 * NewPlaylist - use a new playlist
 **********************************************************************/
void NewPlaylist(FSSpecPtr spec,StringPtr checkSum)
{
	UHandle	text;
	OSErr	err = noErr;
	unsigned char key[kCheckSumSize];

	if (!Snarf(spec,&text,0))
	{
		//	validate checksum if we have one
		if (checkSum)
		{
			Hex2Bytes(checkSum+1,32,key);
			err = SetupForDisplay(LDRef(text),GetHandleSize(text),key,true);
		}
		if (!err)
			err = BuildPlayList(text);
		ZapHandle(text);
		if (!err)
		{
			SizeAdWin();
			gDoNextAd = true;	//	Set up to display next ad
			PreFetchAds();	//	Load all ads into cache
			SetupToolbarAds();
			SetupSponsorAd();
		}
		if (!(*gPlayState)->faceTimeQuota || (*gPlayState)->faceTimeToday < (*gPlayState)->faceTimeQuota)
			// Go back to showing regular ads again if factTimeQuota hasn't expired
			(*gPlayState)->outOfAds = false;
		if (!gNextAdSecs) gDoNextAd = true;	//	We were out of ads. Start showing them again
	}
}

/**********************************************************************
 * LoadPlaylists - load and install playlists from playlist resources
 **********************************************************************/
static OSErr LoadPlaylists(void)
{
	short		oldResFile,count;
	short		i;
	Accumulator	aState;
	OSErr		err;
	
	if (gPlayListQueue) return noErr;	//	Already got 'em
	
	oldResFile = CurResFile();
	UseResFile(SettingsRefN);
	
	//	Setup ad state data
	if (err = SetupAdState(false)) return err;

	//	Now get playlists
	AccuInitWithHandle(&aState,(Handle)gPlayState);
	count = Count1Resources(kPlayListResType);
	for(i=1;i<=count;i++)
	{
		PlayListHandle	hPlayList;
		AdStatePtr pPlayListState;
			
		if (hPlayList = (PlayListHandle)Get1IndResource(kPlayListResType,i))
		{
#if kPlayListVers == 4
			if ((*hPlayList)->version == 3)
			{
				//	Version 3 may have bad checksums. Let's ignore them.
				short adIdx;
				
				for(adIdx=0;adIdx<(*hPlayList)->numAds;adIdx++)
					Zero((*hPlayList)->ads[adIdx].checksum);
				
				(*hPlayList)->version = kPlayListVers;
			}
#else
#error Remove this entire conditional compile directive  if version is greater than 4
#endif
			if ((*hPlayList)->version == kPlayListVers)
			{
				long shownTotal = 0;
				long totalShowMax = 0;
				long curTime = LocalDateTime();
				short adIdx;

				(*hPlayList)->next = nil;	// old value; destroy
				LL_Queue(gPlayListQueue,hPlayList,(PlayListHandle));
				AddStates(hPlayList,&aState,false);
				pPlayListState = FindPlayListState(hPlayList);
								
				for(adIdx=0;adIdx<(*hPlayList)->numAds;adIdx++)
					if (ValidAd(hPlayList,adIdx,curTime,vaCheckNormalAdOnly+vaCheckShowForMax))
					{
						AdInfo	*pAd = &(*hPlayList)->ads[adIdx];
						AdStatePtr	pAdState = FindAdState(pAd->adId,nil);
						if (pAdState)
						{
							shownTotal += pAdState->shownTotal;
							totalShowMax += pAd->showForMax;
						}
					}
				(*hPlayList)->totalShowMax = totalShowMax;
				pPlayListState->shownTotal = shownTotal;
			}
			else
				//	Old playlist format. Delete it.
				RemoveResource((Handle)hPlayList);
		}
	}	
	AccuTrim(&aState);	//	Don't dispose of this accumulator!
	SetCurrentPlayList();

	UseResFile(oldResFile);
	gDoNextAd = true;	//	Set up to display first ad
	
#ifdef DEBUG
	SetupAdDebugMenu();
#endif
	return noErr;
}

/**********************************************************************
 * SetupAdState - setup ad state data
 **********************************************************************/
static OSErr SetupAdState(Boolean reinit)
{
	short		oldResFile;
	PlayStatePtr	pState;
	uLong		stateSize;

	oldResFile = CurResFile();
	UseResFile(SettingsRefN);
	
	//	Start with play state
	if (!gPlayState) gPlayState = Get1Resource(kStateResType,kStateResId);
	if (!gPlayState)
	{
		//	Create a new play state resource
		gPlayState = NuHandle(sizeof(PlayState));
		if (!gPlayState) return MemError();
		AddResource_(gPlayState,kStateResType,kStateResId,"");		
	}
	
#ifdef DEBUG
	LogPlayStatus();
#endif
	
	RollGlobalFacetime();// kick off face time measurement for this session
	
	pState = *gPlayState;
	
	//	Can't use this if it's a wrong version or if it's not
	//	for the current playlist
	stateSize = sizeof(PlayState) + pState->numAds*sizeof(AdState);
	if (reinit || pState->version != kStateVersion ||
		stateSize != GetHandleSize((Handle)gPlayState))
	{
		OSErr err = 1;
		
		if (kStateVersion==12 &&
				pState->version == 11 &&
				GetHandleSize(gPlayState)==sizeof(PlayState11) + pState->numAds*sizeof(AdState)
			 )
		{
			// We can rebuild him!
			short sz = sizeof(PlayState);
			short sz11 = sizeof(PlayState11);
			short diff = sz-sz11;
			short szAds = pState->numAds*sizeof(AdState);
			
			// resize
			SetHandleBig(gPlayState,GetHandleSize(gPlayState)+diff);
			if (!(err=MemError()))
			{
				// move ads out of the way of new data
				BMD(*gPlayState+sz11,*gPlayState+sz,szAds);
				// zero new data
				WriteZero(*gPlayState+sz11,diff);
				// Update the version.  Forgot this first time
				(*gPlayState)->version = kStateVersion;
			}
		}
		
		//	Initialize a new play state
		if (err)
		{
			SetHandleSize((Handle)gPlayState,sizeof(PlayState));
			ZeroHandle(gPlayState);		
			pState = *gPlayState;
		}
	}

	if (!pState->version)
	{
		//	Setup new play state.	
		DateTimeRec	today;
		
		pState->version = kStateVersion;
		pState->adWidth = kAdWidth;
		pState->adHeight = kAdHeight;
		pState->reqInterval = kReqInverval;
		pState->histLength = kHistLength;
		
		GetTime(&today);
		(*gPlayState)->today = today;
		(*gPlayState)->getNextPlaylist = today;
	}
	UseResFile(oldResFile);
	return noErr;
}

/**********************************************************************
 * InsertAd - insert the current ad
 **********************************************************************/
void InsertAd(short adIdx)
{
	WindowPtr	AdWinWP = GetMyWindowWindowPtr (AdWin);
	CGrafPtr	AdWinPort = GetWindowPort (AdWinWP);
	FSSpec	spec, *pImageSpec = nil;
	Str255	sURL,s;
	AdStatePtr	pAdState,pPlayListState;
	uLong		seconds;
	AdId		adId;
	
	GetPlayListURLString(gPlayList,(*gPlayList)->ads[adIdx].srcURL,sURL);
	GetCacheSpec(sURL,&spec,true);

	//	Remove current ad but don't draw to make for a smoother transition
	//	between ads
	SetEmptyClipRgn(AdWinPort);
	PeteDelete(ADpte,0,0x7fffffff);

	if (FileTypeOf(&spec)=='TEXT')
	{
		//	Text file means HTML document
		UHandle	text;
		char	base[256];

		if (!Snarf(&spec,&text,0))
		{
			long	tStart = 0;
			long	tOffset = -1;
			StringPtr	spot;

			//	Need to insert base ref. Use directory containing html source
			if (spot = PRIndex(sURL,'/'))
				*sURL = spot-sURL;
			ComposeRString(base,MHTML_INFO_TAG,sURL,"",0,"");
			Munger(text,0,nil,0,base+1,*base);
			ComposeString(base,"\p<%r>",htmlXHTMLDir+HTMLDirectiveStrn);
			Munger(text,0,nil,0,base+1,*base);
			InsertHTML(text,&tStart,GetHandleSize(text),&tOffset,ADpte,kDontEnsureCR+kNoMargins);
			ZapHandle(text);
		}
	}
	else
	{
		//	Graphic file. Insert into pte
		uLong	start;
		OSErr	err;
		
		PeteGetTextAndSelection(ADpte,nil,&start,nil);	//	Current selection start should be zero
		err = PeteInsertChar(ADpte,-1,' ',nil);
		ASSERT(err==noErr);	//	Error in ad window pte
		if (err == errAECorruptData)
		{
			//	The pte is corrupted. Let's just get a new one and try it again.
			PeteDispose(AdWin,ADpte);
			ADpte = nil;
			InitAdPte();
			err = PeteInsertChar(ADpte,-1,' ',nil);
		}

		if (!err)
			PeteFileGraphicRange(ADpte,start,start+1,&spec,fgDisplayInline+fgCenterInWindow);
		pImageSpec = &spec;		
	}
	PeteLock(ADpte,0,0x7fffffff,peModLock|peSelectLock);
	InfiniteClip(GetWindowPort(AdWinWP));
	InvalContent(AdWin);
	
	seconds = LocalDateTime();
	// If the ad window is open, and there's an ad in in, the old ad was just closed.
	if (IsWindowVisible (AdWinWP) && (gWasCurAd.server || gWasCurAd.ad))
	{
		AuditAdClose(gWasCurAd.server,gWasCurAd.ad);
		gWasCurAd.server = gWasCurAd.ad = 0;
	}
			
	if ((pAdState=FindCurrentAdState()))
	{
		//	Notify link window about ad
		AdInfoPtr	pAd;

		if (!(*gPlayState)->outOfAds)
			pAdState->lastDisplayTime = seconds;
		if (pAd = FindCurrentAd())
		{
			GetPlayListString(gPlayList,pAd->title,s);
			AddAdToLinkHistory(pAd->adId,GetPlayListURLString(gPlayList,pAd->clickURL,sURL), s, pImageSpec);
		}
	}
	if (!(*gPlayState)->outOfAds && (pPlayListState=FindPlayListState(gPlayList)))
		pPlayListState->lastDisplayTime = seconds;

	gWasCurAd = (*gPlayState)->curAd;
	adId = (*gPlayList)->ads[adIdx].adId;
	AuditAdOpen(adId.server, adId.ad);	// audit the fact that an ad was displayed
}

/************************************************************************
 * NewDayCheck - see if we've gone to a new day
 ************************************************************************/
static void NewDayCheck(Boolean withPrejuidice)
{
	static	short	todayMonth, todayDay;
	DateTimeRec		dt;
	PlayStatePtr	pState;
	
	GetTime(&dt);
	if (!todayMonth)
	{
		todayMonth = dt.month;
		todayDay = dt.day;
	}
	else if (!AdWin && (dt.month != todayMonth || dt.day != todayDay))
		//	Need to reopen ad window
		OpenAdWindow();
		
	if (AdWin && gPlayState)
	{
		if (withPrejuidice || DateCompare(&dt,&(*gPlayState)->today))
		{
			//	New day
			short	i;
			AdInfoPtr	pAd;
			
			RollGlobalFacetime();	// record facetime now
			
			AdFailureChecks(&NoAdsRec);
			SaveNoAdsRec(&NoAdsRec);
			
			if ((*gPlayState)->outOfAds || ((pAd=FindCurrentAd()) && pAd->type==kIsRunout))
				ResetAdSchedule();	// Start from beginning of ad list

			//	Reset all shownToday times
			pState=*gPlayState;
			for(i=0;i<pState->numAds;i++)
				pState->stateAds[i].shownToday = 0;

			//	Set up daily facetime info
			pState->faceTimeWeek[pState->today.dayOfWeek-1] = pState->faceTimeToday;
			pState->faceTimeToday = 0;
			pState->adFaceTimeToday = 0;

			pState->today.month = dt.month;
			pState->today.day = dt.day;
			pState->today.dayOfWeek = dt.dayOfWeek;
			pState->today.year = dt.year;
			pState->outOfAds = false;
			pState->dirty = true;
			
			gDoNextAd = true;	//	New day, new ad
		}
	}
}

/************************************************************************
 * ReadNoAdsRec - read the no ads record
 ************************************************************************/
OSErr ReadNoAdsRec(NoAdsAuxPtr noAds)
{
	TOCHandle outTOC = GetSpecialTOC(OUT);
	
	if (outTOC)
	{
		BMD(&(*outTOC)->internalUseOnly,noAds,sizeof(*noAds));
		return noErr;
	}
	return fnfErr;
}

/************************************************************************
 * SaveNoAdsRec - save the no ads record
 ************************************************************************/
OSErr SaveNoAdsRec(NoAdsAuxPtr noAds)
{
	TOCHandle outTOC = GetSpecialTOC(OUT);
	
	if (outTOC)
	{
		BMD(noAds,&(*outTOC)->internalUseOnly,sizeof((*outTOC)->internalUseOnly));
		TOCSetDirty(outTOC,true);
		return noErr;
	}
	return fnfErr;
}

/************************************************************************
 * AdFailureChecks - check for ad failure
 ************************************************************************/
OSErr AdFailureChecks(NoAdsAuxPtr noAds)
{
	Boolean wereFailing = noAds->adsAreFailing;
	Boolean wereSucceeding = noAds->adsAreSucceeding;
	
	// roll the mailcheck value
	noAds->checkedMail = noAds->checkedMailToday;
	noAds->checkedMailToday = false;
	
	// assume life is good
	noAds->adsAreFailing = false;
	noAds->adsAreSucceeding = true;
	
	// and leave it at that.  Ads don't fail anymore, because we are OUTTA HERE!
	// no more Sponsored mode.  No more Eudora business.  Hasta la vista, baby.
	return noErr;
	
#ifndef I_HATE_THE_BOX
	if (AdWin)
	{
		// determine if we're failing or not
		if (!gPlayState)
		{
			// if no playlists, we're failing
			noAds->adsAreFailing = true;
			noAds->adsAreSucceeding = false;
		}
		else
		{
			// did the user use the program significantly?
			Boolean hadSignificantFacetime = (*gPlayState)->faceTimeToday >= ((*gPlayState)->faceTimeQuota)/2;
			// is the ad facetime insufficient?
			Boolean adTimeTooSmall;
			long adFaceTime = (*gPlayState)->adFaceTimeToday+kQuotaSlop+gNextAdSecs;
			
			// half the facetime quota?
			if (adFaceTime >= (*gPlayState)->faceTimeQuota/2)
				adTimeTooSmall = false;
			// half the actual facetime?
			else if (adFaceTime >= (*gPlayState)->faceTimeToday/2)
				adTimeTooSmall = false;
			else
				// something is wrong...
				adTimeTooSmall = true;
			
			noAds->adsAreFailing = adTimeTooSmall && noAds->checkedMail;
			noAds->adsAreSucceeding = hadSignificantFacetime && !adTimeTooSmall;
		}
		
		// Adjust our counters
		if (noAds->adsAreFailing)
		{
			if (wereSucceeding) noAds->consecutiveDays = 1;
			else noAds->consecutiveDays++;
			if (noAds->deadbeatCounter <= kDeadbeatDays)
				noAds->deadbeatCounter++;
		}
		else if (noAds->adsAreSucceeding)
		{
			noAds->deadbeatCounter = MIN(noAds->deadbeatCounter,kDeadbeatDays-1);
			if (wereFailing) noAds->consecutiveDays = 1;
			else noAds->consecutiveDays++;
			if (noAds->consecutiveDays > kAdGracePeriod && noAds->deadbeatCounter>0)
			{
				noAds->deadbeatCounter--;
				noAds->consecutiveDays = 0;
			}
		}
		
#ifdef DEBUG
		ComposeLogS(LOG_PLIST,nil,"\pAreAdsFailing----->: fail %p succ %p gPS %x adFace %d face %d ftq %d",noAds->adsAreFailing?YesStr:NoStr,noAds->adsAreSucceeding?YesStr:NoStr,gPlayState,(*gPlayState)->adFaceTimeToday,
			(*gPlayState)->faceTimeToday, (*gPlayState)->faceTimeQuota);
		ComposeLogS(LOG_PLIST,nil,"\pAreAdsFailing----->: checked %p dbc %d cd %d",noAds->checkedMail?YesStr:NoStr,noAds->deadbeatCounter,noAds->consecutiveDays);
#endif
	}
#endif
	return noErr;
}


/************************************************************************
 * NextAd - get the next ad
 ************************************************************************/
void NextAd(long secsDisplayed)
{
	PlayListHandle	hPlayList;
	AdStatePtr	pAdState,pPlayListState;
	
	if (AdWin && gPlayListQueue)
	{
		uLong	curTime = LocalDateTime();
		Boolean	found = false;
		// If we're out of ads, we display ads ignoring how long they've already displayed
		ValidAdFlags	vaFlags = (*gPlayState)->outOfAds ? vaCheckNormalAdOnly+vaCheckRerun : vaCheckNormalAdOnly+vaCheckDayMax+vaCheckShowForMax;
		short	adIdx;
		
		if (!gPlayList) gPlayList = gPlayListQueue;
		
		//	Add time to previous ad (if we haven't run out of ads including runout ads)
		if ((gWasCurAd.server || gWasCurAd.ad) && !(*gPlayState)->outOfAds)
		{			
			if (pAdState = FindCurrentAdState())
			{
				pAdState->shownToday++;
				pAdState->shownTotal += secsDisplayed;
				(*gPlayState)->adFaceTimeToday += secsDisplayed;
				AuditAdClose(gWasCurAd.server,gWasCurAd.ad);
				gWasCurAd.server = gWasCurAd.ad = 0;
			}
			if (pPlayListState = FindPlayListState(gPlayList))
				pPlayListState->shownTotal += secsDisplayed;
		}	
		(*gPlayState)->showCur = 0;
	
		if (!(*gPlayState)->faceTimeQuota || (*gPlayState)->faceTimeToday < (*gPlayState)->faceTimeQuota || (*gPlayState)->outOfAds)
		{
			//	Find ad to display
			if ((*gPlayState)->schedType == kRandom)
			{
				//	Find next ad randomly
				unsigned short	randomCount = 0;
				Accumulator	a;

				//	Build list of valid ads to select from
				AccuInit(&a);
				for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
				{
					for(adIdx=0;adIdx<(*hPlayList)->numAds;adIdx++)
					{
						if (ValidAd(hPlayList,adIdx,curTime,vaFlags))
						{
							AccuAddPtr(&a,&(*hPlayList)->ads[adIdx].adId,sizeof(AdId));
							randomCount++;
						}
					}
				}
				if (randomCount)
				{
					unsigned short	randomValue = ((unsigned short)Random()) % randomCount;
					FindAd(((AdId *)*a.data)[randomValue],&adIdx,&gPlayList);
					found = true;
				}
				AccuZap(a);
			}
			else
			{
				//	Find next ad sequentially
				short	startAdIdx;
				short	wrapCount;

				if (FindCurrentAdIdx(&adIdx))
					adIdx++;
				else
				{
					//	Couldn't find current ad, start with first ad
					adIdx = 0;
					gPlayList = gPlayListQueue;
				}
				startAdIdx=adIdx-1;
				hPlayList = gPlayList;
				wrapCount = 0;
				do
				{
					while(adIdx<(*hPlayList)->numAds && !found)
					{
						if (adIdx==startAdIdx && hPlayList==gPlayList)
							goto NotFound;	//	Went though all ads. Didn't find any valid ones
						if (ValidAd(hPlayList,adIdx,curTime,vaFlags))
						{
							//	Use it!
							found = true;
							gPlayList = hPlayList;
						}
						else
							adIdx++;
					}
					if (!found)
					{
						hPlayList = (*hPlayList)->next;	//	Try next playlist
						if (!hPlayList)
						{
							//	Last playlist, wrap around to first
							if (wrapCount) goto NotFound;	//	We're already gone this route and haven't found a thing
							wrapCount++;
							hPlayList = gPlayListQueue;	//	Wrap around
						}
						adIdx = 0;
						if (startAdIdx<0) startAdIdx = 0;	// Couldn't find current ad, started with first ad
					}
				} while (!found);
			}
		}
		
		if (found)
		{
			NewAd(adIdx);
		}
		else
		{
			AdInfoPtr	pAd;
	NotFound:
			//	We are out of ads or have reached our quota
			if ((*gPlayState)->outOfAds)
			{
				// Unlikely this will happen, but we just can't find
				// any ads we can display. Go to black.
				ShowBlackTimeImage();
				gNextAdSecs = 0;	//	No more ads until tomorrow
				gBlackTime = -1;	//	Negative value means display black until an ad shows up
			}
			//	Out of regular ads. Do we have a runout ad?
			else if (pAd = FindAdType(kIsRunout,&adIdx,&gPlayList))
			{
				short	showFor = pAd->showFor;
				NewAd(adIdx);
				if (pAdState = FindCurrentAdState())
				{
					// Charge runout ad for full display time
					pAdState->shownToday++;
					pAdState->shownTotal += showFor;
				}
			}
			else
			{
				//	No regular ads and no runouts. Let's start showing
				//	regular ads again at no charge.
				(*gPlayState)->outOfAds = true;
				gNextAdSecs = 1;	// Do this instead of goto top of this function
				ResetAdSchedule();	// Start from beginning of ad list
			}
		}
		
		UL(gPlayList);
#ifdef DEBUG
		if (RunType==Debugging && FindTextLo(nil,"\pPlaylist Info")) DumpPlaylists(false);
#endif
	}
}

/************************************************************************
 * ValidAd - can this ad be used?
 ************************************************************************/
static Boolean ValidAd(PlayListHandle hPlayList,short adIdx,uLong curTime,ValidAdFlags flags)
{
	AdInfo	*pAd = &(*hPlayList)->ads[adIdx];
	AdStatePtr	pAdState;
	
	if (pAd->adId.server || pAd->adId.ad)
	if (pAdState = FindAdState(pAd->adId,nil))
	if (!(flags&vaCheckNormalAdOnly) || !pAd->type)
	if (!(flags&vaCheckDayMax) || !pAd->dayMax || pAdState->shownToday < pAd->dayMax)
	if (!(flags&vaCheckShowForMax) || !pAd->showForMax || pAdState->shownTotal < pAd->showForMax)
	if (!(flags&vaCheckRerun) || !(*gPlayState)->rerunInterval || curTime - pAdState->lastDisplayTime < (*gPlayState)->rerunInterval)
	{
		//	Not done displaying this ad.
		//	See if we are in range
		if ((pAd->startDate==0 || pAd->startDate <= curTime) &&
				(pAd->endDate==0 || pAd->endDate >= curTime))
			return true;
	}
	return false;
}

/************************************************************************
 * NewAd - use a new ad
 ************************************************************************/
static void NewAd(short adIdx)
{
	Str255	sURL;
	FSSpec	spec;
	AdInfo	*pAd = &(*gPlayList)->ads[adIdx];
	AdId	adId;

	(*gPlayState)->curAd = pAd->adId;
	gNextAdSecs = pAd->showFor;
	(*gPlayState)->showCur = pAd->showFor;
	adId = pAd->adId;
	(*gPlayState)->curPlayList = (*gPlayList)->playListId;
	GetPlayListURLString(gPlayList,pAd->srcURL,sURL);
	GetCacheSpec(sURL,&spec,true);

#ifdef DEBUG
	LogAd(ComposeString(sLog,"\pLoading: %p",sURL));
#endif

	gBlackTime = gNextBlackTime;
	if (gBlackTime < pAd->blackBefore) gBlackTime = pAd->blackBefore;
	gNextBlackTime = pAd->blackAfter;
	
#ifdef DEBUG
	if (gBlackTime)
	{
		LogAd(ComposeString(sLog,"\pBlack for: %d",gBlackTime));
	}
	else
	{
		LogAd(ComposeString(sLog,"\pShow for: %d",gNextAdSecs));
	}
#endif

	FaceMeasureReset(gFaceMeasure);
	
	if (!FSpExists(&spec))
		InsertAd(adIdx);
	else
	{
		DownloadListHandle	hDownload;
		
		//	Don't have ad in cache. Pre-cache all ads
		PreFetchAds();

		//	Put our ad first in list and mark it for display
		for(hDownload=gAdDownloadList;hDownload;hDownload=(*hDownload)->next)
		{
			if (SameAd(&adId,&(*hDownload)->adId))
			{
				LL_Remove(gAdDownloadList,hDownload,(DownloadListHandle));
				LL_Push(gAdDownloadList,hDownload);
				(*hDownload)->display = true;
				break;
			}
		}
		gNextAdSecs += 60;	//	Give us a minute to download this thing, otherwise go on to the next ad
	}

	(*gPlayState)->dirty = true;

#ifdef DEBUG
	if (BUG4)
	{
		//	Make those ads display much faster
		if (gNextAdSecs)
		{
			gNextAdSecs /= kAdScheduleAccelerator;
			if (!gNextAdSecs) gNextAdSecs = 1;
		}
		if (gBlackTime)
		{
			gBlackTime /= kAdScheduleAccelerator;
			if (!gBlackTime) gBlackTime = 1;
		}
	}
#endif
}

/************************************************************************
 * RequestPlaylist - download a play list
 ************************************************************************/
void RequestPlaylist(void)
{
	FSSpec	spec;
	long	reference;
	HTTPinfo	httpStuff;
	Accumulator	a;
	Handle	text;
	Str255		s,s2;
	Handle	hPastry;
	Rect	rScreen;
	PlayListHandle	hPlayList;
	short	i;
	long	faceTimeLeft;
	OSErr	err;
	unsigned char key[kCheckSumSize];
	short	depth;
	
	if (gPLDownloadInProgress) return;	//	We're already downloading a playlist
	
	RollGlobalFacetime();	// make sure our counters are up-to-date

	Zero(httpStuff);
	httpStuff.post = true;
	PCopy(httpStuff.sContentType,"\ptext/xml");
	PCopy(httpStuff.sMessageType,"\pCall");
	
	//	Build request info
	AccuInit(&a);
	XMLNoIndent();
	AccuAddStr(&a,"\p<?xml version=\"1.0\"?>");
	AccuAddCRLF(&a);
	AccuAddCRLF(&a);
	AccuAddTagLine(&a,PlayListKeywords[kClientUpdateTkn],false);
	XMLIncIndent();
	
	//	userAgent
	AccuAddXMLObject(&a,PlayListKeywords[kUserAgentTkn],ComposeString(s,"\pEudora/%d.%d.%d.%d (MacOS)",MAJOR_VERSION,MINOR_VERSION,INC_VERSION,BUILD_VERSION));

	//	locale
	AccuAddXMLWithAttr(&a,PlayListKeywords[kLocaleTkn],ComposeString(s,"\planguage=\"%p\"",GetLanguageCode(s2)),true);

	//	clientMode where 0=adware, 1=light, 2=paid
	AccuAddXMLObject(&a,PlayListKeywords[kClientModeTkn],IsAdwareMode() ? "\p0" : IsPayMode() ? "\p2" : IsProfileDeadbeatUser() ? "\p3" : "\p1");

	//	playlists and ad id's
	if (gPlayListQueue)
	{
		PlayListHandle	hNextPlayList;
		Handle		hKeepAdList = NuHandleClear((*gPlayState)->numAds);
		
		for(hPlayList=gPlayListQueue;hPlayList;hPlayList=hNextPlayList)
		{
			uLong	curTime = LocalDateTime();
			uLong	expiredTime = curTime - (*gPlayState)->histLength*24*60*60;
			short	i;
			Boolean	foundKeeper = false;

			GetPlayListString(hPlayList,(*hPlayList)->playListIDString,s2);
			AccuAddXMLWithAttr(&a,PlayListKeywords[kPlayListTkn],ComposeString(s,"\pid=\"%p\"",s2),false);	//	playlist and id
			//	Do all ads
			XMLIncIndent();
			for(i=0;i<(*hPlayList)->numAds;i++)
			{
				long	active = ValidAd(hPlayList,i,curTime,vaCheckShowForMax)?1:0;
				AdId	adId = (*hPlayList)->ads[i].adId;
				short	adStateIdx;
				AdStatePtr	pAdState;
	
				//	Report only ads that aren't too old
				if (adId.server || adId.ad)
				if ((pAdState = FindAdState(adId,&adStateIdx)) && pAdState->lastDisplayTime > expiredTime &&
					  (!(*hPlayList)->ads[i].endDate || (*hPlayList)->ads[i].endDate > expiredTime))
				{
					foundKeeper = true;
					if (hKeepAdList) (*hKeepAdList)[adStateIdx]=true;
					ComposeString(s,"\pid=\"%d.%d\" active=\"%d\"",adId.server,adId.ad,active);
					switch ((*hPlayList)->ads[i].type)
					{
						case kIsButton:
							PCat(s,"\p isButton=\"1\"");
							if ((*gPlayState)->stateAds[adStateIdx].deleted)
								//	deleted toolbar button
								PCat(s,"\p deleted=\"1\"");
							break;
						case kIsSponsor:	PCat(s,"\p isSponsor=\"1\""); break;
						case kIsRunout:		PCat(s,"\p isRunout=\"1\""); break;
					}
					AccuAddXMLWithAttr(&a,PlayListKeywords[kEntryTkn],s,true);
				}
#ifdef I_EVER_FIX_THE_ENDDATE_BUG
				else
				{
					if (pAdState)
						ASSERT(!(*hPlayList)->ads[i].endDate || pAdState->lastDisplayTime < (*hPlayList)->ads[i].endDate);
				}
#endif
			}
				
			hNextPlayList=(*hPlayList)->next;
			
			if (!foundKeeper)
			{
				//	There are no ads in this playlist that are less than "histLength" days old
				//	Get rid of it
				REMOVE_AND_DESTROY(hPlayList);
			}
			else
			{
				AdId	adId;
				short	playListStateIdx;
				AdStatePtr	pPlayListState;
				
				adId.server = kPlayListServerId;
				adId.ad = (*hPlayList)->playListId;
				if (pPlayListState=FindAdState(adId,&playListStateIdx))
					if (hKeepAdList) (*hKeepAdList)[playListStateIdx]=true;						
			}
			XMLDecIndent();
			AccuAddTagLine(&a,PlayListKeywords[kPlayListTkn],true);
		}
		
		if (hKeepAdList)
		{
			PrunePlayState(hKeepAdList);		
			ZapHandle(hKeepAdList);
		}
	}

	//	screen size
	rScreen = GetScreenBounds();
	if (AdWin)
	{
		SetPort_(GetWindowPort(GetMyWindowWindowPtr(AdWin)));	//	RectDepth needs this
		depth = RectDepth(&AdWin->contR);
	}
	else
		depth = (*(*GetMainDevice())->gdPMap)->pixelSize;
	AccuAddXMLWithAttr(&a,PlayListKeywords[kScreenTkn],ComposeString(s,"\pheight=\"%d\" width=\"%d\" depth=\"%d\"",RectHi(rScreen),RectWi(rScreen),depth),true);	

	//	distributorID
	if (text = GetDistributorID()) {
		AccuAddXMLObjectHandle(&a,PlayListKeywords[kDistIDTkn],text);
		ZapHandle(text);
	}

	//	faceTime for every day of week
	faceTimeLeft = 0;		
	*s = 0;
	for(i=0;i<7;i++)
	{
		long time = (*gPlayState)->faceTimeWeek[i];
		
		// if today's value, take larger of last week and this
		if (i==(*gPlayState)->today.dayOfWeek-1 && (*gPlayState)->faceTimeToday > (*gPlayState)->faceTimeWeek[i])
			time = (*gPlayState)->faceTimeToday;
			
		if (i) PCatC(s,',');
		
		NumToString(time/60,s2);
		PCat(s,s2);
	}
	AccuAddXMLObject(&a,PlayListKeywords[kFaceTimeTkn],s);
	
	//	faceTimeLeft
	faceTimeLeft = TotalFaceTimeLeft();

	NumToString(faceTimeLeft/60,s);
	AccuAddXMLObject(&a,PlayListKeywords[kFaceTimeLeftTkn],s);

	//	faceTimeUsedToday
	NumToString((*gPlayState)->adFaceTimeToday/60,s);
	AccuAddXMLObject(&a,PlayListKeywords[kFaceTimeUsedTodayTkn],s);
	
	//	pastry (cookie)
	if(hPastry = GetResourceFromFile(kStateResType,kPastryResId,SettingsRefN))
	{
		if (!PastryIsStale(hPastry))
			AccuAddXMLWithAttrPtr(&a,PlayListKeywords[kPastryTkn],LDRef(hPastry),GetHandleSize(hPastry),true);
		UL(hPastry);
		ReleaseResource(hPastry);
	}
	
	//	user profile
	if (text = GetProfileData())
	{
		AccuAddXMLObjectHandle(&a,PlayListKeywords[kUserProfileTkn],text);
	}

	//	playListVersion
	NumToString(kPlayListVersion,s);
	AccuAddXMLObject(&a,PlayListKeywords[kPlayListVersionTkn],s);	
	
#ifdef LOGINFO
	//	Send date/time and Id info for server log
	{
		Str255	sDate,sTime;
		long	secs = LocalDateTime();
		
		IUTimeString(secs,false,sTime);
		IUDateString(secs,shortDate,sDate);
		AccuAddXMLObject(&a,PlayListKeywords[kDateAndTime],ComposeString(s,"\p%p, %p",sDate,sTime));
		AccuAddXMLObject(&a,PlayListKeywords[kUserId],GetPref(s,PREF_POP));
	}
#endif

	XMLDecIndent();
	AccuAddTagLine(&a,PlayListKeywords[kClientUpdateTkn],true);
	AccuTrim(&a);

	//	Do not dispose of data in handle. DownloadURL will dispose of it
	httpStuff.hRequestData = a.data;
	NewTempSpec (Root.vRef,Root.dirId,nil,&spec);
	gPLDownloadInProgress = true;
	*s = 0;
#ifdef DEBUG
	GetRString(s,DEBUG_PLAYLIST_URL);
#endif
	if (!*s) PCopy(s,PlayListURL);
	s[*s+1] = 0;
	//	Include checksum
	SetupForDisplay(LDRef(a.data),GetHandleSize(a.data),key,false);
	Bytes2Hex(key,sizeof(key),httpStuff.sCheckSum+1);
	*httpStuff.sCheckSum = 32;
	err = DownloadURL(s+1,&spec,nil,FinishedPlayListDownload,&reference,&httpStuff);

	if (err)
	{
		gPlayListDownloadFail = true;
		gPLDownloadInProgress = false;
	}
	
	//	Now is a good time to tidy up the cache
	CleanupAdCache(false);
	
	// Remember when we did this
	gPlayListFetchGMT = GMTDateTime();
}

/************************************************************************
 * GetAdSpec - get filespec for ad
 ************************************************************************/
static OSErr GetAdSpec(FSSpecPtr spec, AdId adId)
{
	short	adIdx;
	Str255	sURL;
	AdInfoPtr	pAd;
	PlayListHandle	hPlayList;
	
	if (pAd = FindAd(adId,&adIdx,&hPlayList))
	{
		GetPlayListURLString(hPlayList,pAd->srcURL,sURL);
		GetCacheSpec(sURL,spec,true);
		return noErr;
	}
	else
		return fnfErr;
}


/************************************************************************
 * MakePastry - save a pastry (cookie)
 ************************************************************************/
static void SavePastry(Ptr pPastry,long len)
{
	short oldRes = CurResFile();

	if (SettingsRefN) UseResFile(SettingsRefN);
	ZapResourceLo(kStateResType,kPastryResId,true);
	AddPResource(pPastry,len,kStateResType,kPastryResId,nil);
	UseResFile(oldRes);
}

/************************************************************************
 * BuildPlayList - parse play list
 ************************************************************************/
static OSErr BuildPlayList(UHandle text)
{
#define kMaxTokenStack 64
	PlayList	list;
	TokenInfo	tokenInfo;
	short		tokenType;
	Str255		sToken;
	short		tokenStack[kMaxTokenStack];
	short		stackIdx = -1;
	Accumulator	accuKeywordList;
	OSErr		err;
	AdInfo		entryInfo;
	short		i;
	PlayStatePtr	pState;
	DateTimeRec	nextPlaylist;
	Accumulator	aStrings,aState,aPlayList;
	PlayListHandle	hPlayList;
	long level;	// keep track of nag level
	
	//	Initialize tokenizer
	tokenInfo.pText = LDRef(text);
	tokenInfo.size = GetHandleSize(text);
	tokenInfo.offset = 0;
	
	//	Add our keywords to keyword list
	if (err = AccuInit(&accuKeywordList))
		return err;
	AccuAddPtr(&accuKeywordList,&i,sizeof(short));	//	Make room for count
	for(i=0;*PlayListKeywords[i];i++)
	{
		err=AccuAddPtr(&accuKeywordList,PlayListKeywords[i],*PlayListKeywords[i]+1);
		if (err) return err;
	}
	*(short *)*accuKeywordList.data = i;
	tokenInfo.aKeywords = &accuKeywordList;
	ZapHandle(gDownloadErrString);
	
	AccuInitWithHandle(&aState,(Handle)gPlayState);

	//	Parse loop
	while ((tokenType = GetNextToken(&tokenInfo)) != kTokenDone)
	{
		TokenToString(&tokenInfo, sToken);

		//ComposeLogS(LOG_PLIST,nil,"\pxml token: %d %p",tokenType,sToken);
		
		if (tokenType == kContent)
		{
			//	Get element content
			long	zone;

			if (stackIdx >= 1 && 
					tokenStack[stackIdx-1] == kPlayListTkn)
			{
				//	Defining play list elements
				switch (tokenStack[stackIdx])
				{
					case kPlayListIdTkn:
						list.playListId = Hash(sToken); 
						AddStringToHandle(&list.playListIDString,&aStrings,sToken);
						break;
					case kMixAlgorithmTkn:
						list.mixType = StringSame(PlayListKeywords[kBlockTkn],sToken) ? kBlock : kMix;
						break;
					case kBlockScheduleAlgorithmTkn:
						list.blockScheduleType = StringSame(PlayListKeywords[kRandomTkn],sToken) ? kRandom : kLinear;
						break;
					case kClickBaseTkn:
						TokenToURL(&list.clickBase,&aStrings,&tokenInfo);
						break;
				}
			}
			else if (stackIdx >= 1 && 
					tokenStack[stackIdx-1] == kClientInfoTkn)
			{
				//	Defining client info elements
				switch (tokenStack[stackIdx])
				{
					case kReqIntervalTkn:
						(*gPlayState)->reqInterval = PStrToNum(sToken);
						if ((*gPlayState)->reqInterval > kMaxReqInterval) (*gPlayState)->reqInterval = kMaxReqInterval;
						break;
					case kHistLengthTkn:	(*gPlayState)->histLength = PStrToNum(sToken); break;
					case kScheduleTkn:		(*gPlayState)->schedType = StringSame(PlayListKeywords[kRandomTkn],sToken) ? kRandom : kLinear; break;
					case kWidthTkn:			(*gPlayState)->adWidth = PStrToNum(sToken); break;
					case kHeightTkn:		(*gPlayState)->adHeight = PStrToNum(sToken); break;
					case kFaceTimeQuotaTkn:	(*gPlayState)->faceTimeQuota = PStrToNum(sToken)*60; break;	//	Convert to seconds
					case kRerunIntervalTkn:	(*gPlayState)->rerunInterval = PStrToNum(sToken)*24*60*60; break;	//	Convert days to seconds
					case kNagTkn:	HandleNag(level,sToken); break;
					case kClientModeTkn:	HandleClientMode(sToken); break;
					case kUserProfileTkn:	HandleUserProfile(sToken); break;
				}
			}
			else if (stackIdx >= 2 && 
					tokenStack[stackIdx-2] == kPlayListTkn && 
					tokenStack[stackIdx-1] == kEntryTkn)
			{
				//	Defining ad entry elements
				switch (tokenStack[stackIdx])
				{
					case kShowForTkn:		entryInfo.showFor = PStrToNum(sToken); break;
					case kDayMaxTkn:		entryInfo.dayMax = PStrToNum(sToken); break;
					case kShowForMax:		entryInfo.showForMax = PStrToNum(sToken); list.totalShowMax += entryInfo.showForMax; break;
					case kSrcTkn:			TokenToURL(&entryInfo.srcURL,&aStrings,&tokenInfo); break;
					case kBlackBeforeTkn:	entryInfo.blackBefore = PStrToNum(sToken); break;
					case kBlackAfterTkn:	entryInfo.blackAfter = PStrToNum(sToken); break;
					case kStartDateTkn:		entryInfo.startDate = BeautifyDate(sToken,&zone); entryInfo.startDate+=zone; break;
					case kEndDateTkn:		entryInfo.endDate = BeautifyDate(sToken,&zone); entryInfo.endDate+=zone; break;
					case kAdIdTkn:			ParseAdId(sToken,&entryInfo.adId); break;
					case kTitleTkn:			AddStringToHandle(&entryInfo.title,&aStrings,sToken); break;
					case kClickTkn:			TokenToURL(&entryInfo.clickURL,&aStrings,&tokenInfo); break;
				}
			}
			else if (stackIdx >= 1 && 
					tokenStack[stackIdx-1] == kFaultTkn)
			{
				//	Defining fault elements
				switch (tokenStack[stackIdx])
				{
					case kFaultCodeTkn:		(*gPlayState)->errCode = gDownloadErrCode = PStrToNum(sToken); break;
					case kFaultStringTkn:
						gDownloadErrString = NewString(sToken);
						Log(LOG_PLIST,sToken);
						break;
				}
			}
		}
		else
		{
			//	Process a tag
			short	tokenIdx,attrIdx;
			long	value;
		
			tokenIdx = GetTokenIdx(&accuKeywordList,sToken)-1;
			switch (tokenType)
			{
				case kElementTag:
				case kEmptyElementTag:
					switch (tokenIdx)
					{
						case kEntryTkn:
							//	New ad entry
							
							Zero(entryInfo);
							while(attrIdx = GetNumAttribute(&tokenInfo,&value))
							{
								switch (attrIdx)
								{
									case kIsButtonTkn:	if (value) entryInfo.type=kIsButton; break;
									case kIsSponsorTkn:	if (value) entryInfo.type=kIsSponsor; break;
									case kIsRunoutTkn:		if (value) entryInfo.type=kIsRunout; break;
								}
							}
							break;
						
						case kSrcTkn:
							if (GetAttribute(&tokenInfo,sToken) == kCheckSum && *sToken==32)
								//	Checksum for src
								Hex2Bytes(sToken+1,32,entryInfo.checksum);
							break;
						
						case  kPlayListTkn:
							//	New playlist
							Zero(list);
							gPlayList = NuHandleClear(sizeof(PlayList));
							AccuInitWithHandle(&aPlayList,(Handle)gPlayList);
							if (!gPlayList) return MemError();
							//	Accumulator for strings
							AccuInit(&aStrings);
							AccuAddChar(&aStrings,0);	//	We don't want any strings with offset zero
							//	Set up some default values
							list.version = kPlayListVers;
							break;

						case kFlushTkn:
							if (tokenStack[stackIdx]==kClientInfoTkn)
								Flush(&tokenInfo);
							break;
							
						case kPastryTkn:
							if (tokenStack[stackIdx]==kClientInfoTkn)
								SavePastry(tokenInfo.pText+tokenInfo.attrStart,tokenInfo.attrLen);
							break;
						
						case kNagTkn:
							level = 0;
							if (GetAttribute(&tokenInfo,sToken) == kLevelTkn)
								StringToNum(sToken,&level);
							break;
							
						case kResetTkn:
							//	The server has determined that we are insane
							//	Reset data and request a new playlist
							ResetAdState();
							AccuInitWithHandle(&aState,(Handle)gPlayState);	//	gPlayState has been resized
							break;
						
						case kClientInfoTkn:
							//	Set up default client info in case some values
							//	aren't included
							(*gPlayState)->reqInterval = kReqInterval;
							(*gPlayState)->histLength = kHistLength;
							(*gPlayState)->schedType = kSchedType;
							(*gPlayState)->adWidth = kAdWidth;
							(*gPlayState)->adHeight = kAdHeight;
							(*gPlayState)->faceTimeQuota = kFaceTimeQuota * 60;
							(*gPlayState)->rerunInterval = kRerunInterval * 24*3600;
							break;
					}
					if (tokenType==kElementTag)
					{
						//	New element. Push token on stack
						//	Don't put empty tags on stack. There is no end-tag
						if (stackIdx < kMaxTokenStack-1)
							tokenStack[++stackIdx] = tokenIdx;
					}
					break;
					
				case kEndTag:
					//	End of element. Pop off stack. Actually search whole stack
					//	in case document is not well-formed.
					while (stackIdx >= 0)
					{
						//	
						if (tokenStack[stackIdx--] == tokenIdx)
							break;
					}
					
					switch (tokenIdx)
					{
						case kEntryTkn:
							//	Save ad entry
							if (!AccuAddPtr(&aPlayList,&entryInfo,sizeof(entryInfo)))
							{
								list.numAds++;
								DeleteAd(entryInfo.adId);
							}
							break;
						
						case kPlayListTkn:
							//	Save playlist
							if (hPlayList=FindPlayList(list.playListId))
							{
								//	We're replacing this playlist. Delete it.
								REMOVE_AND_DESTROY(hPlayList);
							}
							AccuTrim(&aPlayList);
							list.strOffset = GetHandleSize_(gPlayList);
							**gPlayList = list;	//	Save final playlist info
							//	Add strings to end of playlist
							AccuTrim(&aStrings);
							if (HandAndHand(aStrings.data,(Handle)gPlayList)) return MemError();
							AccuZap(aStrings);
							//	Save playlist resource
							AddMyResource_(gPlayList,kPlayListResType,UniqueID(kPlayListResType),"");
							UpdateResFile(SettingsRefN);
							LL_Queue(gPlayListQueue,gPlayList,(PlayListHandle));
							AddStates(gPlayList,&aState,true);
							break;
					}
					break;
			}
		}
	}

	SecondsToDate(LocalDateTime()+((*gPlayState)->reqInterval?(*gPlayState)->reqInterval:kReqInverval)*60L*60L,&nextPlaylist);

	pState = *gPlayState;
	pState->getNextPlaylist = nextPlaylist;
	pState->dirty = true;
	gAdFetchCount = 0;
	pState->adsNotFetched = 0;
	gDownloadErrCode = pState->errCode;
	AccuTrim(&aState);	//	Don't dispose of this accumulator!
	SavePlayStatus();
	SetCurrentPlayList();
	
	UL(text);
#ifdef DEBUG
	SetupAdDebugMenu();
#endif
	return noErr;
}

/************************************************************************
 * ResetAdState - throw away all ad data and request a new playlist
 ************************************************************************/
static void ResetAdState(void)
{
	//	Delete playlists
	while (gPlayListQueue)
	{
		PlayListHandle	hPlayList;
		
		hPlayList = gPlayListQueue;
		REMOVE_AND_DESTROY(hPlayList);
	}
	//	Reinialize ad state
	SetupAdState(true);
	
	//	Get rid of all cached ads
	CleanupAdCache(true);
	
	//	Nothing to display right now
	ShowBlackTimeImage();
	gBlackTime = -1;	//	Negative value means display black until an ad shows up
	RollGlobalFacetime();
	RequestPlaylist();	
#ifdef DEBUG
	{
		MenuHandle mh;

		if (mh = GetMHandle(AD_SELECT_HIER_MENU));
			TrashMenu(mh,gAdsMenuAdsStart);
	}
#endif
}

/************************************************************************
 * DeleteAd - delete this ad from every playlist
 ************************************************************************/
static void DeleteAd(AdId adId)
{
	short	idx;
	PlayListHandle	hPlayList;
	
	while (FindAd(adId,&idx,&hPlayList))
	{
		PlayListPtr	pPlayList = *hPlayList;
		
		pPlayList->numAds--;
		if (pPlayList->numAds)
		{
			long	playListLen = GetHandleSize((Handle)hPlayList);
			long	moveBytes = (long)*hPlayList + playListLen - (long)&pPlayList->ads[idx+1];

			pPlayList->strOffset -= sizeof(AdInfo);
			BlockMove(&pPlayList->ads[idx+1],&pPlayList->ads[idx],moveBytes);
			SetHandleSize((Handle)hPlayList,playListLen-sizeof(AdInfo));
			ChangedResource((Handle)hPlayList);
		}
		else
		{
			//	Nothing left in this playlist. Let's get rid of it.
			REMOVE_AND_DESTROY(hPlayList);
		}
	}
#ifdef DEBUG
	SetupAdDebugMenu();
#endif
}

/************************************************************************
 * AddStates - make sure all ads are in playstate
 ************************************************************************/
static void AddStates(PlayListHandle hPlayList, AccuPtr aState, Boolean init)
{
	AdId	adId;
	AdState	state;
	short	i;
	AdStatePtr	pAdState;
	
	for(i=-1;i<(*hPlayList)->numAds;i++)
	{
		if (i==-1)
		{
			//	Start with playlist state
			adId.server = kPlayListServerId;
			adId.ad = (*hPlayList)->playListId;
		}
		else
			adId = (*hPlayList)->ads[i].adId;

		Zero(state);
		state.adId = adId;
		state.lastDisplayTime = LocalDateTime();

		if (pAdState = FindAdState(adId,nil))
		{
			//	Reinitialize ad state
			if (init)
			{
				*pAdState = state;
				(*gPlayState)->dirty = true;
			}
		}
		else
		{
			//	Not already there. Add it.
			if (!AccuAddPtr(aState,&state,sizeof(AdState)))
			{
				(*gPlayState)->numAds++;
				(*gPlayState)->dirty = true;
			}									
		}
	}
}

/************************************************************************
 * DisposePlaylists - dispose of play lists
 ************************************************************************/
static void DisposePlaylists(void)
{
	PlayListHandle	hPlayList,nextPlaylist;
	
	for(hPlayList=gPlayListQueue;hPlayList;hPlayList=nextPlaylist)
	{
		nextPlaylist = (*hPlayList)->next;
		ReleaseResource((Handle)hPlayList);
	}
	gPlayListQueue = nil;
	gPlayList = nil;
}

/************************************************************************
 * PeteIsInAdWindow - dispose of play list
 ************************************************************************/
Boolean PeteIsInAdWindow(PETEHandle pte)
{
	WindowPtr	pteWinWP = GetMyWindowWindowPtr ((*PeteExtra(pte))->win);
	return pte && GetWindowKind(pteWinWP)==AD_WIN;
}

/************************************************************************
 * ValidAdImage - return error if image is in ad window and has invalid checksum
 ************************************************************************/
OSErr ValidAdImage(PETEHandle pte,Handle hData)
{
	OSErr	err = noErr;

	if (PeteIsInAdWindow(pte))
	if (err = CheckAd((*gPlayState)->curAd,hData,nil))
		//	Bad ad. Display the next ad
		gDoNextAd = true;
	return err;
}

/************************************************************************
 * CheckAd - return error if ad has invalid checksum
 ************************************************************************/
static OSErr CheckAd(AdId adId,Handle hData,FSSpec *spec)
{
	OSErr	err = noErr;
	AdInfoPtr	pAd;
	PlayListHandle	hPlayList;
		
	if (pAd = FindAd(adId,nil,&hPlayList))
	{
		short	i;
		Boolean	hasChecksum = false;
		AdInfo	ad = *pAd;
		
		for(i=0;i<kCheckSumSize;i++)
			if (ad.checksum[i])
			{
				hasChecksum = true;
				break;
			}

		if (hasChecksum)	// If no checksum specified, ad is OK
		{
			Boolean	disposeData = false;
			
			if (!hData && spec)
			{
				Snarf(spec,&hData,0);
				disposeData = true;
			}
			if (hData)
			{
				err = SetupForDisplay(LDRef(hData),GetHandleSize(hData),ad.checksum,true);
				if (err)
				{
					//	Bad ad.
					if (IsChecksumFailAgain(adId))
						//	We've redownloaded and failed again. Get rid of this ad.
						DeleteAd(adId);
					else
					{
					
						//	Bad ad. Delete it and download again
						FSSpec	delSpec;
						Handle	url;
						
						if (spec) delSpec = *spec;
						else GetAdSpec(&delSpec,adId);
						FSpDelete(&delSpec);

						//	Download ad again.
						if (url = GetPlayListURL(hPlayList,ad.srcURL))
						{
							if (!QueueAdDownload(url,ad.adId,ad.type))
								ZapHandle(url);	//	Error, didn't queue, is duplicate
						}
					}
				}
				if (disposeData) ZapHandle(hData);
			}
		}		
	}
	return err;
}

/************************************************************************
 * IsChecksumFailAgain - has this ad failed a checksum previously?
 ************************************************************************/
static Boolean IsChecksumFailAgain(AdId adId)
{
	static AdId	**hFailList;
	Boolean	found = false;
	
	if (!hFailList) hFailList = NuHandle(0);
	else
	{
		short	i,n = HandleCount(hFailList);
		
		for(i=0;i<n;i++)
			if (SameAd(&adId,&(*hFailList)[i]))
				return true;
	}
	PtrAndHand(&adId,(Handle)hFailList,sizeof(adId));
	return false;
}

/************************************************************************
 * IsAdInPlaylist - is this ad in the current playlist?
 ************************************************************************/
Boolean IsAdInPlaylist(AdId adId)
{	
	return FindAd(adId,nil,nil) != nil;
}

/************************************************************************
 * AdWinGotImage - a graphic was just inserted--do we need to report it?
 ************************************************************************/
void AdWinGotImage(PETEHandle pte,FSSpecPtr spec)
{
	if (PeteIsInAdWindow(pte))
	{
		AdStatePtr	pAd;
		
		if (pAd = FindCurrentAdState())
		{
			//	Give link window graphic filespec
			(*gPlayState)->dirty = true;
			AddAdToLinkHistory(pAd->adId, nil, nil, spec);
		}		
	}
}

/************************************************************************
 * FindPlayList - find this playlist
 ************************************************************************/
static PlayListHandle FindPlayList(long playListId)
{
	PlayListHandle	hPlayList;
	
	for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
		if ((*hPlayList)->playListId == playListId)
			return hPlayList;
	
	return nil;
}

/************************************************************************
 * SetCurrentPlayList - set gPlayList to current playlist
 ************************************************************************/
static void SetCurrentPlayList(void)
{
	PlayListHandle	hPlayList;
	
	if (gPlayState && (hPlayList=FindPlayList((*gPlayState)->curPlayList)))
		gPlayList = hPlayList;
	else
		gPlayList = gPlayListQueue;
}

/************************************************************************
 * FindAd - find this ad in a playlist
 ************************************************************************/
static AdInfoPtr FindAd(AdId adId,short *idx,PlayListHandle *playList)
{
	short	adIdx;
	PlayListHandle	hPlayList;
	AdInfoPtr	pAd;
	
	for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
		for (adIdx=0,pAd=(*hPlayList)->ads;adIdx<(*hPlayList)->numAds;adIdx++,pAd++)
			if (SameAd(&adId,&pAd->adId))
			{
				if (idx) *idx = adIdx;
				if (playList) *playList = hPlayList;
				return pAd;
			}
	return nil;
}

/************************************************************************
 * FindAdType - find a valid ad of this type in a playlist
 ************************************************************************/
static AdInfoPtr FindAdType(AdType adType,short *idx,PlayListHandle *playList)
{
	return FindAdTypeLo(adType,0,gPlayListQueue,idx,playList);
}

/************************************************************************
 * FindAdTypeLo - find a valie ad of this type in a playlist
 ************************************************************************/
static AdInfoPtr FindAdTypeLo(AdType adType,short startIdx,PlayListHandle startPlayList,short *idx,PlayListHandle *playList)
{
	AdInfoPtr	pAd;
	PlayListHandle	hPlayList;
	short	adIdx;
	AdStatePtr	pAdState;
	uLong	curTime = LocalDateTime();
		
	for(hPlayList=startPlayList;hPlayList;hPlayList=(*hPlayList)->next)
	{
		for (adIdx=startIdx,pAd=(*hPlayList)->ads+startIdx;adIdx<(*hPlayList)->numAds;adIdx++,pAd++)
			if (adType == pAd->type)
			if (pAdState = FindAdState(pAd->adId,nil))
			if (!pAdState->deleted)
			if (ValidAd(hPlayList,adIdx,curTime,vaCheckDayMax+vaCheckShowForMax))
			{
				if (idx) *idx = adIdx;
				if (playList) *playList = hPlayList;
				return pAd;
			}
		startIdx = 0;
	}
			
	return nil;
}

/************************************************************************
 * FindCurrentAdIdx - find current ad in playlist
 ************************************************************************/
static AdInfoPtr FindCurrentAdIdx(short *idx)
{
	return FindAd((*gPlayState)->curAd,idx,&gPlayList);
}

/************************************************************************
 * FindCurrentAd - find current ad in playlist
 ************************************************************************/
static AdInfoPtr FindCurrentAd(void)
{
	return FindCurrentAdIdx(nil);
}

/************************************************************************
 * FindAdState - find this ad in the playstate
 ************************************************************************/
static AdStatePtr FindAdState(AdId adId,short *idx)
{
	short	adIdx;
	AdStatePtr	pAd;
	
	for (adIdx=0,pAd=(*gPlayState)->stateAds;adIdx<(*gPlayState)->numAds;adIdx++,pAd++)
		if (SameAd(&adId,&pAd->adId))
		{
			if (idx) *idx = adIdx;
			return pAd;
		}
			
	//	Not found
	return nil;
}

/************************************************************************
 * FindCurrentAdState - find current ad in playState
 ************************************************************************/
static AdStatePtr FindCurrentAdState(void)
{
	return FindAdState((*gPlayState)->curAd,nil);
}

/************************************************************************
 * FindPlayListState - find current playlist in playState
 ************************************************************************/
static AdStatePtr FindPlayListState(PlayListHandle hPlayList)
{
	AdId	adId;
	AdStatePtr	pAdState=nil;
	
	if (hPlayList)
	{
		adId.server = kPlayListServerId;
		adId.ad = (*hPlayList)->playListId;
		if (!(pAdState=FindAdState(adId,nil)))
		{
			Accumulator	aState;
			
			AccuInitWithHandle(&aState,(Handle)gPlayState);
			AddStates(hPlayList, &aState, false);
			AccuTrim(&aState);	//	Don't dispose of this accumulator!
			pAdState=FindAdState(adId,nil);
		}
	}
	return pAdState;
}

/************************************************************************
 * CleanupAdCache - purge old ads
 ************************************************************************/
static void CleanupAdCache(Boolean withPrejuidice)
{
	uLong	cutoffDate = LocalDateTime()-kPurgeAdDays*60*60*24;
	Str32	name;
	FSSpec	spec;
	CInfoPBRec hfi;

	// (jp)  9-10-99 If we can't find the Ad folder, don't delete anything
	// You mean, if you CAN find the ad folder, don't delete anything.  Yikes!  SD 12/5/99
	if (!SubFolderSpec(AD_FOLDER_NAME,&spec)) {
		hfi.hFileInfo.ioNamePtr = name;
		hfi.hFileInfo.ioFDirIndex = 0;
		while (!DirIterate(spec.vRefNum,spec.parID,&hfi))
		{
			if (withPrejuidice || hfi.hFileInfo.ioFlMdDat < cutoffDate)
			{
				//	Old enough, delete it
				PCopy(spec.name,name);
				if (!FSpDelete(&spec))	// (jp)  9-10-99 Only decrement the index if there was no err
					hfi.hFileInfo.ioFDirIndex--;
			}
		}
	}
}

/************************************************************************
 * TotalFaceTimeLeft - how much face time do we have lying around?
 ************************************************************************/
long TotalFaceTimeLeft(void)
{
	PlayListHandle hPlayList;
	long faceTimeLeft = 0;
	
	for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
	{
		AdStatePtr	pPlayListState;
		if ((*hPlayList)->totalShowMax && (pPlayListState=FindPlayListState(hPlayList)))
		{
			long	thisFaceTimeLeft;
			thisFaceTimeLeft = (*hPlayList)->totalShowMax - pPlayListState->shownTotal;
			if (thisFaceTimeLeft < 0) thisFaceTimeLeft = 0;
			faceTimeLeft += thisFaceTimeLeft;
		}
	}
	
	return faceTimeLeft;
}

#ifdef LOGADS
/************************************************************************
 * DoLogAds - log ad scheduling
 ************************************************************************/
static void DoLogAds(StringPtr sLog)
{
	FSSpec spec;
	Str31 scratch;

	//	Don't log ads unless Log window is open
	SimpleMakeFSSpec(Root.vRef,Root.dirId,GetRString(scratch,LOG_NAME),&spec);	
	if (FindText(&spec))
		Log(LOG_MORE,sLog);
}
#endif

#ifdef DEBUG
/************************************************************************
 * ToggleAdWindow - open/close ad window
 ************************************************************************/
void ToggleAdWindow(Boolean resetPlayState)
{
	if (AdWin)
		CloseAdWindow();
	else
	{
		//	Reset play state before opening ad window
		short	oldRes = CurResFile();
		
		if (resetPlayState)
		{
			if (SettingsRefN) UseResFile(SettingsRefN);
			Zap1Resource(kStateResType,kStateResId);
			UseResFile(oldRes);
		}
		gPlayState = nil;
		OpenAdWindow();
		RequestPlaylist();
	}
}

/************************************************************************
 * DebugAdMenu - do debug ad menu item
 ************************************************************************/
void DebugAdMenu(short item,short modifiers)
{
	FSSpec	spec;
	
	switch (item)
	{
		newPlayList:
		case dbNewPlaylist:
			if (modifiers&optionKey) ResetAdState();
			RequestPlaylist();
			break;
		case dbReloadPlaylist:
			SubFolderSpec(AD_FOLDER_NAME,&spec);
			PCopy(spec.name,sPlayListFileName);
			NewPlaylist(&spec,nil);
			break;
		case dbDumpPlaylists:
			DumpPlaylists((modifiers&optionKey)!=0);
			break;
		case dbDefaultPLServer:
			SetPref(DEBUG_PLAYLIST_URL,"");
			goto newPlayList;
		default:
			if (gPlayList && item >= gAdsMenuAdsStart)
			{
				PlayListHandle	hPlayList;
				short	adIdx;

				adIdx = item-gAdsMenuAdsStart;
				for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
				{
					if (adIdx < (*hPlayList)->numAds) break;
					adIdx -= (*hPlayList)->numAds+1;
				}
				if (hPlayList && adIdx < (*hPlayList)->numAds)
				{
					gPlayList = hPlayList;
					NewAd(adIdx);
				}
				gBlackTime = 0;	//	Start ad right away
			}
			else if (item > dbDefaultPLServer)
			{
				Str127 server;
				MyGetItem(GetMHandle(AD_SELECT_HIER_MENU),item,server);
				SetPref(DEBUG_PLAYLIST_URL,server);
				goto newPlayList;
			}
			break;
	}
}

/************************************************************************
 * SetupAdDebugMenu - setup ad debug menu
 ************************************************************************/
static void SetupAdDebugMenu()
{
	MenuHandle	mh;
	Str255	s;
	
	if (RunType!=Production && (mh=GetMHandle(AD_SELECT_HIER_MENU)))
	{
		short	i;
		PlayListHandle	hPlayList;

		if (!gAdsMenuAdsStart)
		{
			gAdsMenuAdsStart = dbDefaultPLServer;
			MyAppendMenu(mh,PlayListURL); gAdsMenuAdsStart++;
			for (i=1;*GetRString(s,PLAYLIST_SERVER_STRN+i);i++)
				{MyAppendMenu(mh,s); gAdsMenuAdsStart++;}
			AppendMenu(mh,"\p-"); gAdsMenuAdsStart++;
		}

		TrashMenu(mh,gAdsMenuAdsStart);
		
		for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
		{
			for(i=0;i<(*hPlayList)->numAds;i++)
			{
				GetPlayListString(hPlayList,(*hPlayList)->ads[i].title,s);
				if (!*s) PCopy(s,"\pUntitled");
				if ((*hPlayList)->ads[i].type)
				{
					PCatC(s,' '); PCatC(s,'(');
					PCatC(s,'0'+(*hPlayList)->ads[i].type);
					PCatC(s,')');
				}
				MyAppendMenu(mh,s);
			}
			if ((*hPlayList)->next)
				AppendMenu(mh,"\p-");
		}
	}	
}

/************************************************************************
 * CheckCurrentAd - check the current ad in ad debug menu
 ************************************************************************/
void CheckCurrentAd(void)
{
	MenuHandle	mh;
	Str127 server;
	short mItem;
	
	if (mh=GetMHandle(AD_SELECT_HIER_MENU))
	{
		uLong	curTime = LocalDateTime();
		CheckNone(mh);
		
		// Check playlist server
		GetRString(server,DEBUG_PLAYLIST_URL);
		if (!*server) PCopy(server,PlayListURL);
		SetItemMark(mh,FindItemByName(mh,server),checkMark);
		
		// check ad
		if (gPlayList)
		{
			short	adIdx;
			PlayListHandle	hPlayList;
			MCEntry	menuColor,*pMenuColor;
			
			FindCurrentAdIdx(&adIdx);
			for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
			{
				if (hPlayList==gPlayList) break;
				adIdx += (*hPlayList)->numAds+1;
			}
			SetItemMark(mh,adIdx+gAdsMenuAdsStart,checkMark);

			// Mark inactive ones
			mItem = gAdsMenuAdsStart;
			Zero(menuColor);
			menuColor.mctID = AD_SELECT_HIER_MENU;
			SetRGBGrey(&menuColor.mctRGB2,0x8000);
			if (pMenuColor = GetMCEntry(AD_SELECT_HIER_MENU,0))
				menuColor.mctRGB4 = pMenuColor->mctRGB4;
			for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
			{
				for(adIdx=0;adIdx<(*hPlayList)->numAds;adIdx++,mItem++)
				{
					if (ValidAd(hPlayList,adIdx,curTime,vaCheckDayMax+vaCheckShowForMax))
					{
						DeleteMCEntries(AD_SELECT_HIER_MENU,mItem);
					}
					else
					{
						//	Make item appear to be disabled
						menuColor.mctItem = mItem;
						SetMCEntries(1,&menuColor);
					}
					if (!ValidAd(hPlayList,adIdx,curTime,vaCheckShowForMax)) SetItemStyle(mh,mItem,italic);
				}
				mItem++;
			}
		}		
	}
}

Accumulator	DumpAcc;
short		DumpIndent;

/************************************************************************
 * DumpPlaylists - dump playlist info to a text window
 ************************************************************************/
void DumpPlaylists(Boolean fullDisplay)
{
	//	For every block that's not in our list, dump it to log
	MyWindowPtr	win;
	PlayListHandle	hPlayList;
	Str255	s,s2;
	DateTimeRec	date;
	
	AccuInit(&DumpAcc);

	DumpIndent = 0;
	AccuDump(ComposeString(s,"\pTotal number of ads: %d",(*gPlayState)->numAds));
	AccuDump(ComposeString(s,"\pHistory length: %d",(*gPlayState)->histLength));
	date = (*gPlayState)->getNextPlaylist;
	AccuDump(ComposeString(s,"\pRequest next playlist: %d/%d/%d %d:%d",date.month,date.day,date.year,date.hour,date.minute));
	AccuDump(ComposeString(s,"\pFetch error code: %d",(*gPlayState)->errCode));
	AccuDump(ComposeString(s,"\pRerun mode: %p",(*gPlayState)->outOfAds?"\pyes":"\pno"));
	RollGlobalFacetime();// update faceTimeToday
	AccuDump(ComposeString(s,"\pFacetime today: %d",(*gPlayState)->faceTimeToday));
	AccuDump(ComposeString(s,"\pAd facetime today: %d",(*gPlayState)->adFaceTimeToday));
	AccuDump(ComposeString(s,"\pFacetime quota: %d",(*gPlayState)->faceTimeQuota));
	AccuDump(ComposeString(s,"\pFaceTime week: %d,%d,%d,%d,%d,%d,%d",(*gPlayState)->faceTimeWeek[0],
		(*gPlayState)->faceTimeWeek[1],(*gPlayState)->faceTimeWeek[2],(*gPlayState)->faceTimeWeek[3],
		(*gPlayState)->faceTimeWeek[4],(*gPlayState)->faceTimeWeek[5],(*gPlayState)->faceTimeWeek[6]));
	AccuDump(ComposeString(s,"\pRerun interval: %d",(*gPlayState)->rerunInterval/(24*60*60)));
	AccuDump(ComposeString(s,"\pAd Failure States: Failed--%B  Succeeded--%B Checked Mail--%B Checked Mail Today--%B",
		 NoAdsRec.adsAreFailing, NoAdsRec.adsAreSucceeding, NoAdsRec.checkedMail, NoAdsRec.checkedMailToday));
	AccuDump(ComposeString(s,"\pAd Failure Counters: DeadbeatCounter %d ConsecutiveDays %d", NoAdsRec.deadbeatCounter, NoAdsRec.consecutiveDays));

	for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
	{
		short	adIdx;
		
		AccuAddChar(&DumpAcc,'\r');
		
		GetPlayListString(hPlayList,(*hPlayList)->playListIDString,s2);
		AccuDump(ComposeString(s,"\pPlaylist \"%p\", Ad count: %d, Show max total: %d",s2,(*hPlayList)->numAds,(*hPlayList)->totalShowMax));
		DumpIndent++;
		for(adIdx=0;adIdx<(*hPlayList)->numAds;adIdx++)
		{
			AdInfo	ad = (*hPlayList)->ads[adIdx];
			AdStatePtr	pAdState;
			
			GetPlayListString(hPlayList,ad.title,s2);
			AccuDump(ComposeString(s,"\p\"%p\" Id: %d.%d",s2,ad.adId.server,ad.adId.ad));
			DumpIndent++;
			if (ad.type)
				AccuDump(ComposeString(s,"\pType: %p",ad.type==1?"\pToolbar button":ad.type==2?"\pSponsor":"\pRunout"));
			AccuDump(ComposeString(s,"\pshowFor: %d, dayMax: %d, showForMax: %d",ad.showFor,ad.dayMax,ad.showForMax));
			if (ad.blackBefore || ad.blackAfter)
				AccuDump(ComposeString(s,"\pblackBefore: %d, blackAfter: %d",ad.blackBefore,ad.blackAfter));
			AccuDumpTime("\pStart Date",ad.startDate);
			AccuDumpTime("\pEnd Date",ad.endDate);
			
			if (pAdState = FindAdState(ad.adId,nil))
			{
				AdState	state = *pAdState;

				AccuDump(ComposeString(s,"\pShown today: %d, shown total: %d",state.shownToday,state.shownTotal));
				AccuDumpTime("\pLast display",state.lastDisplayTime);
				
			}
			
			if (fullDisplay)
			{
				GetPlayListURLString(hPlayList,ad.srcURL,s2);
				AccuDump(ComposeString(s,"\pSource URL: %p",s2));
				GetPlayListURLString(hPlayList,ad.clickURL,s2);
				AccuDump(ComposeString(s,"\pClick URL: %p",s2));
			}
			DumpIndent--;			
		}
		DumpIndent--;
	}
	
	if (!(win = FindTextLo(nil,"\pPlaylist Info")))
		win = OpenText(nil,nil,nil,nil,true,"\pPlaylist Info",true,false);
	if (!win) return;
	win->close = DumpClose;
	(*PeteExtra(win->pte))->spelled = sprNeverSpell;
	AccuTrim(&DumpAcc);
	PeteSetText(win->pte,DumpAcc.data);
	AccuZap(DumpAcc);
}

/************************************************************************
 * DumpClose - don't prompt for save on window close
 ************************************************************************/
static Boolean DumpClose(MyWindowPtr win)
{
	return(true);
}

/************************************************************************
 * AccuDump - write a line to dump accumulator
 ************************************************************************/
static void AccuDump(StringPtr s)
{
	short	i;
	
	for(i=0;i<3*DumpIndent;i++) AccuAddChar(&DumpAcc,' ');	//	Do indentation
	AccuAddStr(&DumpAcc,s);
	AccuAddChar(&DumpAcc,'\r');
}

/************************************************************************
 * AccuDumpTime - write date/time to dump accumulator
 ************************************************************************/
static void AccuDumpTime(StringPtr sName, uLong seconds)
{
	Str255	s;
	DateTimeRec	date;
	
	if (seconds)
	{
		SecondsToDate(seconds, &date);
		AccuDump(ComposeString(s,"\p%p: %d/%d/%d %d:%d",sName,date.month,date.day,date.year,date.hour,date.minute));
	}
}

#endif

/************************************************************************
 * PreFetchAds - load all ads into cache
 ************************************************************************/
static void PreFetchAds(void)
{
	short	i;
	Str255	sURL;
	FSSpec	spec;
	PlayListHandle	hPlayList;
	
	if (gAdDownloadList) return;	//	We're still fetching ads
	
	(*gPlayState)->adsNotFetched = 0;
	for(hPlayList=gPlayListQueue;hPlayList;hPlayList=(*hPlayList)->next)
	{
		for(i=0;i<(*hPlayList)->numAds;i++)
		{
			GetPlayListURLString(hPlayList,(*hPlayList)->ads[i].srcURL,sURL);
			GetCacheSpec(sURL,&spec,true);
			if (FSpExists(&spec))
			{
				//	Don't have ad in cache. Go get it.
				Handle	url;

				if (url = GetPlayListURL(hPlayList,(*hPlayList)->ads[i].srcURL))
				{
					if (!QueueAdDownload(url,(*hPlayList)->ads[i].adId,(*hPlayList)->ads[i].type))
						ZapHandle(url);	//	Error, didn't queue, is duplicate
				}
			}	
		}
	}
}

/************************************************************************
 * QueueAdDownload - put ad in download queue if not duplicate
 ************************************************************************/
static Boolean QueueAdDownload(Handle url, AdId adId, AdType adType)
{
	DownloadListHandle	hDownload;
	
	for(hDownload=gAdDownloadList;hDownload;hDownload=(*hDownload)->next)
	{
		//	See if we already have this ad in the queue
		if (SameAd(&adId,&(*hDownload)->adId) || !strcmp(*url,*(*hDownload)->url)) return false;	//	Already in queue
		
	}
	if (hDownload = NuHandleClear(sizeof(DownloadList)))
	{
		(*hDownload)->url = url;
		(*hDownload)->adId = adId;
		(*hDownload)->adType = adType;
		LL_Push(gAdDownloadList,hDownload);
		return true;	
	}
	return false;
}

/************************************************************************
 * CheckDownloadList - see if we can download any ads
 ************************************************************************/
static void CheckDownloadList(void)
{
	while (gAdDownloadList && gDownloadCount < kMaxDownloads)
	{
		Handle	url = (*gAdDownloadList)->url;
		long	reference;
		Str255	sURL;
		FSSpec	spec;
		DownloadListHandle	hDownload = gAdDownloadList;

		gAdDownloadList = (*gAdDownloadList)->next;	//	Take ad off queue
		*sURL=strlen(LDRef(url));
		BlockMoveData(*url,sURL+1,*sURL);
		GetCacheSpec(sURL,&spec,true);
		FindTemporaryFolder(Root.vRef,Root.dirId,&spec.parID,&spec.vRefNum);
		if (TestWriteToAdFolder() ||
				DownloadURL(*url,&spec,(long)hDownload,FinishedAdDownload,&reference,nil))
		{
			ZapHandle(hDownload);
			ZapHandle(url);
			(*gPlayState)->adsNotFetched++;
		}
		else
		{
			gAdFetchCount++;
			gDownloadCount++;
		}
	}
}

/************************************************************************
 * TestWriteToAdFolder - is it ok to write to the ad folder?
 ************************************************************************/
OSErr TestWriteToAdFolder(void)
{
	FSSpec spec;
	OSErr err, deleteErr;
	Boolean can;
	
	SubFolderSpec(AD_FOLDER_NAME,&spec);
	PCopy(spec.name,"\ptest.gif");
	if (err=FSpCreate(&spec,'GIF ','GIF ',smSystemScript))
	if (err!=dupFNErr)
		return err;
	err = CanWrite(&spec,&can);
	deleteErr = FSpDelete(&spec);
	if (deleteErr && !err) err = deleteErr;
	
	return err;
}

/************************************************************************
 * AdCheckingMail - checking mail, user is online, need to fetch ads and/or playlist?
 ************************************************************************/
void AdCheckingMail(void)
{
	RollGlobalFacetime(); // record facetime now
	
	NoAdsRec.checkedMailToday = true;
	
	SavePlayStatus();

	if (AdWin && (gPlayListDownloadFail || !gPlayListQueue))
		//	Haven't been able to get a playlist yet
		RequestPlaylist();
	else if (gPlayState && !gAdFetchCount && (*gPlayState)->adsNotFetched)
		//	There are ads that we haven't been able to fetch yet
		PreFetchAds();
	else if (gPlayState
					 && TotalFaceTimeLeft()<(*gPlayState)->faceTimeQuota/2		// Running low on ads
					 && GMTDateTime()-gPlayListFetchGMT > kMinRetryInterval)	// But don't check too often
	{
		RequestPlaylist();
	}
}

/************************************************************************
 * RollGlobalFacetime - record facetime so far today and start recording again
 ************************************************************************/
void RollGlobalFacetime(void)
{
	long time;
	
	if (!gGlobalFaceMeasure)
	if (gGlobalFaceMeasure = NewFaceMeasure())
		FaceMeasureBegin(gGlobalFaceMeasure);
	
	if (gGlobalFaceMeasure)
	{
		if (FaceMeasureReport(gGlobalFaceMeasure,&time,nil,nil,nil)) return;
		if (gPlayState && time)
		{
			(*gPlayState)->faceTimeToday += time;
			(*gPlayState)->dirty = true;
		}
#ifdef DEBUG
		ComposeLogS(LOG_PLIST,nil,"\pFaceTimeRoll %ds %dm",time,gPlayState ? (*gPlayState)->faceTimeToday/60:-1);
#endif
		FaceMeasureReset(gGlobalFaceMeasure);
	}
}

/************************************************************************
 * PastryIsStale - is the pastry stale?
 ************************************************************************/
OSErr PastryIsStale(Handle pastry)
{
	UPtr spot, end;
	short count=0;
	
	// non-ascii chars?
	if (AnyFunny(pastry,0)) return 1;
	
	// Unbalanced quotes?
	end = *pastry + GetHandleSize(pastry);
	for (spot=*pastry;spot<end;spot++)
		if (*spot=='"') count++;
	if (count%2) return 2;
	
	return 0;
}

/************************************************************************
 * AdCheckingMail - checking mail, user is online, need to fetch ads and/or playlist?
 ************************************************************************/
static void ParseAdId(StringPtr sToken, AdId *pAdId)
{
	UPtr spot;
	Str32 sNum;

	spot = sToken+1;
	PToken(sToken,sNum,&spot,".");
	StringToNum(sNum,&pAdId->server);
	PToken(sToken,sNum,&spot,".");
	StringToNum(sNum,&pAdId->ad);
}

/************************************************************************
 * ShowBlackTimeImage - display image between ads
 ************************************************************************/
static void ShowBlackTimeImage(void)
{
	WindowPtr	AdWinWP = GetMyWindowWindowPtr (AdWin);
	PicHandle pic = GetResource_('PICT',GRAY_LOGO_PICT);
	Rect rPic,rWin;
	
	if (pic && AdWinWP)
	{
		HNoPurge_(pic);
		SetPort_(GetWindowPort(AdWinWP));
		rPic = (*pic)->picFrame;
		rWin = AdWin->contR;
		CenterRectIn(&rPic,&rWin);
		if (rPic.top > 0 || rPic.left > 0)
		{
			//	Pic is too small for window. Erase first.
			EraseRect(&rWin);		
		}
		DrawPicture(pic,&rPic);
		HPurge((Handle)pic);
	}
}

/************************************************************************
 * AddStringToHandle - add a string to a handle
 ************************************************************************/
static OSErr AddStringToHandle(StrOffset *offset,AccuPtr a,StringPtr s)
{
	*offset = a->offset;
	return AccuAddPtr(a,s,(*s)+1);
}

/************************************************************************
 * GetPlayListString - get a string from a playlist
 ************************************************************************/
static StringPtr GetPlayListString(PlayListHandle hPlayList,StrOffset offset,StringPtr s)
{
	if (hPlayList && offset)
		PCopy(s,(*(Handle)hPlayList)+(*hPlayList)->strOffset + offset);
	else
		*s = 0;
	return s;
}

/************************************************************************
 * GetPlayListURL - get a URL string from a playlist
 *			Returns a handle the caller must dispose of
 ************************************************************************/
static Handle GetPlayListURL(PlayListHandle hPlayList,StrOffset offset)
{
	Handle	hURL = nil;
	char	*pURL;
	
	if (hPlayList && offset)
	{
		LDRef(hPlayList);
		pURL = (*(Handle)hPlayList)+(*hPlayList)->strOffset + offset;
		hURL = NuDHTempOK(pURL, strlen(pURL)+1);
		UL(hPlayList);
	}
	return hURL;
}

/************************************************************************
 * GetPlayListURLString - get a URL as a p-string from a playlist
 ************************************************************************/
static StringPtr GetPlayListURLString(PlayListHandle hPlayList,StrOffset offset,StringPtr s)
{
	if (hPlayList && offset)
	{
		Ptr	dest;
		uLong	len;
		
		dest = *(Handle)hPlayList;
		dest += (*hPlayList)->strOffset + offset;
		len=strlen(dest);
		*s=min(len,255);
		BlockMoveData(dest,s+1,*s);
	}				
	else
		*s = 0;
	return s;
}

/************************************************************************
 * Flush - flush a playlist or ad
 ************************************************************************/
static void Flush(TokenInfo	*pInfo)
{
	enum { kIsPlayList=1,kIsEntry,kIsProfileID } what;
	Str255	s,sId;
	short	tokenIdx;
	
	what = 0;
	*sId = 0;
	while(tokenIdx = GetAttribute(pInfo,s))
	{
		switch (tokenIdx)
		{
			case kEntityTkn:
				if (StringSame(PlayListKeywords[kPlayListTkn],s))
					what = kIsPlayList;
				else if (StringSame(PlayListKeywords[kEntryTkn],s))
					what = kIsEntry;
				else if (StringSame(PlayListKeywords[kUserProfileTkn],s))
					what = kIsProfileID;
				break;
			case kIdTkn:
				PCopy(sId,s);
				break;
		}
		if (what && (*sId || what==kIsProfileID))
		{
			if (what==kIsPlayList)
			{
				//	Flush playlist
				PlayListHandle	hPlayList;
								
				if (hPlayList = FindPlayList(Hash(sId)))
					REMOVE_AND_DESTROY(hPlayList);
			}
			else if (what==kIsProfileID)
			{
				SetProfileID("");
			}
			else
			{
				//	Flush entry
				AdId	adId;
				
				ParseAdId(sId,&adId);
				DeleteAd(adId);
			}
			//	Set up to get some more. I think this shouldn't happen
			what = 0;
			*sId = 0;				
		}
	}
}

/************************************************************************
 * SameAdId - are the ad id's the same?
 ************************************************************************/
Boolean SameAd(AdId *pAd1,AdId *pAd2)
{
	return pAd1->server == pAd2->server && pAd1->ad == pAd2->ad;
}

/************************************************************************
 * AdWinNeedsNetwork - do we currently need the network connection?
 ************************************************************************/
Boolean AdWinNeedsNetwork(void)
{
	return (gAdDownloadList || gPLDownloadInProgress || gDownloadCount);
}


/************************************************************************
 * PrunePlayState - remove old ads from play state handle
 ************************************************************************/
static void PrunePlayState(Handle hKeepAdList)
{
	short	i,delCount = 0;
	
	for(i=0;i<(*gPlayState)->numAds;i++)
	{
		if ((*hKeepAdList)[i])
		{
			//	Keep this one
			if (delCount)
				//	Need to move it
				(*gPlayState)->stateAds[i-delCount] = (*gPlayState)->stateAds[i];
		}
		else
			//	Delete this one
			delCount++;
	}

	if (delCount)
	{
		//	We deleted some. Adjust play state handle
		(*gPlayState)->numAds -= delCount;
		SetHandleSize((Handle)gPlayState,GetHandleSize((Handle)gPlayState)-sizeof(AdState)*delCount);
		(*gPlayState)->dirty = true;
	}
}

/************************************************************************
 * MakeAdIconFromFile - create an ad icon from a file
 *
 *	32x32 and 16x16 icons are stored in the file in this format:
	+----------------------+-----------+
	|                      |           |
	|                      |16x16 image|
	|      32x32 image     |           |
	|                      |-----------+
	|                      |
	|                      |
	+----------------------+
 ************************************************************************/
static void MakeAdIconFromFile(FSSpecPtr spec, Handle iconSuite)
{
	OSErr err;
	GraphicsImportComponent	importer;
	GWorldPtr	gWorld = nil;
	Rect r;
	Handle	hIcon;
		
	// figure out which importer can open the ad picture file ...
	if (!GetGraphicsImporterForFile(spec, &importer))
	{
		// Create an offscreen GWorld to do the deed in ...
		SetRect(&r,0,0,48,32);
		err = NewGWorld(&gWorld, 8, &r, nil, nil, useTempMem);	//	Try temp memory first
		if (err) err = NewGWorld(&gWorld, 8, &r, nil, nil, nil);	//	Failed, use application heap
		if (!err)
		{	
			//	Draw into offscreen world
			RGBColor	color,*transparentColor=nil;

			if (GetPNGTransColor(importer,spec,&color))
				transparentColor = &color;	//	Use PNG transparent color when setting up mask
			PushGWorld();
			SetGWorld(gWorld,nil);
			if (!GraphicsImportSetGWorld(importer, gWorld, nil))
			if (!GraphicsImportDraw(importer))
			{
				SetRect(&r,32,0,48,16);
				if (hIcon = MakeIcon(gWorld,&r,8,16)) AddIconToSuite(hIcon,iconSuite,kSmall8BitData);
				if (hIcon = MakeICN_pound(gWorld,&r,16,transparentColor)) AddIconToSuite(hIcon,iconSuite,kSmall1BitMask);
				SetRect(&r,0,0,32,32);
				if (hIcon = MakeIcon(gWorld,&r,8,32)) AddIconToSuite(hIcon,iconSuite,kLarge8BitData);
				if (hIcon = MakeICN_pound(gWorld,&r,32,transparentColor)) AddIconToSuite(hIcon,iconSuite,kLarge1BitMask);
			}
			PopGWorld();
			DisposeGWorld(gWorld);
		}		
		CloseComponent(importer);
	}
}

/************************************************************************
 * SetupSponsorAd - setup sponsor ad
 ************************************************************************/
void SetupSponsorAd(void)
{
	AdInfoPtr	pAd;
	PlayListHandle hPlayList;
	Str255	sURL;
	FSSpec	spec;
	
	if (!IsPayMode()	//	Free and adware only
		&& HaveQuickTime(0x0300))	//	In free mode, QuickTime is not required thus may not be installed
	{
		if (pAd = FindAdType(kIsSponsor,nil,&hPlayList))
		{
			if (!gSponsorAdPic || !SameAd(&gSponsorAdId,&pAd->adId))
			{		
				WindowPtr 	winWP;
				MyWindowPtr	win;
				GraphicsImportComponent	importer;
				StrOffset	srcURL = pAd->srcURL;

				gSponsorAdId = 	pAd->adId;
				GetPlayListURLString(hPlayList,srcURL,sURL);
				GetCacheSpec(sURL,&spec,true);
				ZapSponsorAd();
				if (!FSpExists(&spec))
				{
					short	oldResFile = CurResFile();	//	Graphic importing changes curresfile

					if (!CheckAd(gSponsorAdId,nil,&spec))
					if (!GetGraphicsImporterForFile(&spec,&importer))
					{
						SetupPNGTransparency(importer,&spec);
						GraphicsImportGetAsPicture(importer,&gSponsorAdPic);
						CloseComponent(importer);
					}
					if (!gSponsorAdPic) Zero(gSponsorAdId);
					UseResFile(oldResFile);
				}
				else
				{
					//	Need to download this sponsor ad
					Handle	url;

					if (url = GetPlayListURL(hPlayList,srcURL))
					{
						if (!QueueAdDownload(url,gSponsorAdId,kIsSponsor))
							ZapHandle(url);	//	Error, didn't queue, is duplicate
					}
				}
					
				//	Set up windows to display sponsor ad
				//	Telling it to resize will do
				for (winWP = FrontWindow (); winWP; winWP = GetNextWindow (winWP))
					if (IsKnownWindowMyWindow(winWP))
						if (win = GetWindowMyWindowPtr (winWP))
							if (win->showsSponsorAd)
								MyWindowDidResize(win,nil);			
			}
		}
		else
			//	Make sure we don't have a sponsor ad anymore
			ZapSponsorAd();
	}
}

/************************************************************************
 * ClickSponsorAd - process click if it's in sponsor ad
 ************************************************************************/
Boolean ClickSponsorAd(MyWindowPtr win,EventRecord *event,Point pt)
{
	if (win->showsSponsorAd && win->sponsorAdExists && PtInRect(pt,&win->sponsorAdRect))
	{
		AdUserClick(gSponsorAdId);
		return true;
	}
	return false;
}

/************************************************************************
 * DrawSponsorAd - draw sponsor ad if there is one
 ************************************************************************/
void DrawSponsorAd(MyWindowPtr win)
{
	if (gSponsorAdPic && win->showsSponsorAd && win->sponsorAdExists)
		DrawPicture(gSponsorAdPic,&win->sponsorAdRect);
}

/************************************************************************
 * ZapSponsorAd - get rid of sponsor ad if there is one
 ************************************************************************/
static void ZapSponsorAd(void)
{
	if (gSponsorAdPic)
	{
		//	Get rid of old ad
		KillPicture(gSponsorAdPic);
		gSponsorAdPic = nil;
	}		
}

/************************************************************************
 * ResetAdSchedule - start from top on ad scheduling
 ************************************************************************/
static void ResetAdSchedule(void)
{
	(*gPlayState)->curAd.server = 0;
	(*gPlayState)->curAd.ad = 0;
	(*gPlayState)->dirty = true;
}


/************************************************************************
 * SetupWinSponsorInfo - make sure this window had sponsor ad info if it needs it
 ************************************************************************/
void SetupWinSponsorInfo(MyWindowPtr win)
{
	if (win->showsSponsorAd)
	{
		if (gSponsorAdPic)
		{
			Rect	rPortRect;
			CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
			Rect	rSponsor = (*gSponsorAdPic)->picFrame;
			Rect	rWin = *GetPortBounds(winPort,&rPortRect);
				
			OffsetRect(&rSponsor,rWin.right-RectWi(rSponsor)-kSponsorBorderMargin-GROW_SIZE-rSponsor.left,
				rWin.bottom-RectHi(rSponsor)-kSponsorBorderMargin-rSponsor.top);
			win->sponsorAdRect = rSponsor;
			win->sponsorAdExists = true;
		}
		else
			win->sponsorAdExists = false;
	}
}


/************************************************************************
 * MakeAdIcon - create a toolbar ad icon from URL
 ************************************************************************/
static void MakeAdIcon(PlayListHandle hPlayList,StrOffset srcURL,AdId adId,Handle iconSuite,Boolean downloadOK)
{
	FSSpec	spec;
	Str255	s;
	
	GetPlayListURLString(hPlayList,srcURL,s);
	GetCacheSpec(s,&spec,true);
	if (!FSpExists(&spec) && !CheckAd(adId,nil,&spec))
		MakeAdIconFromFile(&spec,iconSuite);
	else if (downloadOK)
	{
		//	Need to download icon
		Handle	url;
		
		//	Put into c-string handle
		if (url = NuDHTempOK(s+1,(*s)+1))
		{
			(*url)[*s]=0;	//	null-terminate
			if (!QueueAdDownload(url,adId,kIsButton))
				ZapHandle(url);	//	Error, didn't queue, is duplicate
		}
	}
}

/************************************************************************
 * SetTBAdIcon - set this toolbar ad icon that just finished downloading
 ************************************************************************/
static void SetTBAdIcon(AdId adId)
{
	AdInfoPtr	pAd;
	Handle iconSuite = nil;
	short		i;
	TBAdInfo	*pAdInfo;
	PlayListHandle	hPlayList;
	
	if (!NewIconSuite(&iconSuite))
	{
		if (pAd = FindAd(adId,nil,&hPlayList))
		{
			MakeAdIcon(hPlayList,pAd->srcURL,adId,iconSuite,false);
			TBUpdateAdButtonIcon(adId,iconSuite);
		}
	}

	if (gTBAds)
	{
		for (pAdInfo=*gTBAds,i=HandleCount(gTBAds);i--;pAdInfo++)
			if (SameAd(&pAdInfo->adId,&adId))
			{
				pAdInfo->iconSuite = iconSuite;
				break;
			}
	}
}

/************************************************************************
 * AdGetTBIcon - get icons for toolbar ad
 ************************************************************************/
Handle AdGetTBIcon(AdId adId)
{
	AdInfoPtr	pAd;
	PlayListHandle hPlayList;
	Handle		iconSuite = nil;

	LoadPlaylists();	//	Ad window may not be initialized yet
	
	if (pAd = FindAd(adId,nil,&hPlayList))
	{
		StrOffset	srcURL = pAd->srcURL;
		if (!NewIconSuite(&iconSuite))
			MakeAdIcon(hPlayList,srcURL,adId,iconSuite,true);
	}
	return iconSuite;
}

/************************************************************************
 * SetupToolbarAds - set up ad toolbar ads
 ************************************************************************/
static void SetupToolbarAds(void)
{
	Accumulator	a;
	PlayListHandle	hPlayList;
	AdInfoPtr	pAd;
	short	adIdx;
	TBAdInfo	adInfo;
	Str255		s;
	short		i;
	TBAdInfo	*pAdInfo;
	
	if (gTBAds)
	{
		//	Add to existing list
		AccuInitWithHandle(&a,(Handle)gTBAds);
		for (pAdInfo=*gTBAds,i=a.offset/sizeof(**gTBAds);i--;pAdInfo++)
			pAdInfo->deleted = true;
	}
	else
	{
		//	New list of toolbar ads
		if (AccuInit(&a)) return;
		gTBAds = (TBAdHandle)a.data;
	}
	
	if (IsAdwareMode()) // toolbar ads in Light?  I don't think so.  (!IsPayMode())
	{
		for(adIdx=0,hPlayList=gPlayListQueue;pAd=FindAdTypeLo(kIsButton,adIdx,hPlayList,&adIdx,&hPlayList);adIdx++)
		{
			Boolean		found;

			//	Do we already have this ad?
			found = false;
			for (pAdInfo=*gTBAds,i=a.offset/sizeof(**gTBAds);i--;pAdInfo++)
			{
				if (SameAd(&pAdInfo->adId,&pAd->adId))
				{
					found = true;
					pAdInfo->deleted = false;
					break;
				}
			}

			if (!found)
			{
				//	Add new one to list
				StrOffset	srcURL = pAd->srcURL;
				adInfo.adId = pAd->adId;
				GetPlayListString(hPlayList,pAd->title,s);
				PStrCopy(adInfo.title,s,sizeof(adInfo.title));
				if (!NewIconSuite(&adInfo.iconSuite))
					MakeAdIcon(hPlayList,srcURL,adInfo.adId,adInfo.iconSuite,true);
				adInfo.deleted = false;
				AccuAddPtr(&a,&adInfo,sizeof(adInfo));
			}
		}
	}
	else
	{
		a.offset = 0;	//	Remove ads if in pay mode
	}
	
	AccuTrim(&a);	
	TBAddAdButtons(gTBAds);
	if (!GetHandleSize_(gTBAds)) ZapHandle(gTBAds);
}

/************************************************************************
 * AdDeleteButton - a toolbar ad has been deleted by user
 ************************************************************************/
void AdDeleteButton(AdId adId)
{
	AdStatePtr	pAdState;

	if (pAdState = FindAdState(adId,nil))
	{
		pAdState->deleted = true;
		(*gPlayState)->dirty = true;
	}
}

/************************************************************************
 * AdValidate - validate ad/playlist with MD5
 *	This function is intentionally a bit messy to try and confuse any
 *	hacker looking for our secret. It also has a non-descriptive function
 *  name for the same reason.
 ************************************************************************/
OSErr SetupForDisplay(Ptr data,long dataLen,unsigned char checksum[kCheckSumSize],Boolean check)
{
	MD5_CTX md5;
	static Secret0 = 0x15028A81;
	long	*in = (long*)&md5.in[0];
	OSErr result = noErr;
//	char secret[] = "\pJello be dim.";
	
	MD5Init(&md5);

//	MD5Update(&md5,secret+1,*secret);
//	The following does the same as the previous line but
//	without revealing the secret string
	in[3] = Secret3^0x4b99fe13;
	md5.i[0] = 104;
	in[1] = Secret1^0x0747db4b;
	md5.i[1] = 0;
	md5.buf[0] = 0x67452301;
	md5.buf[1] = 0xEFCDAB89;
	in[0] = Secret0^0x5f67e6ed;
	md5.buf[2] = 0x98BADCFE;
	md5.buf[3] = 0x10325476;
	in[2] = Secret2^0x8361e9bf;
	
	MD5Update(&md5,data,dataLen);
	MD5Final(&md5);
	
	if (check)
	{
		//	Check this checksum
		if (memcmp(md5.digest,checksum,kCheckSumSize))
			result = (*gPlayState)->errCode = badCksmErr;	//	Validation failure!
	}
	else
	{
		//	Return this checksum
		BMD(md5.digest,checksum,kCheckSumSize);
	}

	return result;
}

/************************************************************************
 * InitAdPte - setup pte for ad window
 ************************************************************************/
static void InitAdPte(void)
{
	CGrafPtr	AdWinPort = GetMyWindowCGrafPtr (AdWin);
	PETEDocInitInfo	initInfo;
	RGBColor	bkColor;
				
	DefaultPII(AdWin,false,0,&initInfo);
	initInfo.inParaInfo.startMargin = 0;
	initInfo.inParaInfo.endMargin = WindowWi(GetMyWindowWindowPtr(AdWin))+1;
	PeteCreate(AdWin,&ADpte,0,&initInfo);
	CleanPII(&initInfo);
	PeteLock(ADpte,0,0x7fffffff,peModLock|peSelectLock);
    PETESetDefaultBGColor(PETE,ADpte,GetPortBackColor(AdWinPort,&bkColor));
	PeteDidResize(ADpte,&AdWin->contR);
}

/************************************************************************
 * GetAdFolderSpec - get spec for ad folder, creating if necessary
 ************************************************************************/
OSErr GetAdFolderSpec(FSSpecPtr spec)
{
	Str255	s;
	CInfoPBRec	hfi;
	long junk;
	OSErr	err=noErr;
	
	FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(s,AD_FOLDER_NAME),spec);
	IsAlias(spec,spec);
	if (FSpGetHFileInfo(spec,&hfi))
		err = FSpDirCreate(spec,smSystemScript,&junk);
	else
	{
		//	Make sure user hasn't locked folder or replaced it with a file
		if (hfi.hFileInfo.ioFlAttrib&0x01)
			//	It was locked. Unlock it.
			err = FSpRstFLock(spec);
		if (!(hfi.hFileInfo.ioFlAttrib&0x10))
		{
			//	Not a folder. Delete it and create folder.
			if (!(err = FSpDelete(spec)))
				err = FSpDirCreate(spec,smSystemScript,&junk);
		}
	}
	return err;
}

/************************************************************************
 * GetLanguageCode - get language code in "xx-xx" format
 ************************************************************************/
UPtr GetLanguageCode(UPtr sResult)
{
	Str255	s;
	short	index;
	
	index = (short)GetScriptManagerVariable(smRegionCode)+1;
	if (index >= 100) index++;	//	1-99 and 101-109
	GetRString(s,LanguageIdStrn+index);
	PStrCopy(sResult,s,6);
	return sResult;
}

/************************************************************************
 * HandleNag - tell Eudora the user needs to be nagged
 ************************************************************************/
OSErr HandleNag(long level,PStr text)
{
	if (level==0)
	{
		ZapResource(NAG_REQUEST_TYPE,PLNAG_LEVEL0_DLOG);
		AddPResource(text+1,*text,NAG_REQUEST_TYPE,PLNAG_LEVEL0_DLOG,"");
	}
	if (level==1)
	{
		ZapResource(NAG_REQUEST_TYPE,PLNAG_LEVEL1_DLOG);
		AddPResource(text+1,*text,NAG_REQUEST_TYPE,PLNAG_LEVEL1_DLOG,"");
	}
	if (level==2)
	{
		ZapResource(NAG_REQUEST_TYPE,NAG_INTRO_DLOG);
		AddPResource(text+1,*text,NAG_REQUEST_TYPE,NAG_INTRO_DLOG,"");
	}
	PlaylistNagCount++;
	return noErr;
}

/************************************************************************
 * HandleUserProfile - store a profile given to us by the playlist server
 ************************************************************************/
OSErr HandleUserProfile(PStr text)
{
	return SetProfileID(text);
}

/************************************************************************
 * HandleClientMode - switch modes based on playlist server orders;
 * or, actually, tell the main loop to do so
 ************************************************************************/
OSErr HandleClientMode(PStr text)
{
	long newMode=-1;
	StringToNum(text,&newMode);
	if (newMode>=0) NewClientModePlusOne = newMode+1;
	return noErr;
}

/************************************************************************
 * ForecPlaylistRequest - force a playlist request
 ************************************************************************/
void ForcePlaylistRequest(void)
{
	RequestPlaylist();
}

/************************************************************************
 * GetSponsorAdInfo - get some information about the sponsor ad
 ************************************************************************/
void GetSponsorAdTitle(UPtr sTitle)
{
	AdInfoPtr	pAd;
	PlayListHandle hPlayList;

	if (pAd = FindAdType(kIsSponsor,nil,&hPlayList))
		GetPlayListString(hPlayList,pAd->title,sTitle);
	else
		*sTitle = 0;	
}

#endif
