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

#include "linkmng.h"
#define FILE_NUM 128

/* Copyright (c) 1999 by QUALCOMM Incorporated */

/************************************************************************
 *  routines to manage multiple link history lists
 ************************************************************************/
 
#include "linkmng.h"
#define FILE_NUM 128

#define LINK_HISTORY_VERSION 4		// increment this when changing the HistoryStruct

#define LINK_TOC_TYPE			'LToc'
#define LINK_RESID				130

// until ListView is taught about items with names > 31 characters
typedef Str31 URLNameStr;


//
// Data structures for link management
//

/* LinkTypeEnum - types of links stored in the link history */
typedef enum {
	ltAd=0,
	ltHttp,
	ltFtp,
	ltMail,
	ltDirectoryServices,
	ltSetting,
	ltFile,
	LinkTypeLimit
} LinkTypeEnum;

/* LinkLabelEnum - labels for links */
typedef enum {
	llRemindMe=1,
	llBookmarked,
	llAttempted,
	llNone,
	llNotDisplayed,
	LinkLabelLimit
} LinkLabelEnum;

/* Structure for a given history entry, version 1 */
typedef struct
{
	short 			version;				/* the version of the link history entry */
	long			hashName; 				/* hash value on history name */
	URLNameStr		name;					/* the name of the url */
	unsigned long	cacheSeconds;			/* date this url was last clicked */
	LinkTypeEnum	type;					/* the type of url this is */
	long			dirty:1;				/* has the history entry been modified */
	long			deleted:1;				/* has the history entry been deleted */
	long			thumb:1;				/* does this history entry have a thumbnail */
	long			label:4;				/* label */
	long 			incompleteAd:1;			/* this entry is an ad that's not ready yo be displayed in the history */				
	long			remind:1;				/* remind the user to visit this link */
	long			spare:23;				/* leftovers */
	long			urlOffset;				/* offset in file where the real url is at */
	long			imageOffset;			/* offset in file where the location of the icon is at */
	Handle			hUrl;					/* Handle to URL this entry will link to */
	AdId			adId;					/* ID of Ad this entry refers to */
} HistoryStruct, *HistoryStructPtr, **HistoryStructHandle;

/* Structure to keep track of an infividual history file */
typedef struct HistoryDStruct
{
	FSSpec spec;							/* the history file */
	HistoryStructHandle theData;			/* the toc */
	Boolean ro;								/* read only */
	Boolean dirty;							/* is the history file dirty? */
} HistoryDesc, *HistoryDPtr, **HistoryDHandle;

/* Structure just enough info for the history window.  Will sort this puppy. */
typedef struct
{
	URLNameStr		name;					/* the name of the url */
	unsigned long	cacheSeconds;			/* date this url was last clicked */
	LinkTypeEnum	type;					/* the type of url this is */
	VLNodeID		nodeId;					/* id of this node, calculated from which and index */
	LinkLabelEnum	label;					/* the label of this history entry */
} ShortHistoryStruct, *ShortHistoryStructPtr, **ShortHistoryStructHandle;

/* Structure to maintain a cache of preview icon handles */
typedef struct LHPIconCacheStruct
{
	Handle theIcon;
	AdId adId;
	ICacheHandle next;
} LHPIconCacheStruct, *LHPIconCachePtr, **LHPIconCacheHandle;

//
// Globals for link management
//

// Global list of all known history files
HistoryDHandle gHistories = nil;	

// Global list of loaded preview icons
LHPIconCacheHandle gPreviewIcons = nil;

// Global spec pointing inside the Link History FOlder
FSSpec gLinkHistoryFolder;

// GLobal list of all preview icons

// Types of links we care about if following them fails with some error
LinkTypeEnum gLabelTheseLinks[] = {ltAd, ltHttp, ltFtp, ltMail, ltDirectoryServices};

//
// Some helpful #defines
//

#ifdef DEBUG
	#define COMPACT_THRESHHOLD 2
#else
	#define COMPACT_THRESHHOLD 10
#endif

#define AGE_INTERVAL 60*60*60	// age once an hour

#define MAIN_HISTORY_FILE 0
#define DEFAULT_LINK_TYPE_ICON HTTP_LINK_TYPE_ICON
#define This (*gHistories)[which]
#define NHistoryFiles (gHistories ? GetHandleSize_(gHistories)/sizeof(HistoryDesc) : 0)

//
// Link management prototypes
//
 
OSErr WriteHistTOC(short which);
Handle GetHistoryData(short which,short index,Boolean readFromDisk);
OSErr ReadHistTOC(short which);
static void ZapHistoryFile(short which, Boolean destroy);
OSErr RegenerateLinkHistory(short which, Boolean rebuild);
long HistMatchFound(long hashName,Handle theUrl,short which);
void ReadHistFileList(FSSpec *pSpec,Boolean reread);
OSErr AddHistoryToTOC(short which,UPtr name,long hashName,LinkTypeEnum type, LinkLabelEnum label,Boolean thumb,Handle url,AdId adId);
OSErr SaveIndHistoryFile(short which);
void DeleteHistEntryFromTOC(short which, short index);
Boolean TimeToCompactTOC(short which);
OSErr CompactHistTOC(short which);
short LinkTypeToIconID(LinkTypeEnum type);
VLNodeID EntryToNodeId(short which, short index);
void NodeIdToEntry(VLNodeID id, short *which, short *index);
OSErr AddURLToHistory(short which, PStr url, PStr name, OSErr urlOpenErr);
short MenuItemNameToIconID(short menuID, short item);
LinkTypeEnum LinkType(ProtocolEnum protocol, PStr proto);
void LinkUpdateCacheDate(short which, short index);
short HistoryCount(short which);
Boolean LocateAdInHistories(AdId adId, short *which, short *index);
short AgeHistoryFile(short which);
void LabelToString(LinkLabelEnum label, Str255 dateStr);
Boolean LabelableLink(LinkTypeEnum linkType);
Boolean CorrectVersion(HistoryStructHandle theToc);
void UpdateLinkLabel(HistoryStructPtr entry, OSErr err);
LinkLabelEnum OpenErrToLabel(OSErr err);
Boolean InterestingProtocol(Str255 proto);
void MakeLinkName(Str255 host, Str255 query, URLNameStr urlName);

// Sorting
OSErr BuildListOfHistoriesForWindow(ShortHistoryStructHandle *histories, Boolean needsSort, LinkSortTypeEnum sortType);
void SortShortHistoryHandle(ShortHistoryStructHandle toSort, int (*compare)());
int HistTypeCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
int HistNameCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
int HistDateCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
int HistRemindCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);
void SwapHist(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2);

// Ad preview stuff
OSErr CreateIconFromAdGraphic(AdId adId, FSSpecPtr adGraphic);
void AdIdToName(AdId adId, URLNameStr name);
Boolean NameToAdId(URLNameStr name, AdId *ad);
OSErr DeleteAdGraphic(AdId adId);
OSErr IconFromAd(FSSpecPtr iconSpec, FSSpecPtr adSpec);
OSErr MakeLHIconFile(GWorldPtr gWorld, Rect *pRect, FSSpecPtr iconSpec, GraphicsImportComponent importer, FSSpecPtr sourceSpec);
void PurgeLinkHistoryPreviewOrphans(void);

// Icon cache management
void AddIconToPVICache(Handle theIcon, AdId adId);
LHPIconCacheHandle FindPVICache(AdId adId);
void RemoveIconFromPVICache(AdId adId);
void RemovePVIFromPVICache(LHPIconCacheHandle *toRemove);

// Icon building routines, mostly taken from MakeIcon
OSErr SetUpPixMap( short depth, Rect *bounds,CTabHandle colors,PixMapHandle aPixMap);
void TearDownPixMap(PixMapHandle pix);
Handle MakeIconMask(GWorldPtr srcGWorld, Rect *srcRect, short iconSize);
Handle MakeIconLo(GWorldPtr srcGWorld, Rect *srcRect, short dstDepth, short iconSize, RGBColor *transColor);
void FreeBitMap(BitMap *Bits);
void CalcOffScreen(register Rect *frame,register long *needed, register short *rows);
void NewBitMap(Rect *frame,BitMap *theMap);
void NewMaskedBitMap(BitMap	*srcBits, BitMap *maskBits, Rect *srcRect);

// Nickname routines we've assimilated
extern OSErr KillNickTOC(FSSpecPtr spec);
extern Boolean NeatenLine(UPtr line, long *len);

/************************************************************************
 * AddURLToMainHistory - add a URL to the main history file
 ************************************************************************/
OSErr AddURLToMainHistory(PStr url, PStr name, OSErr urlOpenErr)
{
	return (AddURLToHistory(MAIN_HISTORY_FILE, url, name, urlOpenErr));
}

/************************************************************************
 * AddURLToMainHistory - add a URL to the main history file
 ************************************************************************/
OSErr AddURLToHistory(short which, PStr url, PStr name, OSErr urlOpenErr)
{
	OSErr err = noErr;
	ProtocolEnum protocol;
	Str255 proto,host,query;
	long hashName;
	Handle hUrl = nil;
	URLNameStr urlName;
	LinkLabelEnum linkLabel = llNone;
	LinkTypeEnum linkType;
	short index;
	
	// don't add the URL if it wasn't parsable
	if (urlOpenErr == 1) return (urlOpenErr);	
	
	// parse the URL.  See what it is.
	if (!(err=ParseURL(url,proto,host,query)))
	{
		// see if this is one of the protocols we should be keeping history entries for
		if (!InterestingProtocol(proto)) return (userCanceledErr);
		
		protocol = FindSTRNIndex(ProtocolStrn,proto);
		FixURLString(host);
		if (protocol!=proMail) FixURLString(query);
	
		//
		//	Figure out the history entry data
		//
		
		// make sure the name of the url contains something interesting
		if (name && name[0])
		{
			// use the name passed in
			PStrCopy(urlName, name, sizeof(urlName));
		}
		else
		{
			if (host[0]) 	
			{
				// make a name for this link entry out of the host and query portions of the URL itself	
				MakeLinkName(host, query, urlName);
			}
			else
			{
				// URL had no host.  Use the actual URL
				PStrCopy(urlName, url, sizeof(urlName));
			}
		}

		// The name of the history entry will be a hash of the url itself
		hashName = NickHashString(url);
		
		// What sort of link is this?
		linkType = LinkType(protocol, proto);
		
		// Was this link successfully launched?
		if (urlOpenErr != noErr)
		{
			// is this a link type we should label?
			if (LabelableLink(linkType))
			{
				// the link was attempted, but probably failed.
				linkLabel = OpenErrToLabel(urlOpenErr);
			}
		}
		
		// Turn the url into a handle
		hUrl = NuHTempOK(0);
		if (!hUrl) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError()));
		err = PtrPlusHand(url+1,hUrl,url[0]);
		if (err) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err));
		
		//
		//	Add the new history entry
		//
		
		// Make sure the history files are around somewhere
		err = GenHistoriesList();
		
		if (err == noErr)
		{
			// Is this entry already in the history file?
			if ((index=HistMatchFound(hashName, hUrl, which)) >= 0)
			{	
				// adjust the label of the link ...
				UpdateLinkLabel(&((*(*gHistories)[which].theData)[index]), urlOpenErr);
				
				// update the date in the TOC ...
				LinkUpdateCacheDate(which, index);
			}
			else
			{
				// If not, add the history to the history file
				AdId junk;
				
				junk.server = junk.ad = 0;
				err =  AddHistoryToTOC(which, urlName, hashName, linkType, linkLabel, false, hUrl, junk);
				if (err == noErr)
				{
					// save the history file
					err = SaveIndHistoryFile(which);
				}
			}
			
			// Update the Link History window ...
			LinkTickle();
		}
	}
	
	return (err);
}

/************************************************************************
 * MakeLinkName - build a name for a link history entry
 ************************************************************************/
void MakeLinkName(Str255 host, Str255 query, URLNameStr urlName)
{
	short linkNameLength = sizeof(URLNameStr);
	
	// initialize the name
	WriteZero(urlName, linkNameLength);
	
	// start with the host name
	PCopy(urlName,host);
	
	// more room?
	if (urlName[0] < linkNameLength-1)
	{
		// add a slash
		urlName[urlName[0]+1] = '/';
		urlName[0] += 1;
		
		// tack on the query
		if (query[0]) PSCat_C(urlName,query,linkNameLength);
	}
	
	// replace last character with elipsis, if we reached the maximum length
	if (urlName[0] == linkNameLength-1)
	{
		urlName[urlName[0]] = 'É';
	}
}

/************************************************************************
 * InterestingProtocol - see if this is a protocol worth remembering
 ************************************************************************/
Boolean InterestingProtocol(Str255 proto)
{
	Boolean remember = false;
	Str255 insterestingProtocols, token;
	UPtr spot;
	
	if (proto[0])
	{
		// read in the list of interesting protocols
		GetRString(insterestingProtocols,LINK_INTERESTING_PROTO);
		
		// see if proto is one of them
		for(spot=insterestingProtocols+1;PToken(insterestingProtocols,token,&spot,",") && !remember;)
		{
			if (StringSame(token, proto)) remember = true;
		}
	}
	
	return (remember);
}

/************************************************************************
 * UpdateLinkLabel - update the label of a link according to an error
 ************************************************************************/
void UpdateLinkLabel(HistoryStructPtr entry, OSErr err)
{	
	if (LabelableLink(entry->type) && (err!=noErr))
	{
		// adjust the label unless the user cancelled ...
		if (err != olaCancel) entry->label = OpenErrToLabel(err);
	 	
	 	// remind the user later if we should ...
	 	entry->remind = (err == oldaRemind);
	 	gNeedRemind |= (entry->remind!=0);
	}
	else entry->label = llNone;
}

/************************************************************************
 * OpenErrToLabel - given an error code, return the label we should use
 ************************************************************************/
LinkLabelEnum OpenErrToLabel(OSErr err)
{
	LinkLabelEnum result = llNone;
	
	if ((err!=noErr))
	{
	 	if (err == oldaBookmark) result = llBookmarked;
	 	else if (err == oldaRemind) result = llRemindMe;
	 	else result = llAttempted;
	}
	
	return (result);
}
			
/************************************************************************
 * RegenerateLinkHistory - make sure the history list is in memory
 ************************************************************************/
static OSErr RegenerateLinkHistory(short which, Boolean rebuild)
{
	OSErr err = noErr;
	UHandle hand;
	
	if (rebuild) ZapHistoryFile(which, true);
	
	if (!(*gHistories)[which].theData) // If handle for data doesn't exist, create it
	{
		hand = NuHTempOK(0L);
		if (!hand) err = WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError());
		(*gHistories)[which].theData = (HistoryStructHandle)hand;
	}
	else // Handle exists
	{
		if (!rebuild)
			return (noErr);
	}
	
	if (err != noErr || rebuild)
	{
		err = ReadHistTOC(which);
	}

	if (err)
		ZapHandle((*gHistories)[which].theData);

	return(err);
}

/************************************************************************
 * ZapHistoryFile - release memory for all histories in a file
 ************************************************************************/
static void ZapHistoryFile(short which, Boolean destroy)
{
	short	i;
	HistoryStructHandle history = gHistories ? (*gHistories)[which].theData : nil;

	if (history)
	{
		// throw away all the URL and image handles we have laying around
		for (i=0;i<HistoryCount(which);i++)
		{
			if ((*history)[i].hUrl)
			{
				ZapHandle((*history)[i].hUrl);
				(*history)[i].hUrl = nil;
			}
		}
		
		// if we're destroying, trash the actual history list as well
		if (destroy)
		{
			ZapHandle((*gHistories)[which].theData);
			(*gHistories)[which].theData = nil;
		}
	}
}

/************************************************************************
 * ZapHistoriesList - destroy the history file list, or make it smaller
 ************************************************************************/
void ZapHistoriesList(Boolean destroy)
{
	short which;
	short n = gHistories ? NHistoryFiles : 0;
	
	// clean up the inidividual history files
	for (which = 0; which < n; which++)
	{
		ZapHistoryFile(which, destroy);
	}
	
	// if we're destroying, zap the handle to all the histories as well.
	if (destroy) 
	{
		if (gHistories) ZapHandle(gHistories);
		gHistories = nil;
	}
}

/************************************************************************
 * GenHistoriesList - generate the history file list
 ************************************************************************/
OSErr GenHistoriesList(void)
{
	OSErr err = noErr;
	Str31 name;
	FSSpec folderSpec;
	HistoryDesc ad;
	
	Zero(ad);
	
	// do nothing if the history files have already been loaded.
	if (gHistories) return(noErr);
	
	/*
	 * allocate empty handle for new
	 */
	 
	if (!(gHistories=NuHandle(0L)))
	{
		WarnUser(MEM_ERR,err = MemError());
		return (err);
	}
	
	/*
	 * add the Eudora History file
	 */
	 
	FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(name,LINK_HISTORY_FILE),&ad.spec);
	if (PtrPlusHand_(&ad,gHistories,sizeof(ad))) DieWithError(MEM_ERR,MemError());
	RegenerateLinkHistory(MAIN_HISTORY_FILE, true);

	/*
	 * add any additional history files in the Link History Folder
	 */
	 
	ReadHistFileList(&folderSpec,false);

	return (err);
}

/************************************************************************
 * ReadHistFileList - find extra link history files
 ************************************************************************/
void ReadHistFileList(FSSpec *pSpec,Boolean reread)
{
	Str31 name;
	CInfoPBRec hfi;
	HistoryDesc ad;
	long dirId;
	short count = 1;
	
	/*
	 * create the folder if we need it ...
	 */
	if (SubFolderSpec(LINK_HISTORY_FOLDER,pSpec)!=noErr)
	{
		DirCreate(Root.vRef,Root.dirId,GetRString(name,LINK_HISTORY_FOLDER),&dirId);
		SimpleMakeFSSpec(pSpec->vRefNum,dirId,"\p",&gLinkHistoryFolder);
		return;		
	}
	else
		gLinkHistoryFolder = *pSpec;
	
	/*
	 * read in the history files ...
	 */
	
	Zero(ad);	
	hfi.hFileInfo.ioNamePtr = name;
	hfi.hFileInfo.ioFDirIndex = 0;
	while (!DirIterate(pSpec->vRefNum,pSpec->parID,&hfi))
	{
		if (hfi.hFileInfo.ioFlFndrInfo.fdType=='TEXT' && hfi.hFileInfo.ioFlFndrInfo.fdCreator==CREATOR)
		{
			SimpleMakeFSSpec(pSpec->vRefNum,pSpec->parID,name,&ad.spec);
			if (!CanWrite(&ad.spec,&ad.ro)) ad.ro = !ad.ro;	/* opposite sense! */
			if (PtrPlusHand_(&ad,gHistories,sizeof(ad))) DieWithError(MEM_ERR,MemError());
			
			// read it in
			RegenerateLinkHistory(count++, true);
		}
	}
}

/************************************************************************
 * AddHistoryToTOC - add a history entry to the TOC
 ************************************************************************/
OSErr AddHistoryToTOC(short which,UPtr name,long hashName,LinkTypeEnum type,LinkLabelEnum label,Boolean thumb,Handle url,AdId adId)
{
	long currHistCount;
	HistoryStructHandle histories = (*gHistories)[which].theData;
	OSErr err = noErr;
	HistoryStruct histInfo;
	 
	//
	//	Make sure the history list is around
	//
	
	if (RegenerateLinkHistory(which, false)!=noErr) return (err);

	//
	// Make room for the new history entry
	//
	
	HUnlock((Handle)histories);
	currHistCount = HistoryCount(which);
	if (currHistCount > 0)
	{
		SetHandleBig_((Handle)histories,(currHistCount + 1)*sizeof(HistoryStruct));
		if (err = MemError()) 
			return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err));

	}
	else
	{
		ZapHandle((*gHistories)[which].theData);
		histories = NuHTempOK(sizeof(HistoryStruct));
		if (!histories)
			return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError()));
		else
			(*gHistories)[which].theData = histories;
	}

	// 
	// Add the TOC entry
	//
	
	// Fill in the basic data
	WriteZero(&histInfo,sizeof(histInfo));
	histInfo.version = LINK_HISTORY_VERSION;
	histInfo.hashName = hashName;
	PSCopy(histInfo.name, name);
	histInfo.cacheSeconds = LocalDateTime();
	histInfo.type = type;
	histInfo.dirty = true;
	histInfo.deleted = false;
	histInfo.thumb = thumb;
	histInfo.label = label;
	histInfo.incompleteAd = ((type == ltAd) && !(url && *url && **url));
	histInfo.urlOffset = (-1L);
	histInfo.imageOffset = (-1L);
	histInfo.adId = adId;
	
	// now the potentially large url
	if (url && *url && **url)
	{		
		histInfo.hUrl = url;
	}
	
	//	Put hist info into the history TOC array
	(*(histories))[currHistCount] = histInfo;
	
	UL(histories);
	(*gHistories)[which].dirty = true;
	return(0);
}

/************************************************************************
 * SaveAllHistoryFiles - save all history files that need it
 ************************************************************************/
OSErr SaveAllHistoryFiles(void)
{
	OSErr err = noErr;
	short hFiles;
	
	for (hFiles = 0; hFiles < NHistoryFiles; hFiles++)
	{
		if ((*gHistories)[hFiles].dirty)
			err = err || SaveIndHistoryFile(hFiles);
	}
	
	return (err);
}

/************************************************************************
 * SaveIndHistoryFile - save an individual History file
 ************************************************************************/
OSErr SaveIndHistoryFile(short which)
{
	Str31 aliasCmd;
	int err;
	long bytes,offset;
	short refN=0;
	long i, count;
	FSSpec spec,tmpSpec;
	Boolean junk;
	Handle	tempHandle;

#ifdef	DEBUG
	if (InAThread()) return noErr;
#endif

	/*
	 * do we need to save it?
	 */
	if (!(*gHistories)[which].dirty) return(noErr);
		
	/*
	 * find the file
	 */
	spec = (*gHistories)[which].spec;
	if (err=FSpMyResolve(&spec,&junk))
	{
		FileSystemError(SAVE_LINK_HISTORY,spec.name,err);
		return(err);
	}
	
	/*
	 * make && open a temp file
	 */
	if (err=NewTempSpec(spec.vRefNum,spec.parID,nil,&tmpSpec))
		goto done;
	if (err=FSpCreate(&tmpSpec,CREATOR,MAILBOX_TYPE,smSystemScript))
		{FileSystemError(SAVE_LINK_HISTORY,tmpSpec.name,err); goto done;}
	if (err=FSpOpenDF(&tmpSpec,fsRdWrPerm,&refN))
		{FileSystemError(OPEN_LINK_HISTORY,tmpSpec.name,err); goto done;}
	
		
	count = HistoryCount(which); // Get history entry count

	/*
	 *	Write out the url
	 */
	 
	GetRString(aliasCmd,LINK_CMD);
	PCatC(aliasCmd,' ');
	HLock((Handle)(*gHistories)[which].theData);
	for (i=0;!err && i<count;i++)
	{
		CycleBalls();
		if ((*((*gHistories)[which].theData))[i].dirty) // Nickname has been modified...use in memory copy
			tempHandle = GetHistoryData(which,i,false);
		else // Nickname hasn't been modified, use on disk copy if necessary
			tempHandle = GetHistoryData(which,i,true);
		
		(*((*gHistories)[which].theData))[i].dirty = false;
		
		bytes = (tempHandle && *tempHandle && **tempHandle) ? GetHandleSize_(tempHandle) : 0;		
		
		if (bytes >0 && !(*((*gHistories)[which].theData))[i].deleted)
		{	
			GetFPos(refN,&offset);
			(*((*gHistories)[which].theData))[i].urlOffset = offset;
		}
		else
		{
			// (*gHistories)[which] entry is missing a URL.  If it's not an incomplete ad, remove it from the TOC, and skip it.
			if (!(*((*gHistories)[which].theData))[i].incompleteAd) (*((*gHistories)[which].theData))[i].deleted = true;
			continue;
		}
					
		if ((!(*((*gHistories)[which].theData))[i].deleted) && bytes > 0 && !(err = FSWriteP(refN,aliasCmd)))
		{
			HLock(tempHandle);
			if (!(err = AWrite(refN,&bytes,*tempHandle)))
			{
				bytes = 1;
				err = AWrite(refN,&bytes,"\015");
			}
			HUnlock(tempHandle);
		}
	}

	UL((*gHistories)[which].theData);

	GetFPos(refN,&bytes);
	SetEOF(refN,bytes);
	MyFSClose(refN); refN = 0;

	/* do the deed */
	if (!err) err = ExchangeAndDel(&tmpSpec,&spec);
	if (!err) (*gHistories)[which].dirty = False;

	WriteHistTOC(which);

done:
	if ((*gHistories)[which].theData) UL((*gHistories)[which].theData);
	if (refN) MyFSClose(refN);
	if (err) FSpDelete(&tmpSpec);
	return(err);
}

/**********************************************************************
 * WriteHistTOC - write the toc for a history file
 **********************************************************************/
OSErr WriteHistTOC(short which)
{
	uLong	fileModDate;
	FSSpec	spec = (*gHistories)[which].spec;
	OSErr	err = noErr;
	short refN;
	HistoryStructHandle theData;
	short oldResF = CurResFile();

#ifdef	DEBUG
	if (InAThread()) return noErr;
#endif

	if (TimeToCompactTOC(which)) CompactHistTOC(which);

	theData = DupHandle((*gHistories)[which].theData);
	if ((theData) && ((err=MemError())==noErr))
	{
		IsAlias(&spec,&spec);
		KillNickTOC(&spec);
		FSpCreateResFile(&spec,CREATOR,'TEXT',smSystemScript);
		fileModDate = AFSpGetMod(&spec);

		if (-1!=(refN=FSpOpenResFile(&spec,fsRdWrPerm)))
		{
			AddResource(theData,LINK_TOC_TYPE,LINK_RESID,"");
			err = ResError();
			if (!err)
			{
				UpdateResFile(refN);
				err = ResError();
			}
			CloseResFile(refN);
		}
		else err = ResError();

		if (!err) err = AFSpSetMod(&spec,fileModDate);
		
		UseResFile (oldResF);
	}
	
	return (err);
}

/************************************************************************
 *  GetHistoryData - Get the url or image of a given history
 ************************************************************************/
Handle GetHistoryData(short which,short index,Boolean readFromDisk)
{
	Handle	tempHandle;
	HistoryStructHandle histories = (*gHistories)[which].theData;
	FSSpec	spec;
	Boolean finished = false;
	int err;
	Str255 line;
	short type;
	Boolean exLine=False;
	long len;
	Handle	dataHandle;
	LineIOD lid;
	long	theOffset;
	Str31	theCmd;
	
	spec = (*gHistories)[which].spec;

	if (index < 0 || which < 0 || index >= HistoryCount(which) || (*histories)[index].deleted)
		return (nil);

	tempHandle = (*histories)[index].hUrl;

	if (!readFromDisk)
	{
		if ((*histories)[index].dirty)
			return (tempHandle);
			
		if (tempHandle !=nil && *tempHandle !=nil)
			return (tempHandle);
	}
			
	if (tempHandle != nil)
		ZapHandle(tempHandle);

	theOffset = (*histories)[index].urlOffset;
	GetRString(theCmd,LINK_CMD);

	if ( theOffset >= 0)
	{
		if (err=FSpOpenLine(&spec,fsRdPerm,&lid))
		{
			if (err !=fnfErr) FileSystemError(OPEN_LINK_HISTORY,spec.name,err);
			return (nil);
		}
		
		dataHandle = NuHTempOK(0L);
		if (!dataHandle)
		{
			WarnUser(LINK_HISTORY_GET_DATA_ERR,MemError());
			return (nil);

		}
		
		/*
		 * Offset is from the beginning of the line; add in length of the command and length of name
		 * and two spaces
		 */
		theOffset += *theCmd + 1;
		SeekLine(theOffset,&lid);
		type = GetLine(line,sizeof(line),&len,&lid);
		if (type==LINE_START)
		do
		{
			// process current line
			len = strlen(line);
			exLine = NeatenLine(line,&len);
			if (exLine && !issep(*line)) // If line was escaped and the first character isn't a space, add one
			{
				if (err=PtrPlusHand_(" ",dataHandle,1)) break;
			}
			if (err=PtrPlusHand_(line,dataHandle,len)) break;
			
			// grab the next line; may or may not be ours
			type = GetLine(line,sizeof(line),&len,&lid);
			if (exLine && type==LINE_START) type = LINE_MIDDLE;	// extended line means new line is really part of this line
		}
		while (type==LINE_MIDDLE);
		
		CloseLine(&lid);
		if (err)
		{
			WarnUser(LINK_HISTORY_GET_DATA_ERR,err);
			ZapHandle(dataHandle);
			return (nil);
		}

		(*histories)[index].hUrl = dataHandle;

		UL(dataHandle);
		return (dataHandle);
	}
	else	
		return (nil);
}

/************************************************************************
 * ReadHistTOC - read in the history list TOC
 ************************************************************************/
OSErr ReadHistTOC(short which)
{
	OSErr err = noErr;
	FSSpec lSpec = (*gHistories)[which].spec;
	Boolean sane;
	short refN=0;
	short oldResF = CurResFile();
	HistoryStructHandle theToc = nil;
	UHandle hand = nil;
		
	//
	//	Open the History file
	//
	
	IsAlias(&(*gHistories)[which].spec,&lSpec);	
	if (err=FSpRFSane(&lSpec,&sane)) return(-1);
	if (!sane)
	{
		FSpKillRFork(&lSpec);
		return (-1);
	}
	else if (-1!=(refN=FSpOpenResFile(&lSpec,fsRdPerm)))
	{		
		// Clear out the old handle
		if ((*gHistories)[which].theData) ZapHandle((*gHistories)[which].theData);
		
		//
		// Read to TOC handle out of the file
		//
				
		theToc = Get1Resource(LINK_TOC_TYPE,LINK_RESID);
		if (noErr==(err=ResError()))
		{ 	
			short i, count;
			
			if (theToc != nil)
			{
				// is this toc the right version?
				if (CorrectVersion(theToc))
				{
					DetachResource(theToc);
					(*gHistories)[which].theData = theToc;
					
					// iterate through the toc and reset garbage fields
					count = GetHandleSize_(theToc)/sizeof(HistoryStruct);
					for (i = 0; i < count; i++) (*theToc)[i].hUrl = nil;
				}
				else
				{
					// wrong version of the toc. Lose the history.
					theToc = nil;
				}
			}
		}
		
		// got nothing from the resource file
		if (theToc == nil)
		{
			// create a new, empty handle.  No history entries are defined.
			hand = NuHTempOK(0L);
			err = MemError();
			if (hand && (err==noErr)) (*gHistories)[which].theData = (HistoryStructHandle)hand;
		}
		
		// was there an error?
		if (err != noErr)
		{
		 	WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err);
		 	ZapHandle((*gHistories)[which].theData);
		 	(*gHistories)[which].theData = nil;
		}
	}
		
	if (refN) CloseResFile(refN);
	UseResFile (oldResF);

	return(err);
} 

/************************************************************************
 * CorrectVersion - is this HistoryStructHandle something we can handle?
 ************************************************************************/
Boolean CorrectVersion(HistoryStructHandle theToc)
{
	Boolean result = false;
	
	if (theToc)
	{
		if (GetHandleSize(theToc) > 0)
		{
			if ((*theToc)->version == LINK_HISTORY_VERSION)
				result = true;
		}
	}
	
	return (result);
}

/************************************************************************
 * HistMatchFound - find a given history in a history file
 ************************************************************************/
long HistMatchFound(long hashName,Handle theUrl,short which)
{
	long i;
	Handle hUrl = nil;
	URLNameStr tempStr, tempName;
	HistoryStructHandle theHistories = (*gHistories)[which].theData;
	long stop = (GetHandleSize_(theHistories)/sizeof(HistoryStruct));
	Boolean	needStringMatch = false;
	long matched = -1;
	HistoryStruct *theStruct, *endStruct;
	
	endStruct = (*theHistories)+stop;
	for (theStruct=*theHistories; theStruct<endStruct; theStruct++)
		if (theStruct->hashName == hashName && !theStruct->deleted)
			if (matched==-1) matched = theStruct-*theHistories;		
			else {needStringMatch = True; break;}

	if (!needStringMatch) return(matched);
	
	// two names with equal hash values found, compare names.
	PSCopy(tempName,*theUrl);
	*tempName = RemoveChar(' ',tempName+1,*tempName);
	for (i=0;i<stop;i++)
	{
		if ((*theHistories)[i].hashName == hashName && !(*theHistories)[i].deleted)
		{
			hUrl = GetHistoryData(which,i,true);	// don't zap the returned handle.  It's a part of the history toc.
			if (hUrl)
			{
				PCopy(tempStr, *hUrl);
				*tempStr = RemoveChar(' ',tempStr+1,*tempStr);
				if (StringSame(tempName,tempStr))
					return (i);
			}
		}
	}

	return (-1);	
}

/************************************************************************
 * DeleteHistEntryFromTOC - delete a history entry from the TOC.
 ************************************************************************/
void DeleteHistEntryFromTOC(short which, short index)
{
	(*(*gHistories)[which].theData)[index].deleted = true;
	(*gHistories)[which].dirty = true;
	
	if ((*(*gHistories)[which].theData)[index].type == ltAd)	// did we just delete an ad?
		if ((*(*gHistories)[which].theData)[index].thumb!=0)	// does it think it has a thumbnail?
			DeleteAdGraphic((*(*gHistories)[which].theData)[index].adId);
}

/************************************************************************
 * TimeToCompactTOC - is it worth our time to compact this TOC?
 ************************************************************************/
Boolean TimeToCompactTOC(short which)
{
	short deletedCount = 0;
	short historyCount = HistoryCount(which);
	HistoryStructHandle histories = (*gHistories)[which].theData;
	
	while (historyCount > 0)
	{
		if ((*histories)[historyCount-1].deleted) deletedCount++;
		historyCount--;
	}
	
	return (deletedCount >= COMPACT_THRESHHOLD);
}
 	
 /************************************************************************
 * CompactHistTOC - remove deleted entries from the TOC.
 ************************************************************************/
OSErr CompactHistTOC(short which)
{
	OSErr err = noErr;
	Accumulator histAccu;
	short count = 0, historyCount = HistoryCount(which);
	HistoryStructHandle histories = (*gHistories)[which].theData;
			
	if ((err = AccuInit(&histAccu))==noErr)
	{
		while ((count != historyCount) && (err==noErr))
		{
			if ((*histories)[count].deleted)
				;	// skip this TOC entry.  It's deleted.
			else
			{
				// add this history entry to the Accumulator
				err = AccuAddPtr(&histAccu, &(*histories)[count], sizeof(HistoryStruct));
			}
			count++;
		}
		
		if (err == noErr)
		{
			AccuTrim(&histAccu);
			ZapHandle((*gHistories)[which].theData);
			(*gHistories)[which].theData = histAccu.data;
			histAccu.data = nil;
		}
		
		AccuZap(histAccu);
	}
		
	return (err);
}

/************************************************************************
 * HistoryCount - return # of history entries
 ************************************************************************/
short HistoryCount(short which)
{
	if ((*gHistories)[which].theData)
		return ((GetHandleSize_((*gHistories)[which].theData)/sizeof(HistoryStruct)));
	else
		return (0);
}

/************************************************************************
 * AddAllHistoryItems - add all the history items to the list
 ************************************************************************/
void AddAllHistoryItems(ViewListPtr pView, Boolean needsSort, LinkSortTypeEnum sortType)
{
	OSErr err = noErr;
	short i,count,cur = 0;
	VLNodeInfo	info;
	ShortHistoryStructHandle histories = NULL;
	Boolean reverseSort = (sortType&kLinkReverseSort)!=0;
	
	// get a sorted list of all history entries to add
	err = BuildListOfHistoriesForWindow(&histories, needsSort, sortType);
	if ((err==noErr) && histories)
	{
		count = GetHandleSize(histories)/sizeof(ShortHistoryStruct);
		for (i=0;i<count;i++)
		{
			cur = reverseSort ? (count - 1 - i) : i;
			
			Zero(info);
			PStrCopy(info.name,(*histories)[cur].name,sizeof(info.name));
			info.nodeID = (*histories)[cur].nodeId;
			info.iconID = LinkTypeToIconID((*histories)[cur].type);
			LVAdd(pView, &info);				
		}
		
		// cleanup, don't need this anymore.
		ZapHandle(histories);
	}
}

/************************************************************************
 * ShortHistoryStructHandle - return a sorted list of structures that
 *	contain just enough information to add to the history window.
 ************************************************************************/
OSErr BuildListOfHistoriesForWindow(ShortHistoryStructHandle *histories, Boolean needsSort, LinkSortTypeEnum sortType)
{
	OSErr err = noErr;
	short which, i;
	short numEntries, count;
	int (*compare)() = nil;
	
	*histories = nil;
	
	// How many history entries are there in all files?
	numEntries = 0;
	for (which = 0; which < NHistoryFiles; which++)
		numEntries += HistoryCount(which);

	// Make a new handle that's big enough for them all
	*histories = NuHandle(numEntries*sizeof(ShortHistoryStruct));
	err = MemError();
	if (*histories && (err == noErr))
	{
		// fill the new handle with all of the entries
		count = 0;
		for (which = 0; which < NHistoryFiles; which++)
		{
			for (i = 0; i < HistoryCount(which); i++)
			{
				// only add non-deleted items
				if (!((*(*gHistories)[which].theData)[i].deleted) && !((*(*gHistories)[which].theData)[i].incompleteAd))
				{
					PCopy((**histories)[count].name,(*(*gHistories)[which].theData)[i].name);				
					(**histories)[count].cacheSeconds = (*(*gHistories)[which].theData)[i].cacheSeconds;	
					(**histories)[count].type = (*(*gHistories)[which].theData)[i].type;		
					(**histories)[count].nodeId = EntryToNodeId(which, i);	
					(**histories)[count].label = (*(*gHistories)[which].theData)[i].label;
					count++;
				}
			}
		}
		
		// Resize the handle down if we hafta.
		if (count != numEntries) SetHandleSize(*histories, count*sizeof(ShortHistoryStruct));
		
		// Sort the handle if we need to
		if (needsSort) 
		{
			switch (sortType&kLinkSortTypeMask)
			{
				case sType:
					compare = HistTypeCompare;
					break;
					
				case sName:
					compare = HistNameCompare;
					break;
					
				case sDate:
					compare = HistDateCompare;
					break;
					
				case sSpecialRemind:
					compare = HistRemindCompare;
					break;
			}
			
			if (compare) SortShortHistoryHandle(*histories, compare);
		}
	}
	else
	{
		WarnUser(MEM_ERR,err);
		*histories = nil;
	}
		
	return (err);
}

/************************************************************************
 * LinkType - Given a protocol, return the type of link
 ************************************************************************/
 LinkTypeEnum LinkType(ProtocolEnum protocol, PStr proto)
 {
 	LinkTypeEnum type = ltHttp;	// assume web link
 	Str255 scratch;
 	
	switch (protocol)
	{
		case proFinger:
		case proPh:
		case proPh2:
		case proLDAP:
			type = ltDirectoryServices;
			break;
			
		case proMail:
			type = ltMail;
			break;
			
		case proFile:
		case proCompFile:
			type = ltFile;
			break;
			
		case proSetting:
			type = ltSetting;
			break;
			
		default:
		{
			// make sure this isn't an ftp link
			if (StringSame(proto, GetRString(scratch,ANARCHIE_FTP)))
				type = ltFtp;
			break;
		}
	}
	
	return (type);
 }

/************************************************************************
 * LinkTypeToIconID - Given a protocol, return the icon to be used
 ************************************************************************/
short LinkTypeToIconID(LinkTypeEnum type)
{
	short iconID;
	
	if (type==ltAd)
	{
		// Use the thumbnail if present, a web link otherwise.
		iconID = kCustomIconResource;
	}
	else
	{
		// assign an icon based on the type of URL this is
		switch (type)
		{
			case ltDirectoryServices:
				iconID = MenuItemNameToIconID(WINDOW_MENU,WIN_PH_ITEM);
				break;
				
			case ltMail:
				iconID = MAILTO_LINK_TYPE_ICON;
				break;
			
			case ltHttp:
				iconID = HTTP_LINK_TYPE_ICON;
				break;
				
			case ltFtp:
				iconID = FTP_LINK_TYPE_ICON;
				break;
				
			case ltFile:
				iconID = MenuItemNameToIconID(FILE_MENU,FILE_SAVE_AS_ITEM);
				break;
				
			case ltSetting:
				iconID = SETTINGS_ICON;
				break;
				
			default:
				iconID = DEFAULT_LINK_TYPE_ICON;	// assume it's a web link
				break;
		}
	}
		
	return (iconID);
}

/************************************************************************
 * MenuItemNameToIconID - return the icon id of a menu item by name
 ************************************************************************/
short MenuItemNameToIconID(short menuID, short item)
{
	MenuHandle mh;
	short id = DEFAULT_LINK_TYPE_ICON;
	Str255 itemText;
		
	if (mh=GetMHandle(menuID))
	{
		GetMenuItemText(mh, item, itemText);
		id = Names2Icon(itemText,"\p");
	}
	
	return (id);
}

/************************************************************************
 * EntryToNodeId - create an ID out of file # and index
 ************************************************************************/
VLNodeID EntryToNodeId(short which, short index)
{
	VLNodeID id;
	
	id = (which*0x10000) + index;
	
	return (id);
}

/************************************************************************
 * NodeIdToEntry - return the file # and index of a given id
 ************************************************************************/
void NodeIdToEntry(VLNodeID id, short *which, short *index)
{
	*which = HiWord(id);
	*index = LoWord(id);
}

/************************************************************************
 * DeleteHistoryEntry - delete a history entry from the History window
 ************************************************************************/
void DeleteHistoryEntry(VLNodeInfo *info)
{
	short which;
	short index;
	
	NodeIdToEntry(info->nodeID, &which, &index);
	DeleteHistEntryFromTOC(which, index);
}
	
/************************************************************************
 * OpenHistoryEntry - open a history entry from the History window
 ************************************************************************/
OSErr OpenHistoryEntry(VLNodeInfo *info)
{
	short which;
	short index;
	Handle hUrl;
	Str255	proto;
	short len;
	OSErr openErr = noErr;
			
	// Figure out which file this entry is in ...
	NodeIdToEntry(info->nodeID, &which, &index);

	// read in the URL from the file
	// Note: GetHistoryData returns a handle that belongs to the history toc struct.  Don't trash it!
	if ((*(*gHistories)[which].theData)[index].dirty) hUrl = GetHistoryData(which,index,false);
	else hUrl = GetHistoryData(which,index,true);
	
	if (hUrl && (len = GetHandleSize(hUrl)))
	{
		char *url = LDRef(hUrl);
		
        // parse and open this URL     
		if (fnfErr==(openErr=OpenLocalURLPtr(url,len,nil,nil,false)))
			if ((openErr=ParseProtocolFromURLPtr(url,len,proto))==noErr)
				openErr = OpenOtherURLPtr(proto,url,len);
		UL(hUrl);
		
		// update the link's label
		UpdateLinkLabel(&((*(*gHistories)[which].theData)[index]), openErr);

		// Update the date in the History TOC
		LinkUpdateCacheDate(which, index);
		
		// Update the Link history window
		LinkTickle();
	}	
	
	return (openErr);
}	

/************************************************************************
 * GetDateString - given the ID of a node, return the date in a string
 ************************************************************************/
Boolean GetDateString(VLNodeID id, Str255 dateStr)
{
	short which;
	short index;
	Str31 zone;
	
	// Find the history entry
	NodeIdToEntry(id, &which, &index);
	
	// Does this entry have a label?
	if ((*(*gHistories)[which].theData)[index].label!=llNone)
	{
		LabelToString((*(*gHistories)[which].theData)[index].label,dateStr);
	}
	else
	{	
		// return the standard date string
		*zone = 0;
		SecsToLocalDateTime((*(*gHistories)[which].theData)[index].cacheSeconds-ZoneSecs(), ZoneSecs(), zone, dateStr);
	}
	
	return (true);
}

/************************************************************************
 * IsMarkedRemind - is this cell one we need to remind the user about?
 ************************************************************************/
Boolean IsMarkedRemind(VLNodeID id)
{
	Boolean result = false;
	short which;
	short index;
	
	NodeIdToEntry(id, &which, &index);
	result = (*(*gHistories)[which].theData)[index].label == llRemindMe;
	
	return (result);
}

/**********************************************************************
 * LabelToString - given a history label, return the string of that
 *	label.
 **********************************************************************/
void LabelToString(LinkLabelEnum label, Str255 dateStr)
{
	GetRString(dateStr,LinkHistoryLabelsStrn+label);
}

/**********************************************************************
 * LabelableLink - return true if this is a link type we should label
 **********************************************************************/
Boolean LabelableLink(LinkTypeEnum linkType)
{
	Boolean result = false;
	LinkTypeEnum scan;
	short count = sizeof(gLabelTheseLinks)/sizeof(LinkTypeEnum);
	
	for (scan = 0; !result && (scan < count); scan++)
	{
		if (gLabelTheseLinks[scan]==linkType)
			result = true;
	}
	
	return (result);
}

/**********************************************************************
 * GetLHPreviewIcon - return a handle to the icon for this id.
 **********************************************************************/
Handle GetLHPreviewIcon(VLNodeID id)
{
	Handle theIcon = nil, rIconSuite = nil;
	short which;
	short index;
	AdId adId;
	FSSpec adGraphicSpec;
	URLNameStr adGraphicName;
	OSErr err = noErr;
	LHPIconCacheHandle iconCache;
	
	if (LinkHasCustomIcons())
	{
		// Find the history entry
		NodeIdToEntry(id, &which, &index);
		
		// Make sure it's an ad
		if ((*(*gHistories)[which].theData)[index].type == ltAd)
		{	
			adId = (*(*gHistories)[which].theData)[index].adId;
			
			// Is the ad icon already loaded?
			if (iconCache=FindPVICache(adId))
			{
				theIcon = (*iconCache)->theIcon;
				if (theIcon && *theIcon)
				{
					HNoPurge(theIcon);	// don't purge this again until we're done with it.
					return (theIcon);
				}
				// else
					// the icon has been purged.  Read it back in.
			}

			// Find the icon preview file
			AdIdToName(adId, adGraphicName);
			if (noErr == FSMakeFSSpec(gLinkHistoryFolder.vRefNum,gLinkHistoryFolder.parID,adGraphicName,&adGraphicSpec))
			{
				short iconRes, oldResFile = CurResFile();
				
				// open the file
				iconRes = FSpOpenResFile(&adGraphicSpec,fsRdPerm);
				if (iconRes != -1)
				{										
					// read in the icon
					err = GetIconSuite(&rIconSuite, kCustomIconResource, svAllAvailableData);
					if (rIconSuite && *rIconSuite && (err==noErr))
					{	
						err = DupIconSuite(rIconSuite, &theIcon, false);
						if (err == noErr)
						{
							// Remember to mark it as purgable when we're done with it!
							HNoPurge(theIcon);
							
							// Add this icon to the list of loaded icons
							AddIconToPVICache(theIcon, adId);
						}
					}
					else theIcon = nil;
					
					CloseResFile(iconRes);
				}
				UseResFile(oldResFile);
			}
		}
	}		
	return (theIcon);
}

/************************************************************************
 * LinkUpdateCacheDate - this link was clicked on today
 ************************************************************************/
void LinkUpdateCacheDate(short which, short index)
{
	// make sure the link histories are around
	if (gHistories || (GenHistoriesList()==noErr))
	{
		// update the date for this TOC entry to today
		(*(*gHistories)[which].theData)[index].cacheSeconds = LocalDateTime();
		
		// save the TOC part of the file only
		WriteHistTOC(which);
	}
}

/************************************************************************
 * SortShortHistoryHandle - sort the monster link history handle
 ************************************************************************/
void SortShortHistoryHandle(ShortHistoryStructHandle toSort, int (*compare)())
{
	short count = 0;

	if (compare)
	{
		count = GetHandleSize(toSort)/sizeof(ShortHistoryStruct);
		QuickSort((void*)LDRef(toSort),sizeof(ShortHistoryStruct),0,count-1,(void*)compare,(void*)SwapHist);
		UL(toSort);
	}
}

/**********************************************************************
 * HistTypeCompare - compare two cells based on the url type
 **********************************************************************/ 
int HistTypeCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
{
	return (hist1->type - hist2->type);
}

/**********************************************************************
 * HistNameCompare - compare two cells based on the url itself
 **********************************************************************/ 
int HistNameCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
{
	return (StringComp(hist1->name,hist2->name));
}

/**********************************************************************
 * HistDateCompare - compare two cells based on the cache date
 **********************************************************************/ 
int HistDateCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
{
	int result;
	
	// does either entry have a label?
	if ((hist1->label != llNone) || (hist2->label != llNone))
	{
		// the entry with the most important label is newest ...
		result = hist1->label - hist2->label;
	}
	else
	{
		// the entry with the largest cacheSecods is newest ...
		result = hist2->cacheSeconds - hist1->cacheSeconds;
	}
	
	return (result);
}

/**********************************************************************
 * HistRemindCompare - compare two cells based on the cache date. 
 *	Special case for Remind sorting.
 **********************************************************************/ 
int HistRemindCompare(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
{
	int result;
	
	// does either entry have a Remind Me label?
	if ((hist1->label == llRemindMe) || (hist2->label == llRemindMe))
	{
		// both are set to Remind Me
		if ((hist1->label == llRemindMe) && (hist2->label == llRemindMe)) result = HistNameCompare(hist1, hist2);
		// one is set to Remind Me
		else if (hist1->label == llRemindMe) result = -1;
		else result = 1;
	}
	else
	{
		// the entry with the largest cacheSecods is newest ...
		result = hist2->cacheSeconds - hist1->cacheSeconds;
	}
	
	return (result);
}

/**********************************************************************
 * SwapHist - swap two history entries.
 **********************************************************************/ 
void SwapHist(ShortHistoryStructPtr hist1, ShortHistoryStructPtr hist2)
{
	ShortHistoryStruct tempHist;
	
	tempHist = *hist1;
	*hist1 = *hist2;
	*hist2 = tempHist;
}

/************************************************************************
 * GetLinkURL - Return a handle to the URL of a link.  (*gHistories)[which] URL should
 *	NOT be dumped, even after a one night stand.
 ************************************************************************/
Handle GetLinkURL(VLNodeInfo *info)
{
	short which;
	short index;
	Handle hUrl;
	
	// Figure out which file this entry is in ...
	NodeIdToEntry(info->nodeID, &which, &index);

	// read in the URL from the file
	// Note: GetHistoryData returns a handle that belongs to the history toc struct.  Don't trash it!
	if ((*((*gHistories)[which].theData))[index].dirty) hUrl = GetHistoryData(which,index,false);
	else hUrl = GetHistoryData(which,index,true);
		
	return (hUrl);
}

/**********************************************************************
 * AdWasClicked - call this when an Ad is clicked on in the Ad window
 **********************************************************************/
void AdWasClicked(AdId adId, OSErr openErr)
{
	short which, index;

	if (LocateAdInHistories(adId, &which, &index))
	{	
		// Update the label of this ad.
		UpdateLinkLabel(&((*(*gHistories)[which].theData)[index]), openErr);
		
		// Give it a new cache date when found.
		LinkUpdateCacheDate(which, index);
		
		// update the Link History window
		LinkTickle();
	}
}

/**********************************************************************
 * LocateAdInHistories - find the ad in the histories files
 **********************************************************************/
Boolean LocateAdInHistories(AdId adId, short *which, short *index)
{
	short count;
	Boolean foundIt = false;
	short w, i;
	
	// must have somewhere to put our results
	if (!which || !index) return (false);
	
	// Make sure the histories are around ...
	if (gHistories || (GenHistoriesList()==noErr))
	{
		// Iterate through all History files, looking for this Ad.
		for (w = 0; !foundIt && (w < NHistoryFiles); w++)
		{
			count = HistoryCount(w);
			for (i = 0; !foundIt && (i < count); i++)
			{
				if (((*(*gHistories)[w].theData)[i].adId.server == adId.server)
					&& ((*(*gHistories)[w].theData)[i].adId.ad == adId.ad))
				{
			 		*which = w;
					*index = i;
					foundIt = true;
			 	}
			}
		}
	}	
	
	return (foundIt);
}

/**********************************************************************
 * FindRemindLink - see if any links are marked as remind me
 **********************************************************************/
Boolean FindRemindLink(void)
{
	short count;
	Boolean foundOne = false;
	short w, i;
	
	// Make sure the histories are around ...
	if (gHistories || (GenHistoriesList()==noErr))
	{
		// Iterate through all History files
		for (w = 0; !foundOne && (w < NHistoryFiles); w++)
		{
			count = HistoryCount(w);
			for (i = 0; !foundOne && (i < count); i++)
			{
				if ((*(*gHistories)[w].theData)[i].remind)
					foundOne = true;
			}
		}
	}	
	
	return (foundOne);
}

/**********************************************************************
 * UnRemindLinks - forget about it!
 **********************************************************************/
void UnRemindLinks(Boolean labelToo)
{
	short count;
	short w, i;
	Boolean foundOne;
	
	// Make sure the histories are around ...
	if (gHistories || (GenHistoriesList()==noErr))
	{
		// Iterate through all History files
		for (w = 0; (w < NHistoryFiles); w++)
		{
			count = HistoryCount(w);
			foundOne = false;
			for (i = 0; (i < count); i++)
			{
				if ((*(*gHistories)[w].theData)[i].remind)
				{
					foundOne = true;
					(*(*gHistories)[w].theData)[i].remind = 0;
				}
				
				if (labelToo && ((*(*gHistories)[w].theData)[i].label==llRemindMe)) 
				{
					foundOne = true;
					(*(*gHistories)[w].theData)[i].label = llBookmarked;
				}
			}
			if (foundOne) WriteHistTOC(w);
		}
	}	
}

/**********************************************************************
 * AddAdToLinkHistory - call this to add an ad to the Link History
 *
 *	If adURL is non-null, we're adding an ad to the history list
 *	if adGraphic is non-nul, we're creating a preview for the lw window
 **********************************************************************/
OSErr AddAdToLinkHistory(AdId adId, StringPtr pUrl, Str255 adTitle, FSSpecPtr adGraphic)
{
	OSErr err = noErr;
	short which = 0, index;
	ProtocolEnum protocol;
	Str255 proto,host,query;
	OSErr labelableErrors[] = {fnfErr};
	long hashName;
	Handle hUrl = nil;
	URLNameStr urlName;
	Boolean needSave = false;
	
	//	Add the Ad to the link History Window
	if (pUrl && *pUrl && adTitle)
	{	
		//	Add the ad's URL to the link history file.
		if (!(err=ParseURL(pUrl,proto,host,query)))
		{
			protocol = FindSTRNIndex(ProtocolStrn,proto);
			FixURLString(host);
			if (protocol!=proMail) FixURLString(query);
		
			// make sure the name of the url contains something interesting
			if (adTitle && adTitle[0])
			{
				// use the name passed in
				PStrCopy(urlName, adTitle, sizeof(urlName));
			}
			else
			{
				if (host[0]) 	
				{
					// use the host of the actual url as the name		
					PCopy(urlName, host);
				}
				else
				{
					// URL had no host.  Use the actual URL
					PStrCopy(urlName, pUrl, sizeof(urlName));
				}
			}

			// The name of the history entry will be a hash of the url itself
			hashName = NickHashString(pUrl);
			
			// Turn the url into a handle we can keep around ...
			hUrl = NuHTempOK(0);
			if (!hUrl) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,MemError()));
			err = PtrPlusHand(pUrl+1,hUrl,pUrl[0]);
			if (err) return(WarnUser(LINK_HISTORY_NEW_HISTORY_ERR,err));
			
			// Make sure the history files are around somewhere
			err = GenHistoriesList();
		
			// See if the ad already exists in a history file ...	
			if (LocateAdInHistories(adId, &which, &index))
			{
				// we'll need to save these changes if they were major ...
				if ((*(*gHistories)[which].theData)[index].incompleteAd) needSave = true;
				
				// update it with the new history information
				(*(*gHistories)[which].theData)[index].hashName = hashName;
				//(*(*gHistories)[which].theData)[index].cacheSeconds = LocalDateTime();	// - leave the date alone jdboyd 2/9/01
				(*(*gHistories)[which].theData)[index].dirty = true;
				(*(*gHistories)[which].theData)[index].incompleteAd = false;
				(*(*gHistories)[which].theData)[index].hUrl = hUrl;
				PCopy((*(*gHistories)[which].theData)[index].name,urlName);	
			}
			else
			{
				// add the ad as a new history entry
				if (err == noErr)
				{
					// Add the history to the history file
					err =  AddHistoryToTOC(MAIN_HISTORY_FILE, urlName, hashName, ltAd, llNotDisplayed, false, hUrl, adId);
					if (err == noErr) needSave = true;
				}
			}
		}
	}
	
	// Add the graphic to the Link History, if all has gone well
	if ((err == noErr) && adGraphic && LinkHasCustomIcons())
	{
		// Is the ad graphic a valid graphic file?
		if (FSpFileSize(adGraphic) > 0)
		{
			// Make sure the history files are loaded ...
			err = GenHistoriesList();
			
			if (err == noErr)
			{
				// Create an icon out of the graphic, save it in the History folder.
				if (CreateIconFromAdGraphic(adId, adGraphic) == noErr)
				{
					// find the ad this graphic belongs to
					if (!LocateAdInHistories(adId, &which, &index))
					{
						// it does not exist.  Add it now, using a bogus name and hashname
						err =  AddHistoryToTOC(MAIN_HISTORY_FILE, "\p", -1, ltAd, llNotDisplayed, true, hUrl, adId);
					}
					else
					{
						// (*gHistories)[which] history entry now has a thumbnail ...
						(*(*gHistories)[which].theData)[index].thumb = true;
					}
					if (err == noErr) needSave = true;
				}
			}		
		}
		// else
			// the ad graphic file was 0 bytes in length.  Nothing to do, we'll get called later when it's downloaded.
	}
	
	// save the history file
	if (needSave)
	{
		(*gHistories)[which].dirty = true;
		err = SaveIndHistoryFile(which);
		
		// Update the Link History window
		LinkTickle();
	}

	return (err);
}

/**********************************************************************
 * AgeLinks - go through history files, throw out old links.
 *	Warning: This is expensive!
 **********************************************************************/	
void AgeLinks(void)
{
	short which;
	short totalRemoved = 0;
	static long lastAged;
	
	// Nothing to do if there are no history files ...
	if (gHistories==nil) return;
	
	// Only do this once every hour ...
	if (lastAged == 0) lastAged = TickCount();
	else
	{	
		if ((TickCount() - lastAged) < AGE_INTERVAL)
			return;
	}
	
	for (which = 0; which < NHistoryFiles; which++)
		totalRemoved += AgeHistoryFile(which);
	
	// refresh the Link History window if we removed any entries
	if (totalRemoved > 0) 
		LinkTickle();
		
	// Trash orphaned link history previews
	PurgeLinkHistoryPreviewOrphans();
	
	// Let's not do this anytime soon ...
	lastAged = TickCount();
}

/**********************************************************************
 * AgeHistoryFile - go through a history file, throw out old links
 **********************************************************************/	
short AgeHistoryFile(short which)
{
	short count = HistoryCount(which);
	short i;
	long age;
	long maxAge = 60*60*24*GetRLong(LINK_AGE);
	long today;
	short removed = 0;
	
	// when is today?
	today = GMTDateTime();
	
	// Go through each history entry ...
	for (i = 0; i < count; i++)
	{
		// skip deleted entries.  They are already gone ...
		if ((*(*gHistories)[which].theData)[i].deleted == 0)
		{
			//
		 	//	Age Ads only if they're not on the current playlist
		 	//	Treat ads differently only for adware users that *have* a playlist.
		 	//
		 		
			if (IsAdwareMode() && (*(*gHistories)[which].theData)[i].type == ltAd)
			{		 
		 		if (IsAdInPlaylist((*(*gHistories)[which].theData)[i].adId))
					continue;
		 	}
		 	 	
	 		//
	 		//	Throw out any links older than a LINK_AGE days
	 		//
	 		
			age = today - (*(*gHistories)[which].theData)[i].cacheSeconds;
			if (age > maxAge) 
			{
				DeleteHistEntryFromTOC(which, i);
				removed++;
			}
		}
	}
	
	// Save the history TOC if we removed anything.
	if (removed > 0) WriteHistTOC(which);
	
	return (removed);
}

/**********************************************************************
 * PurgeLinkHistoryPreviewOrphans - iterate through the Link History
 *	folder, and trash preview files no longer in use.
 **********************************************************************/
void PurgeLinkHistoryPreviewOrphans(void)
{
	URLNameStr name;
	CInfoPBRec hfi;
	short count = 1;
	AdId adId;
	short which, index;
	
	hfi.hFileInfo.ioNamePtr = name;
	hfi.hFileInfo.ioFDirIndex = 0;
	while (!DirIterate(gLinkHistoryFolder.vRefNum,gLinkHistoryFolder.parID,&hfi))
	{
		if (hfi.hFileInfo.ioFlFndrInfo.fdType==LINK_HISTORY_PREVIEW_TYPE && hfi.hFileInfo.ioFlFndrInfo.fdCreator==CREATOR)
		{
			// Found a preview icon.  Figure out the name of the file
			if (NameToAdId(name, &adId))
			{
				// Is this ad on in any history file?
				if (!LocateAdInHistories(adId, &which, &index))
				{
					// It's not.  Delete thje graphic from the folder.
					if (DeleteAdGraphic(adId)==noErr)
						hfi.hFileInfo.ioFDirIndex--;
					//else
						//error deleting it.  Skip it, try it next time we purge.
				}
			}
		}
	}
}
	
/**********************************************************************
 * CreateIconFromAdGraphic - given an Ad, create a file with an icon
 *	representing the ad.
 **********************************************************************/
OSErr CreateIconFromAdGraphic(AdId adId, FSSpecPtr adGraphic)
{
	OSErr err = noErr;
	URLNameStr graphicName;
	FSSpec adIconSpec;
	
	// The name of the graphic file will be the adId.
	AdIdToName(adId, graphicName);
	
	// See if the graphic exists already;
	if (FSMakeFSSpec(gLinkHistoryFolder.vRefNum, gLinkHistoryFolder.parID, graphicName, &adIconSpec)!=noErr)
	{
		// the icon does not yet exist.  Create it from the adGraphic file.
		err = IconFromAd(&adIconSpec, adGraphic);
	}
	else err = dupFNErr;
	
	return (err);
}
		
/**********************************************************************
 * AdIdToName - given an AdId, return the name of its preview file
 **********************************************************************/
void AdIdToName(AdId adId, URLNameStr name)
{	
	Str255 scratch;
	
	scratch[0] = 0;
	name[0] = 0;
	
	NumToString(adId.server,name);
	NumToString(adId.ad,scratch);
	PCat(name,"\p,");
	PCat(name,scratch);
}

/**********************************************************************
 * NameToAdId - given a name, return the id of the ad it belongs to
 **********************************************************************/
Boolean NameToAdId(URLNameStr name, AdId *ad)
{	
	Boolean result = false;
	PStr scan;
	Str255 scratch;
	
	ad->server = ad->ad = 0;
	
	if (name && name[0]) 
	{
		// the name is <server,ad>
		scan = PPtrFindSub("\p,", name, name[0]);
		if (scan)
		{
			// figure out the server id.
			PStrCopy(scratch,name,scan - name);
			StringToNum(scratch, &(ad->server));
			
			// figure out the ad id. 
			scratch[0] = name[0] - (scan - name);
			BlockMove(scan+1,scratch+1,scratch[0]);
			StringToNum(scratch, &(ad->ad));
			
			result = true;
		}
	};
		
	return (result);
}

/**********************************************************************
 * DeleteAdGraphic - given an AdId, delete its preview file
 **********************************************************************/
OSErr DeleteAdGraphic(AdId adId)
{
	OSErr err = noErr;
	FSSpec adGraphicSpec;
	URLNameStr adGraphicName;
	
	// locate the ad file in the Link History Folder
	AdIdToName(adId, adGraphicName);
	err = FSMakeFSSpec(gLinkHistoryFolder.vRefNum,gLinkHistoryFolder.parID,adGraphicName,&adGraphicSpec);
	if (err == noErr)
	{
		// Delete the ad preview we've found.
		err = FSpDelete(&adGraphicSpec);
		
		// Remove the icon handle from the icon cache
		RemoveIconFromPVICache(adId);
	}
	// else
		// the ad preview can't be found.  Too bad.
	
	return (err);
}

/**********************************************************************
 * AddIconToPVICache - remember a loaded ad preview icon
 **********************************************************************/
void AddIconToPVICache(Handle theIcon, AdId adId)
{
	LHPIconCacheHandle newPVI;
	
	newPVI = NewZH(LHPIconCacheStruct);
	if (newPVI)
	{
		(*newPVI)->theIcon = theIcon;
		(*newPVI)->adId = adId;
		LL_Queue(gPreviewIcons, newPVI, (LHPIconCacheHandle));
	}	
}

/**********************************************************************
 * FindPVICache - find an icon in the preview icon cache
 **********************************************************************/
LHPIconCacheHandle FindPVICache(AdId adId)
{
	LHPIconCacheHandle scan;
	
	for (scan = gPreviewIcons; scan && (((*scan)->adId.server != adId.server) || ((*scan)->adId.ad != adId.ad)); scan = (*scan)->next);
	
	return (scan);
}


/**********************************************************************
 * RemoveIconFromPVICache - remove an icon from the cache
 **********************************************************************/
void RemoveIconFromPVICache(AdId adId)
{
	LHPIconCacheHandle scan;
	
	// Locate the cache entry associated with this adId.
	for (scan = gPreviewIcons; scan; scan = (*scan)->next)
	{
		if (((*scan)->adId.server == adId.server) && ((*scan)->adId.ad == adId.ad))
		{
			RemovePVIFromPVICache(&scan);
			break;
		}
	}
}

/**********************************************************************
 * RemovePVIFromPVICache - remove a PVI cache entry frm the list
 **********************************************************************/
void RemovePVIFromPVICache(LHPIconCacheHandle *toRemove)
{
	if (gPreviewIcons && toRemove && *toRemove)
	{			
		// remove it from the list
		LL_Remove(gPreviewIcons,*toRemove,(LHPIconCacheHandle));
		
		// nuke the icon cache
		DisposeIconSuite((**toRemove)->theIcon,true);
		
		// and now the cache entry
		ZapHandle(*toRemove);
		*toRemove = nil;
	}
}

/**********************************************************************
 * ZapPVICache = destroy the icon cache
 **********************************************************************/
void ZapPVICache(void)
{
	LHPIconCacheHandle scan = gPreviewIcons, next = nil;
	
	while (scan)
	{
		next = (*scan)->next;
		RemovePVIFromPVICache(&scan);
		scan = next;
	}	
}

/**********************************************************************
 * IconFromAd - create a file containing an icon representing the
 *	picture in the adSpec.  This assumes the adSpec is a valid file.
 **********************************************************************/
OSErr IconFromAd(FSSpecPtr iconSpec, FSSpecPtr adSpec)
{
	OSErr err = noErr;
	GraphicsImportComponent	importer;
	MatrixRecord whatIsTheMatrix;
	GWorldPtr	gWorld = nil;
	short depth = 8;	// Listview supports 8 bit icons ...
	Rect r,srcRect;
		
	// figure out which importer can open the ad picture file ...
	err = GetGraphicsImporterForFile(adSpec, &importer);
	if ((err == noErr))
	{
		// Create an offscreen GWorld to do the deed in ...
		SetRect(&r, 0 ,0, 32, 32);
		err = NewGWorld(&gWorld, depth, &r, nil, nil, useTempMem);	//	Try temp memory first
		if (err) err = NewGWorld(&gWorld, depth, &r, nil, nil, nil);	//	Failed, use application heap
		if (err == noErr)
		{		
			//	(jp)  12-5-99  Many of the graphics importer routines actually can (and do) return
			//	errors.  For example, if for what ever reason, we have a blank ad (as I currently have)
			//	and which could conceivably happen if we didn't have enough memory to display an ad in
			//	the ad window, GraphicsImportGetBoundsRect and GraphicsImportDraw return errors which
			//	results in a "TV snow" icon.  I added more error checking... which means that legitimate
			//	errors really might be returned.
			
			// Tell the importer where to put it ...
			err= GraphicsImportSetGWorld(importer, gWorld, nil);
			if (!err) {			
				// Tell the importer how to scale the picture when drawn ...
				err = GraphicsImportGetBoundsRect(importer, &srcRect);
			}
			if (!err) {
				RectMatrix(&whatIsTheMatrix, &srcRect, &r); 
				err = GraphicsImportSetMatrix(importer, &whatIsTheMatrix);
			}
			if (!err)
				err = GraphicsImportDraw(importer);
			
			// Now make an icon out of this GWorld ...
			if (!err)
				err = MakeLHIconFile(gWorld, &r, iconSpec, importer, adSpec);	
			else {
			// We'll still need to put something reasonable in the icon...
			// How about the gray logo pict?  Not pretty, but somthing for now...
				PicHandle pic;
				if (pic = GetResource_('PICT',GRAY_LOGO_PICT)) {
					PushGWorld();
					SetGWorld(gWorld,nil);
					HNoPurge_(pic);
					DrawPicture(pic,&r);
					HPurge((Handle)pic);
					err = MakeLHIconFile(gWorld, &r, iconSpec, importer, adSpec);
					PopGWorld();	
				}
			}			
		}
		
		// Cleanup
		CloseComponent(importer);
	}
	
	// Cleanup
	if (gWorld) DisposeGWorld(gWorld);
	
	return (err);
}

/**********************************************************************
 * MakeLHIconFile - create file containing a 32*32 bit icon in 1 and
 *	8 bits.
 **********************************************************************/
OSErr MakeLHIconFile(GWorldPtr gWorld, Rect *pRect, FSSpecPtr iconSpec, GraphicsImportComponent importer, FSSpecPtr sourceSpec)
{
	OSErr err = noErr;
	FInfo info;
	short iconRes;
	short oldResFile = CurResFile();
	RGBColor	color,*transparentColor=nil;
	
	//
	// First, see if this icon file already exists
	//
	
	if (noErr==FSpExists(iconSpec)) return noErr;
	
	
	//
	// Create a new res file to store the icons in.
	//
	
	FSpCreateResFile(iconSpec,CREATOR,LINK_HISTORY_PREVIEW_TYPE,smSystemScript);
	if ((err=ResError())!=noErr)
	{
	 	return (err);
	}
	
	iconRes = FSpOpenResFile(iconSpec,fsRdWrPerm);
	if (((err=ResError())!=noErr) || (iconRes ==-1))
	{
		FSpDelete(iconSpec);
		return (err);
	}
	
	//
	// Now create and save the icons
	//
	if (GetPNGTransColor(importer,sourceSpec,&color))
		transparentColor = &color;	//	Use PNG transparent color when setting up mask

	err = MakeIconSuite (gWorld, pRect, transparentColor, "\p");

	CloseResFile(iconRes);
	UseResFile(oldResFile);
		
		
	//
	// set the icon of the new file to the custom icon
	//
	
	if (err == noErr)
	{
		FSpGetFInfo(iconSpec,&info);
		info.fdFlags |= kHasCustomIcon;
		FSpSetFInfo(iconSpec,&info);
	}
	
	// If there was an error, forget about it
	if (err)
	{
		FSpDelete(iconSpec);
	}
	
	return (err);
}


OSErr MakeIconSuite (GWorldPtr gWorld, Rect *pRect, RGBColor *transparentColor, PStr name)

{
	Handle	icon = nil;
	OSErr		err = noErr;
	
	icon = MakeIcon(gWorld,pRect,1,16);			// make 16x16 1-bit color icon
	if(icon)
	{
		AddResource( icon, 'SICN', kCustomIconResource, name);
		err = err || ResError();
	}

	icon =	MakeICN_pound(gWorld, pRect, 32, transparentColor);		// create 32x32 1-bit color icon AND MASK
	if(icon)
	{
		AddResource( icon, 'ICN#', kCustomIconResource, name);
		err = err || ResError();
	}

	icon =	MakeICN_pound(gWorld, pRect, 16, transparentColor);		// create 16x16 1-bit color icon AND MASK
	if(icon)
	{
		AddResource( icon, 'ics#', kCustomIconResource, name);
		err = err || ResError();
	}

	icon = MakeIcon(gWorld,pRect,8,32);			// make 32x32 8-bit color icon
	if(icon)
	{
		AddResource( icon, 'icl8', kCustomIconResource, name);
		err = err || ResError();
	}

	icon = MakeIcon(gWorld,pRect,4,32);			// make 32x32 4-bit color icon
	if(icon)
	{
		AddResource( icon, 'icl4', kCustomIconResource, name);
		err = err || ResError();
	}

	icon = MakeIcon(gWorld,pRect,8,16);				// make 16x16 8-bit color mask
	if(icon)
	{
		AddResource( icon, 'ics8', kCustomIconResource, name);
		err = err || ResError();
	}

	icon = MakeIcon(gWorld,pRect,4,16);				// make 16x16 4-bit color mask
	if(icon)
	{
		AddResource( icon, 'ics4', kCustomIconResource, name);
		err = err || ResError();
	}
	
	return (err);
}

/**********************************************************************
 * MakeICN_pound - create a one bit icon and mask from gwp
 **********************************************************************/
Handle MakeICN_pound(GWorldPtr gwp, Rect *srcRect, short iconDimension, RGBColor *transparentColor)
{
	GWorldPtr	scaledGWorld;
	Rect	scaledIcon;
	Handle icon = nil;
	Handle iconMask = nil;
	Size iconSize;
	OSErr	theError;
	
	scaledGWorld = nil;
	// If we're making a scaled icon with transparency (one that is not of the same dimensions as the source rect),
	// we'll have to create a second GWorld of the proper size in order to correctly create the icon mask.
	if (transparentColor && RectWi (*srcRect) != iconDimension) {
		SetRect (&scaledIcon, 0, 0, iconDimension, iconDimension);
		theError = NewGWorld (&scaledGWorld, 8, &scaledIcon, nil, nil, useTempMem);	//	Try temp memory first
		if (theError)
			theError = NewGWorld (&scaledGWorld, 8, &scaledIcon, nil, nil, nil);			//	Failed, use application heap
		if (!theError) {
			PushGWorld ();
			SetGWorld (scaledGWorld, nil);
			EraseRect (&scaledIcon);
			CopyBits(GetPortBitMapForCopyBits(gwp),
				GetPortBitMapForCopyBits(scaledGWorld),
				srcRect,
				&scaledIcon,
				srcCopy | ditherCopy, nil);
			PopGWorld ();	
		}
	}
	
	icon = MakeIcon(gwp, srcRect, 1, iconDimension);			
	if(icon)
	{
		if (transparentColor)
		{
			//	Set every bit in mask except for transparent color
			if (transparentColor && RectWi (*srcRect) != iconDimension)
				iconMask = MakeIconLo(scaledGWorld, &scaledIcon, 1, iconDimension, transparentColor);		
			else
				iconMask = MakeIconLo(gwp, srcRect, 1, iconDimension, transparentColor);		
		}
		else
			iconMask = MakeIconMask(gwp, srcRect, iconDimension);
		if(iconMask)
		{
			iconSize = GetHandleSize(icon);
			SetHandleSize(icon, iconSize + GetHandleSize(iconMask));
			if (MemError()==noErr) BlockMove(*iconMask, (Ptr)(((long)*icon) + iconSize), GetHandleSize(iconMask));			
			DisposeHandle(iconMask);
		}
	}

	if (scaledGWorld)
		DisposeGWorld (scaledGWorld);

	return (icon);
}

/**********************************************************************
 * MakeIconMask - create an icon mask for an ad preview.  This is
 *	just a black box as big as the srcRect.
 **********************************************************************/
Handle MakeIconMask(GWorldPtr srcGWorld, Rect *srcRect, short iconSize)
{
	Handle	iconBits;
	OSErr err = noErr;
	GWorldPtr gWorld = nil;
	PixMapHandle hPM;
	RGBColor saveBackColor;
	RGBColor blackColor = { 0,0,0 };
									
	// Create an offscreen graphics world ...
	err = NewGWorld(&gWorld, 1, srcRect, nil, nil, useTempMem);	//	Try temp memory first
	if (err) err = NewGWorld(&gWorld, 1, srcRect, nil, nil, nil);	//	Failed, use application heap
	if (err == noErr)
	{		
		// draw a 32x32 black box
		PushGWorld();
		SetGWorld(gWorld,nil);
				
		hPM = GetGWorldPixMap(gWorld);
		LockPixels(hPM);	
		
		GetBackColor(&saveBackColor);
		RGBBackColor(&blackColor);	
		EraseRect(srcRect);
		RGBBackColor(&saveBackColor);
		
		UnlockPixels(hPM);
		PopGWorld();

		// Make an icon out of it ...
		iconBits = MakeIcon(gWorld, srcRect, 1, iconSize);
		
	}

	// Cleanup
	if (gWorld) DisposeGWorld(gWorld);
	
	return (iconBits);
}

/*
 *	These routines are taken from the MakeIcon sample code
 */
Handle MakeIcon(GWorldPtr srcGWorld, Rect *srcRect, short dstDepth, short iconSize)
{
	return MakeIconLo(srcGWorld, srcRect, dstDepth, iconSize, nil);
}

Handle MakeIconLo(GWorldPtr srcGWorld, Rect *srcRect, short dstDepth, short iconSize, RGBColor *transColor)
/*
	Creates a handle to the image data for an icon, or nil if an error.
	The source image is specified by GWorld and regtangle defining the area
	to create the icon from.
	The type of icon is specified by the depth and Size paramters.
	iconSize is used for both height and width.
	For example, to create an Icl8 pass 8 for dstDepth and 32 for iconSize.
	to create an ics8 pass 8 for the dstDepth and 16 for iconSize.
	to create an ICON pass 1 for the dstDepth and 32 for iconSize.
	
	
*/
{
	long			bytesPerRow;
	long			imageSize;
	Handle			dstHandle;
	PixMapHandle	pix;
	Rect			iconFrame;
	QDErr			err;
	
	PushGWorld();	// save Graphics env state

	SetGWorld(srcGWorld,nil);
	
	iconFrame.top = 0;
	iconFrame.left = 0;
	iconFrame.bottom = iconSize;
	iconFrame.right = iconSize;
	
	// make a gworld for the icl resource
	pix = (PixMapHandle)NewHandleClear(sizeof(PixMap));
	
	/* See Tech Note #120 - for info on creating a PixMap by hand as SetUpPixMap
		does.  SetUpPixMap was taken from that Tech Note....
	*/
	err =  SetUpPixMap(dstDepth,&iconFrame,GetCTable(dstDepth),pix);

	if(err)
	{
		PopGWorld();
		return nil;
	}
		
	LockPixels(GetGWorldPixMap(srcGWorld));
	LockPixels(pix);
			
	if (transColor)
	{
		long	transIdx = Color2Index(transColor);
		short	row,col;
		RGBColor	color;
		unsigned char	mask;
		Ptr		pBitMap;
		short	offset;
		
		//	Set bit in mask for every pixel not same color as transparency color
		pBitMap = (*pix)->baseAddr;
		offset = 0;
		mask = 0x80;
		for(row=0;row<iconSize;row++)
		{
			for(col=0;col<iconSize;col++)
			{
				GetCPixel(srcRect->left+col,srcRect->top+row,&color);
				if (Color2Index(&color)!=transIdx)
					//	Not transparent. Set this one
					pBitMap[offset] |= mask;
				mask >>= 1;
				if (!mask)
				{
					offset++;
					mask = 0x80;
				}
			}
			pBitMap += (*pix)->rowBytes & 0x3fff;
			offset = 0;
			mask = 0x80;
		}
	}
	else
		CopyBits(GetPortBitMapForCopyBits(srcGWorld),
				(BitMapPtr)*pix,
				srcRect,
				&iconFrame,
				srcCopy | ditherCopy, nil);
 
 
	UnlockPixels(GetGWorldPixMap(srcGWorld));
 
 	bytesPerRow = ((dstDepth * ((**pix).bounds.right - (**pix).bounds.left) + 15) / 16) * 2;
	imageSize  = (bytesPerRow) * ((**pix).bounds.bottom - (**pix).bounds.top);

	dstHandle = NewHandle(imageSize);
	err = MemError ();
	if(err || dstHandle == nil)
	{
		PopGWorld();
		return nil;	
	}
	HLock((Handle)dstHandle);
	BlockMove(GetPixBaseAddr(pix),*dstHandle,imageSize);
	HUnlock(dstHandle);
	UnlockPixels(pix);
	TearDownPixMap(pix);
	
	PopGWorld(); // Restore graphics env to previous state
	
	HNoPurge(dstHandle);
	return dstHandle;
}

void FreeBitMap(BitMap *Bits)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	Dumps a BitMap created by NewBitMap below.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
{
	DisposePtr(Bits->baseAddr);
	Bits->baseAddr=nil;
	SetRect(&Bits->bounds,0,0,0,0);
	Bits->rowBytes=0;
}

void CalcOffScreen(register Rect *frame,register long *needed, register short *rows)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	Calculates how much memory and rowbytes a bitmap of the size frame would require.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
{
	*rows=((((frame->right) - (frame->left)) + 15)/16) *2;
	*needed=(((long)(*rows)) * (long)((frame->bottom) - (frame->top)));
}
 
void NewBitMap(Rect *frame,BitMap *theMap)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	Creates a new empty bitmap.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
{
	Size	size;
	short	rbytes;
	
	CalcOffScreen(frame,&size,&rbytes);
	
	theMap->rowBytes=rbytes;
	theMap->bounds=*frame;
	theMap->baseAddr=NewPtrClear(size);
}

void NewMaskedBitMap(BitMap	*srcBits, BitMap *maskBits, Rect *srcRect)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	Pass a pointer to an existing bitmap, and this creates a bitmap that is an
	equivelent mask.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
{
	short	rowbytes,height,words;
	long	needed;

	NewBitMap(srcRect,maskBits);
	
	if(MemError())		
	{
		maskBits->baseAddr=nil;
	 	SetRect(&maskBits->bounds,0,0,0,0);
	 	maskBits->rowBytes=0;
	 }
	 else		/*	memory was allocated for Mask BitMap ok */
	 {
	 	CalcOffScreen(srcRect,&needed,&rowbytes);
	 	words=rowbytes/2;
	 	height=srcRect->bottom - srcRect->top;
	 	CalcMask(srcBits->baseAddr,maskBits->baseAddr,rowbytes,rowbytes,height,words);
	 }
}

#define kDefaultRes 0x00480000 /* Default resolution is 72 DPI; Fixed type */

OSErr SetUpPixMap(
    short        depth,       /* Desired number of bits/pixel in off-screen */
    Rect         *bounds,     /* Bounding rectangle of off-screen */
    CTabHandle   colors,      /* Color table to assign to off-screen */
    PixMapHandle aPixMap      /* Handle to the PixMap being initialized */
    )
{
    CTabHandle newColors;   	/* Color table used for the off-screen PixMap */
    Ptr        offBaseAddr; 	/* Pointer to the off-screen pixel image */
    OSErr      error;       	/* Returns error code */
    short      bytesPerRow;		/* Number of bytes per row in the PixMap */


    error = noErr;
    newColors = nil;
    offBaseAddr = nil;

  	bytesPerRow = ((depth * (bounds->right - bounds->left) + 15) / 16) * 2;

   /* Clone the clut if indexed color; allocate a dummy clut if direct color*/
    if (depth <= 8)
        {
        newColors = colors;
//        error = HandToHand((Handle *) &newColors);	// This doesn't appear to be necessary and creates a memory leak alb 2/00
        }
    else
        {
        newColors = (CTabHandle) NewHandle(sizeof(ColorTable) -
                sizeof(CSpecArray));
        error = MemError();
        }
    if (error == noErr)
        {
        /* Allocate pixel image; long integer multiplication avoids overflow */
        offBaseAddr = NewPtrClear((unsigned long) bytesPerRow * (bounds->bottom -
                bounds->top));
        if (offBaseAddr != nil)
            {
            /* Initialize fields common to indexed and direct PixMaps */
            (**aPixMap).baseAddr = offBaseAddr;  /* Point to image */
            (**aPixMap).rowBytes = bytesPerRow | /* MSB set for PixMap */
                    0x8000;
            (**aPixMap).bounds = *bounds;        /* Use given bounds */
//          (**aPixMap).pmVersion = 0;           /* No special stuff */
//          (**aPixMap).packType = 0;            /* Default PICT pack */
//          (**aPixMap).packSize = 0;            /* Always zero in mem */
            (**aPixMap).hRes = kDefaultRes;      /* 72 DPI default res */
            (**aPixMap).vRes = kDefaultRes;      /* 72 DPI default res */
            (**aPixMap).pixelSize = depth;       /* Set # bits/pixel */
//          (**aPixMap).planeBytes = 0;          /* Not used */
//          (**aPixMap).pmReserved = 0;          /* Not used */

            /* Initialize fields specific to indexed and direct PixMaps */
            if (depth <= 8)
                {
                /* PixMap is indexed */
//              (**aPixMap).pixelType = 0;       /* Indicates indexed */
                (**aPixMap).cmpCount = 1;        /* Have 1 component */
                (**aPixMap).cmpSize = depth;     /* Component size=depth */
                (**aPixMap).pmTable = newColors; /* Handle to CLUT */
                }
            else
                {
                /* PixMap is direct */
                (**aPixMap).pixelType = RGBDirect; /* Indicates direct */
                (**aPixMap).cmpCount = 3;          /* Have 3 components */
                if (depth == 16)
                    (**aPixMap).cmpSize = 5;       /* 5 bits/component */
                else
                    (**aPixMap).cmpSize = 8;       /* 8 bits/component */
                (**newColors).ctSeed = 3 * (**aPixMap).cmpSize;
//              (**newColors).ctFlags = 0;
//              (**newColors).ctSize = 0;
                (**aPixMap).pmTable = newColors;
                }
            }
        else
            error = MemError();
        }
    else
        newColors = nil;

    /* If no errors occured, return a handle to the new off-screen PixMap */
    if (error != noErr)
        {
        if (newColors != nil)
            DisposeCTable(newColors);
        }

    /* Return the error code */
    return error;
    }

void TearDownPixMap(PixMapHandle pix)
{
	DisposeCTable((*pix)->pmTable);
	DisposePtr((*pix)->baseAddr);
	DisposeHandle((Handle)pix);	
}
