/* 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 "junk.h"
#define FILE_NUM 144

#include "trans.h"

typedef enum { kAutoScore, kUserMarkJunk, kUserMarkNotJunk } TJunkType;
	
void ScoreOneMessage ( TLMHandle tList, TOCHandle tocH, short sumNum, TJunkType howToScore );
Boolean NagHitReturnItem(EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon);
void MarkOneAsJunk ( TLMHandle tList, TOCHandle tocH, short sumNum, Boolean isJunk );
OSErr JunkSetScoreLo(TOCHandle tocH,short sumNum,short because,short score);
Boolean CanScoreToc( TOCHandle tocH );
Boolean WhiteListByMID(TOCHandle tocH, short sumNum);

// IMAP support
OSErr JunkIMAP(TOCHandle tocH, short sumNum, Boolean isJunk, Boolean dontMove);
OSErr ArchiveIMAPJunk(void);
void JunkMoveIMAPMessages(TOCHandle tocH, Boolean isJunk);

/************************************************************************
 * MoveToJunk - 
 ************************************************************************/
OSErr MoveToJunk ( TOCHandle source, short spamThresh, FilterPB *fpb ) {
	OSErr	err = noErr;
	TOCHandle junk = GetJunkTOC ();
	short	found = 0;
	int		i;
	
	if ( junk == NULL )
		return paramErr;
	
//	select all messages above the junk mailbox threshhold
	for ( i = 0; i < (*source)->count; i++ ) {
		if ((*source)->sums [ i ].spamScore < spamThresh )
			BoxSetSummarySelected ( source, i, false );
		else {
			BoxSetSummarySelected ( source, i, true );
			found += 1;

		//	don't trust the date on junk
			if ( !JunkPrefBelieveDate ()) 
				(*source)->sums[i].seconds = (*source)->sums[i].arrivalSeconds;
		
		//	Clean up the server
			if ( JunkPrefServerDel() && !((*source)->sums[i].flags & FLAG_SKIPPED ))
				AddTSToPOPD ( DELETE_ID, source, i, False );
			}
		}

//	if we found any junk, move it to the junk mailbox
	if ( found > 0 ) {
		FSSpec junkSpec = GetMailboxSpec ( junk, -1 );
		err = MoveSelectedMessagesLo ( source, &junkSpec, false, false, false, false );

	//	Bookkeeping
		if ( PrefIsSet ( PREF_REPORT ) && fpb != NULL ) 
			while ( found-- > 0 )
				AddSpecToList ( &junkSpec, fpb->report );
		UseFeature ( featureJunk );
		}

	return err;
	}

/**********************************************************************
 * MoveToIMAPJunk - if an IMAP message is junk, get rid of it.
 *
 *	Note, this is different than MoveToJunk that is used on local
 *	mailboxes.  It must be able to move messages one at a time for
 *	incremental IMAP filtering.
 *
 *	Additionally, it does not move open spam messages, nor does it
 *	move deleted messages.
 **********************************************************************/
OSErr MoveToIMAPJunk(TOCHandle source, short sumNum, short spamThresh, FilterPB *fpb) 
{
	OSErr err = noErr;
	TOCHandle junkTocH = GetJunkTOC ();
	short found = 0;
	short i;
	short start, end;
	Handle uids;
	FSSpec junkSpec;
	
	// locate the proper junk mailbox.  Don't warn the user, we're already
	// warned them before the IMAP operation got underway.
    junkTocH = LocateIMAPJunkToc(source, true, true);
    
	// Sanity check
	if (((*source)->imapTOC == 0) || (junkTocH == NULL))
		return (paramErr);
	
	// count the messages to be moved
	if (sumNum == -1)
	{
		// do them all.
		start = 0;
		end = (*source)->count;
		
		// count number of spam messages.  Ignore deleted and open messages.
		for (i=start;(i<end);i++)
			if (((*source)->sums[i].spamScore >= spamThresh) 
				&& ((*source)->sums[i].messH == NULL)
				&& (((*source)->sums[i].opts&OPT_DELETED)==0))
				found++;		
	}	
	else
	{
		// just one message	
		found = 1;
	}
	
	
	if (sumNum != -1)
	{
		// doing a single message.
		err = IMAPMoveMessageDuringFiltering(source, sumNum, junkTocH, false, fpb);
	}
	else
	{
		// doing a range of messages
	
		// junk all spam messages
		if (found > 0)
		{
			uids = NuHandleClear(found*sizeof(unsigned long));
			if (uids)
			{
				i = found;
				for (sumNum=start;(sumNum<end) && i;sumNum++)
					if (((*source)->sums[sumNum].spamScore >= spamThresh) 
						&& ((*source)->sums[sumNum].messH == NULL)
						&& (((*source)->sums[sumNum].opts&OPT_DELETED)==0))
					{
						((unsigned long *)(*uids))[--i] = ((*source)->sums[sumNum].uidHash);
						
						// don't trust the date on junk
						if (!JunkPrefBelieveDate ()) 
							(*source)->sums[sumNum].seconds = (*source)->sums[sumNum].arrivalSeconds;
					}
								
				err = IMAPTransferMessages(source, junkTocH, uids, false, false);
			}
		}
		
		//	Bookkeeping
		if (PrefIsSet(PREF_REPORT) && (fpb != NULL))
		{
			junkSpec = GetMailboxSpec (junkTocH, -1);
			while ( found-- > 0 )
				AddSpecToList (&junkSpec, fpb->report);
		}
		UseFeature ( featureJunk );
	}

	return err;
}
	
/**********************************************************************
 * FilterJunk - if a message is junk, get rid of it
 **********************************************************************/
OSErr FilterJunk ( TOCHandle fromTocH ) {
	OSErr err = noErr;
	
	JunkScoreBox ( fromTocH, -1, -1, true );
	err = MoveToJunk ( fromTocH, GetRLong ( JUNK_MAILBOX_THRESHHOLD ), NULL );
	
	return err;
	}

//	Helper routine for "Junk" - Mark one message as junk
void MarkOneAsJunk ( TLMHandle tList, TOCHandle tocH, short sumNum, Boolean isJunk ) {
	short spamScore = isJunk ? GetRLong(JUNK_XFER_SCORE) : 0;
	if ( tList != NULL )
		ScoreOneMessage ( tList, tocH, sumNum, isJunk ? kUserMarkJunk : kUserMarkNotJunk );
	JunkSetScore ( tocH, sumNum, JUNK_BECAUSE_USER, !isJunk||spamScore?spamScore:(*tocH)->sums[sumNum].spamScore );
//	don't trust the date on junk
	if (isJunk && !JunkPrefBelieveDate ()) 
		(*tocH)->sums[ sumNum ].seconds = (*tocH)->sums[ sumNum ].arrivalSeconds;

	if (!(*tocH)->imapTOC && JunkPrefServerDel() && !((*tocH)->sums[ sumNum ].flags&FLAG_SKIPPED))
		AddTSToPOPD( DELETE_ID, tocH, sumNum, False );
	}

/************************************************************************
 * Junk - mark selected messages as junk (or not)
 ************************************************************************/
// no error handling yet
// no easyopen yet
OSErr Junk( TOCHandle tocH, short sumNum, Boolean isJunk, Boolean ezOpen ) {
	FilterPB fpb;
	FSSpec junkSpec;
	TOCHandle junkTocH;
	TLMHandle tList = NULL;
	short i;
    TOCHandle realTOC;
	short	realSum;
	    	
	UseFeature(featureJunk);

// Properly handle IMAP mailboxes ...
	if ((*tocH)->imapTOC)
		return (JunkIMAP(tocH, sumNum, isJunk, false));
	
// Porperly handle IMAP messages in virtual TOCs ...
	if ((*tocH)->virtualTOC)
	{	
		// prrocess all IMAP messages in this virtual toc
		for (i=(*tocH)->count;i--;)
			if ((*tocH)->sums[i].selected)
			{
				realTOC = GetRealTOC(tocH,i,&realSum);
				if (realTOC)
					JunkIMAP(realTOC, realSum, isJunk, true);				
			}
		
		// move all the processed IMAP messages
		JunkMoveIMAPMessages(tocH, isJunk);
	}
	
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );
		
//	Looking for selected messages
	if ( sumNum < 0 )
	{
		// mark the selected ones with the chosen score
		for (i=(*tocH)->count;i--;)
			if ((*tocH)->sums[i].selected )
			{
				MarkOneAsJunk ( tList, tocH, i, isJunk );
				if (!isJunk)
				{
					// redate if need be
					if ((*tocH)->sums[i].seconds==(*tocH)->sums[i].arrivalSeconds) RedateTS(tocH,i);
					
					// put back on server if need be
					PushPers(PERS_FORCE(TS_TO_PERS(tocH,i)));
					if (PrefIsSet(PREF_LMOS)) RemIdFromPOPD(PERS_POPD_TYPE(CurPers),DELETE_ID,(*tocH)->sums[i].uidHash);
					PopPers();
				}
			}
		
		// if they're being marked not junk...
		if (!isJunk) 
		{
			Boolean filter = true;
		
			// Whitelist them?
			if (JunkPrefNotJunkWhite())
				WhiteListTS(tocH,-1);
			
			// Before filtering, adjust the selection to only messages
			// actually in the junk mailbox, if it's a virtual TOC
 			if ((*tocH)->virtualTOC) for (i=(*tocH)->count-1;i>=0;i--)
 			{
 				FSSpec spec;
 				if ((*tocH)->sums[sumNum].selected)
 				{
 					if ((*tocH)->sums[sumNum].flags&FLAG_OUT)
 						BoxSetSummarySelected(tocH,i,false);
 					else
 					{
 						spec = GetMailboxSpec(tocH,i);
 						if (!SpecIsJunkSpec(&spec))
 							BoxSetSummarySelected(tocH,i,false);
 					}
 				}
 			}
 			else if (!BoxIsJunkBox(tocH))
 				filter = false;
 			
			if (filter)
			{
				// filter them
				FilterSelectedMessages(flkIncoming,tocH,&fpb);
				FilterPostprocess(flkIncoming,&fpb);
			}
		}
		// if they're being marked junk, transfer them to junk
		else if (!BoxIsJunkBox(tocH))
		{
			if ((*tocH)->which!=JUNK && (junkTocH=GetJunkTOC()))
			{
				junkSpec = GetMailboxSpec(junkTocH,-1);
				MoveSelectedMessages(tocH,&junkSpec,false);
			}
		}
	}
	else
	{
	//	do just the one message
		MarkOneAsJunk ( tList, tocH, sumNum, isJunk );
		
		// if it's not junk...
		if (!isJunk)
		{
			if (!isJunk)
			{
				// fix the date?
				if ((*tocH)->sums[sumNum].seconds==(*tocH)->sums[sumNum].arrivalSeconds) RedateTS(tocH,sumNum);
						
				// put back on server if need be
				PushPers(PERS_FORCE(TS_TO_PERS(tocH,sumNum)));
				if (PrefIsSet(PREF_LMOS)) RemIdFromPOPD(PERS_POPD_TYPE(CurPers),DELETE_ID,(*tocH)->sums[sumNum].uidHash);
				PopPers();
			}
			
			// Whitelist it?
			if (JunkPrefNotJunkWhite())
				WhiteListTS(tocH,sumNum);
			
			// Filter it
			FilterMessage(flkIncoming,tocH,sumNum);
		}
		// if it is junk, move it
		else if (ezOpen)
			TransferMenuChoice(TRANSFER_MENU,MAILBOX_JUNK_ITEM,tocH,sumNum,0,false);
		else if ((*tocH)->which!=JUNK && (junkTocH=GetJunkTOC()))
		{
			junkSpec = GetMailboxSpec(junkTocH,-1);
			MoveMessageLo(tocH,sumNum,&junkSpec,false,false,true);
		}
	}
	// lies, lies, lies
	if ( tList != NULL )
		DisposeHandle ( tList );
	return noErr;
}

/************************************************************************
 * JunkIMAP - mark selected IMAP messages as junk (or not)
 ************************************************************************/
OSErr JunkIMAP(TOCHandle tocH, short sumNum, Boolean isJunk, Boolean dontMove) 
{
	OSErr err = noErr;
	TLMHandle tList = NULL;
	TOCHandle destTocH = NULL;
	PersHandle imapPers = TOCToPers(tocH);
    MailboxNodeHandle imapDest = NULL;
    Boolean filteringUnderway = IMAPFilteringUnderway();
    long uid = (*tocH)->sums[sumNum].uidHash; 
    short i, count;
    Handle uids;
       	
    // sanity check
    if (!tocH || !imapPers)
    	return (paramErr);
    
    // build list of translators ...
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );
	
	//	Looking for selected messages
	if ( sumNum < 0 )
	{
		// mark the selected ones with the chosen score
		for (i=(*tocH)->count;i--;)
			if ((*tocH)->sums[i].selected )
				MarkOneAsJunk (tList, tocH, i, isJunk);
										
		// determine where to move them
		if (!isJunk)
		{
			// Whitelist them?
			if (JunkPrefNotJunkWhite())
				WhiteListTS(tocH,-1);
			
			if (BoxIsJunkBox(tocH))
			{
				// Put the message back into the proper inbox.
				if ((imapDest = LocateInboxForPers(imapPers)) != NULL)
					destTocH = TOCBySpec(&(*imapDest)->mailboxSpec);
			}
		}
		// if they're being marked junk, transfer them to junk
		else if (!BoxIsJunkBox(tocH))
		{
			// This is an IMAP mailbox we're junking from.  Get the proper JUNK mailbox
			imapDest = GetIMAPJunkMailbox(imapPers, true, false);
			if (imapDest && (imapDest != TOCToMbox(tocH)))
				destTocH = TOCBySpec(&(*imapDest)->mailboxSpec);
		}
		
		if (!dontMove)
		{
			// build a list of the currently selected, non-deleted messages and move them.
			// if we're not junking, the selection may have expanded!
			count = 0;
			for (i=0;(i<(*tocH)->count);i++) 
				if (((*tocH)->sums[i].selected) && (((*tocH)->sums[i].opts&OPT_DELETED)==0)) 
					count++;
				
			if (count)
			{
				uids = NuHandleClear(count*sizeof(unsigned long));
				if (uids)
				{
					for (i=0;i<(*tocH)->count && count;i++)
						if (((*tocH)->sums[i].selected) && (((*tocH)->sums[i].opts&OPT_DELETED)==0))
						{
							((unsigned long *)(*uids))[--count] = ((*tocH)->sums[i].uidHash);
							
							// also mark the messages as not junk to catch the expanded selection
							if (!isJunk) MarkOneAsJunk (tList, tocH, i, false);
						}
					
					// actually move them now
					if (destTocH)
						err = IMAPTransferMessages(tocH, destTocH, uids, false, false);
				}
				else
				{
					// Memory error.
					DisposeHandle (tList);
					return (MemError());
				}
			}
		}
	}
	else
	{
		//	do just the one message
		MarkOneAsJunk (tList, tocH, sumNum, isJunk);
		
		if (!dontMove)
		{
			// if it's not junk...
			if (!isJunk)
			{
				// Whitelist it?
				if (JunkPrefNotJunkWhite())
					WhiteListTS(tocH,sumNum);
				
				// Find the proper Inbox to move this messgae back to
				if ((imapDest = LocateInboxForPers(imapPers)) != NULL)
					destTocH = TOCBySpec(&(*imapDest)->mailboxSpec);
						
				// Move the message
				if (destTocH)
					err = IMAPTransferMessage(tocH, destTocH, uid, false, false);
			}
			else if (!BoxIsJunkBox(tocH))
			{
				imapDest = GetIMAPJunkMailbox(imapPers, true, false);
				if (imapDest)
				{
					if ((destTocH = TOCBySpec(&(*imapDest)->mailboxSpec)) != NULL)
					{
						// this gets a little tricky with IMAP filtering ...
						if (filteringUnderway)
						{       
							IMAPStopFiltering(false);

							// copy the message from this mailbox to the destination
							if (IMAPTransferMessage(tocH,destTocH,uid,true,true)==noErr)
							{                               
								// then mark it as deleted.  We'll expunge when filtering is complete
								IMAPMarkMessageAsDeleted(tocH, uid, false);
								if (FancyTrashForThisPers(tocH)) FlagForExpunge(tocH);
							}
							IMAPStartFiltering(tocH, true);
						}
						else
						{
							MoveMessageLo(tocH,sumNum,&(*imapDest)->mailboxSpec,false,false,true);
						}
					}
				} 
			}	
		}
	}
	// lies, lies, lies
	if ( tList != NULL )
		DisposeHandle ( tList );
	return noErr;
}


/************************************************************************
 * ArchiveJunk - destroy the junk that's there
 ************************************************************************/
OSErr ArchiveJunk(TOCHandle tocH)
{
	long i;
	uLong threshTime = GMTDateTime() - GetRLong(JUNK_MAILBOX_EMPTY_DAYS) * 24 * 3600;
	uLong threshScore = GetRLong(JUNK_MAILBOX_EMPTY_THRESH);
	long count = 0;
	short button;
	Str255 dest;
	Boolean trash;
	Boolean nuke;
	FSSpec spec;
	
	if (!tocH) return fnfErr;
	
	SetPrefLong(PREF_LAST_JUNK_TRIM,GMTDateTime()/3600);
	
	for (i=0;i<(*tocH)->count;i++)
	{
		if ((*tocH)->sums[i].arrivalSeconds < threshTime && (*tocH)->sums[i].spamScore >= threshScore)
		{
			BoxSetSummarySelected(tocH,i,true);
			count++;
		}
		else
			BoxSetSummarySelected(tocH,i,false);
	}
	
	if (count)
	{
		// warning?
		if (JunkPrefBoxArchiveWarning())
		{
			button = ComposeStdAlert(Note,JUNK_EMPTY_WARNING,FILE_ALIAS_JUNK,count,FILE_ALIAS_JUNK,JUNK_MAILBOX_EMPTY_DAYS);
			if (CommandPeriod || button==kAlertStdAlertCancelButton) return userCanceledErr;
			if (button==kAlertStdAlertOtherButton)
				SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxArchiveWarning);
		}
		
		// progress dialog
		OpenProgress();
		ProgressMessage(kpTitle,ComposeRString(dest,TRIMMING_JUNK,FILE_ALIAS_JUNK));
				
		// where are we going?
		GetRString(dest,JUNK_MAILBOX_EMPTY_DEST);
		if (EqualStrRes(dest,FILE_ALIAS_JUNK) || EqualStrRes(dest,JUNK))
		{
			if (kAlertStdAlertOKButton==ComposeStdAlert(Stop,JUNK_JUNK_IS_BAD_TRIM_DEST,FILE_ALIAS_JUNK,FILE_ALIAS_TRASH))
			{
				SetPref(JUNK_MAILBOX_EMPTY_DEST,"");
				GetRString(dest,TRASH);
			}
			else
				return dupFNErr;
		}
		
		trash = EqualStrRes(dest,TRASH) || EqualStrRes(dest,FILE_ALIAS_TRASH);
		nuke = AmQuitting && trash && PrefIsSet(PREF_AUTO_EMPTY) || StringSame("\p-",dest);
		
		// if we're quitting and the trash will be emptied, we can just nuke now
		if (nuke)
			DoIterativeThingyLo(tocH,MESSAGE_DELETE_ITEM,optionKey|shiftKey,0,false);
		else if (trash)
			DoIterativeThingyLo(tocH,MESSAGE_DELETE_ITEM,0,0,false);
		else if (!BoxSpecByName(&spec,dest))
			MoveSelectedMessages(tocH,&spec,false);
		else
			ComposeStdAlert(Caution,JUNK_CANT_ARCHIVE,FILE_ALIAS_JUNK,FILE_ALIAS_JUNK,dest);
		
		// we may be on the hairy edge of life.  Write the new toc if so
		if (AmQuitting) WriteTOC(tocH);
	}
	
	// Handle IMAP Junk mailboxes as well 
	ArchiveIMAPJunk();
		
	return noErr;
}

/************************************************************************
 * ArchiveIMAPJunk - destroy junk in all IMAP Junk Mailboxes
 ************************************************************************/
static OSErr ArchiveIMAPJunk(void)
{
	long i;
	uLong threshTime;
	uLong threshScore;
	long count = 0;
	short button;
	Str255 dest;
	Boolean trash;
	Boolean nuke;
	Boolean archiveIMAP;
	FSSpec spec;
	TOCHandle tocH, toTocH;
	MailboxNodeHandle junkMBox;
	Boolean bWarned = false;
	Handle uids = nil;
	long sumNum;
	MailboxNodeHandle destMBox;
	PersHandle destPers;
		
	PushPers(CurPers);
	for (CurPers = PersList; CurPers; CurPers = (*CurPers)->next)
	{
		if (IsIMAPPers(CurPers))
		{
			threshTime = GMTDateTime() - GetRLong(JUNK_MAILBOX_EMPTY_DAYS) * 24 * 3600;
			threshScore = GetRLong(JUNK_MAILBOX_EMPTY_THRESH);
			
			SetPrefLong(PREF_LAST_JUNK_TRIM,GMTDateTime()/3600);
			
			// Find this personality's junk mailbox
			if (junkMBox = GetIMAPJunkMailbox(CurPers, false, true))
			{
				spec = (*junkMBox)->mailboxSpec;
				if (tocH = TOCBySpec(&spec))
				{
					// count the number of messages to move.  Ignore deleted messages
					for (i=0;i<(*tocH)->count;i++)
						if (((*tocH)->sums[i].arrivalSeconds < threshTime)
							&& ((*tocH)->sums[i].spamScore >= threshScore)
							&& (((*tocH)->sums[i].opts&OPT_DELETED)==0))
							count++;
					
					if (count)
					{
						// warning?
						if (!bWarned && JunkPrefBoxArchiveWarning())
						{
							button = ComposeStdAlert(Note,JUNK_EMPTY_WARNING,FILE_ALIAS_JUNK,count,FILE_ALIAS_JUNK,JUNK_MAILBOX_EMPTY_DAYS);
							if (CommandPeriod || button==kAlertStdAlertCancelButton) return userCanceledErr;
							if (button==kAlertStdAlertOtherButton)
								SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxArchiveWarning);
							bWarned = true;
						}
			
						// warn the user, we may have to go online for this
						if (Offline && GoOnline()) return(OFFLINE);
						
						// progress dialog
						OpenProgress();
						ProgressMessage(kpTitle,ComposeRString(dest,TRIMMING_JUNK,FILE_ALIAS_JUNK));
								
						// where are we going?
						toTocH = NULL;
						GetRString(dest,JUNK_MAILBOX_EMPTY_DEST);
						trash = EqualStrRes(dest,TRASH) || EqualStrRes(dest,FILE_ALIAS_TRASH);
						nuke = AmQuitting && trash && PrefIsSet(PREF_AUTO_EMPTY) || StringSame("\p-",dest);
						
						archiveIMAP = false;
						if (!nuke && !trash) 
						{
							toTocH = NULL;
							if (!BoxSpecByName(&spec,dest))
							{
								FSSpec imapSpec;	
								imapSpec = spec;
								imapSpec.parID = SpecDirId(&spec);
								
								// Look in the local IMAP tree first for the destination mailbox
								destMBox = LocateNodeBySpec((*CurPers)->mailboxTree, &imapSpec);
								
								// If not found, look in all IMAP trees for the destination mailbox
								if (destMBox == NULL)
									LocateNodeBySpecInAllPersTrees(&imapSpec, &destMBox, &destPers);
								
								if (destMBox)
								{
									toTocH = TOCBySpec(&(*destMBox)->mailboxSpec);
									archiveIMAP = true;
								}
								else
								{
									// If still not found, use the local mailbox ...
									toTocH = TOCBySpec(&spec);
								}
							}
						}
						
						// Actually archive messages now ...
						if (nuke || trash || (archiveIMAP && toTocH))
						{
							// build a list of UIDs to transfer ...
							uids = NuHandleClear(count*sizeof(unsigned long));
							if (uids)
							{
								for (sumNum=0;sumNum<(*tocH)->count && count;sumNum++)
									if (((*tocH)->sums[sumNum].arrivalSeconds < threshTime) && ((*tocH)->sums[sumNum].spamScore >= threshScore) && (((*tocH)->sums[sumNum].opts&OPT_DELETED)==0))
										((unsigned long *)(*uids))[--count] = ((*tocH)->sums[sumNum].uidHash);
					
								// transfer them in the foreground.
								if (archiveIMAP)
									IMAPTransferMessages(tocH, toTocH, uids, false, true);
								else
									IMAPDeleteMessages(tocH, uids, nuke, true, false, true);
							}
						}
						else if (toTocH)
						{
							// we're archiving to a local mailbox
							IMAPStartFiltering(tocH, true);	// display progress like we're filtering
							for (sumNum=0;sumNum<(*tocH)->count && count;sumNum++)
								if (((*tocH)->sums[sumNum].arrivalSeconds < threshTime) && ((*tocH)->sums[sumNum].spamScore >= threshScore) && (((*tocH)->sums[sumNum].opts&OPT_DELETED)==0))
									IMAPMoveMessageDuringFiltering(tocH, sumNum, toTocH, false, NULL);
							IMAPStopFiltering(true);
						}
						else
							ComposeStdAlert(Caution,JUNK_CANT_ARCHIVE,FILE_ALIAS_JUNK,FILE_ALIAS_JUNK,dest);
					}
					
					// We're done with this Junk mailbox
					tocH = NULL;
				}
			}
			// else
				// junk mailbox could not be found. 
		}
	}
	PopPers();
	
	return noErr;
}

/************************************************************************
 * SpecIsJunkSpec is a spec the junk mailbox?
 ************************************************************************/
Boolean SpecIsJunkSpec(FSSpecPtr spec)
{
	return IsRoot(spec) && EqualStrRes(spec->name,JUNK);
}

/************************************************************************
 * BoxIsJunkBox - is a tocH the junk mailbox?
 ************************************************************************/
Boolean BoxIsJunkBox(TOCHandle tocH)
{
	return tocH &&
					(
						(*tocH)->which==JUNK || 
						(*tocH)->imapTOC && IsIMAPJunkMailbox(TOCToMbox(tocH))
					);
}

/************************************************************************
 * PreexistingJunkWarning - warn the user about their existing junk mailbox
 *   I know it doesn't check for errors.  There's not much we could do
 *   except fail to launch, and that seems more drastic than just letting
 *   things proceed.
 ************************************************************************/
void PreexistingJunkWarning(FSSpecPtr spec)
{
	Boolean folder = FSpIsItAFolder(spec);
	short template = folder ? JUNK_PREEXISTING_FOLDER_WARNING:JUNK_PREEXISTING_WARNING;
	short button = ComposeStdAlert(Note,template,JUNK,JUNK);
	FSSpec newSpec;

	if (folder)
	{
		newSpec = *spec;
		GetRString(newSpec.name,JUNK_PREEXISTING_RENAME_NAME);
		UniqueSpec(&newSpec,100);
		FSpRename(spec,newSpec.name);
		FSpCreateResFile(spec,CREATOR,MAILBOX_TYPE,smSystemScript);
	}
	else if (button==kAlertStdAlertCancelButton)
	{
		FSSpec tocSpec;
		FSSpec newTOCSpec;
		
		// rename the mailbox
		newSpec = *spec;
		GetRString(newSpec.name,JUNK_PREEXISTING_RENAME_NAME);
		UniqueSpec(&newSpec,100);
		FSpCreateResFile(&newSpec,CREATOR,MAILBOX_TYPE,smSystemScript);
		FSpExchangeFiles(&newSpec,spec);
		
		// rename the toc file, if any
		Box2TOCSpec(spec,&tocSpec);
		if (FSpExists(&tocSpec))
		{
			Box2TOCSpec(&newSpec,&newTOCSpec);
			FSpRename(&tocSpec,newTOCSpec.name);
		}
	}
	else if (CommandPeriod || button==kAlertStdAlertOtherButton)
	{
		// disable the feature
		SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxHold);
	}
	// if the user says ok, we need do nothing special
	
	// Do not pass this way again
	SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxExistWarning);
}

/************************************************************************
 * JunkTOCCleanse - do some stuff to the junk box on open
 ************************************************************************/
void JunkTOCCleanse(TOCHandle tocH)
{
	short i = (*tocH)->count;
	short spamXferScore = GetRLong(JUNK_XFER_SCORE);
	
	while (i--)
	{
		if ((*tocH)->sums[i].spamBecause==0)
			JunkSetScore(tocH,i,JUNK_BECAUSE_XFER,spamXferScore);
	}
}


/************************************************************************
 * JunkRescan - rescan a mailbox for junk (not the junk mailbox)
 ************************************************************************/
OSErr JunkRescanBox ( TOCHandle tocH ) {
	OSErr err = noErr;
	short sumNum;
	uLong spamThresh = GetRLong ( JUNK_MAILBOX_THRESHHOLD );
	TLMHandle tList = NULL;
	
	UseFeature(featureJunk);
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );
	if ( tList != NULL ) {	// we have at least one plugin!
		ASSERT ( tocH != NULL );
		ASSERT ( tocH != GetJunkTOC ());

		// rescore any we need to
		for ( sumNum = (*tocH)->count; sumNum-- ;) {
			CycleBalls();

		//	Save off the selected flag
			(*tocH)->sums[sumNum].spareShort = (*tocH)->sums[sumNum].selected;

		//	If it's never been scored, or scored by a plugin, rescore it
			if ((*tocH)->sums[sumNum].spamScore == -1 || (*tocH)->sums[sumNum].spamBecause==JUNK_BECAUSE_PLUG)
				ScoreOneMessage ( tList, tocH, sumNum, kAutoScore );
			}		
		
	//	transfer the rescored ones
		err = MoveToJunk ( tocH, spamThresh, NULL );
		
	//	restore the selection
		for ( sumNum = (*tocH)->count; sumNum-- ;)
			BoxSetSummarySelected ( tocH, sumNum, (*tocH)->sums[sumNum].spareShort );

		DisposeHandle ((Handle) tList );
		}
	
	return err;
	}

/************************************************************************
 * JunkRescan - rescan a the junk mailbox for junk
 ************************************************************************/
OSErr JunkRescanJunkMailbox () {
	FilterPB fpb;
	short sumNum;
	uLong spamThresh = GetRLong ( JUNK_MAILBOX_THRESHHOLD );
	TOCHandle tocH = GetJunkTOC ();	
	TLMHandle tList = NULL;
	
//	Bail if there isn't a junk mailbox
	if ( tocH == NULL ) return paramErr;

	UseFeature(featureJunk);
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );

	if ( tList != NULL ) {	// we have at least one plugin!
	//	rescore any we need to
		for ( sumNum = (*tocH)->count; sumNum-- ;) {
			CycleBalls();

		//	Save off the selected flag
			(*tocH)->sums[sumNum].spareShort = (*tocH)->sums[sumNum].selected;

		//	If it's never been scored, or scored by a plugin, rescore it
			if ((*tocH)->sums[sumNum].spamScore == -1 || (*tocH)->sums[sumNum].spamBecause==JUNK_BECAUSE_PLUG)
				ScoreOneMessage ( tList, tocH, sumNum, kAutoScore );

			BoxSetSummarySelected ( tocH, sumNum, (*tocH)->sums[sumNum].spamScore < spamThresh );
			}		

		FilterSelectedMessages ( flkIncoming, tocH, &fpb);
		FilterPostprocess      ( flkIncoming, &fpb );
		
		// restore the selection
		for (sumNum=(*tocH)->count;sumNum--;)
			BoxSetSummarySelected ( tocH, sumNum, (*tocH)->sums[sumNum].spareShort );
		
		DisposeHandle ((Handle) tList );
		}
		
	return noErr;
	}

/**********************************************************************
 * JunkSetScore - set a message's junk score, 
 * 				handle virtual TOCs, too
 **********************************************************************/
OSErr JunkSetScore(TOCHandle tocH,short sumNum,short because,short score)
{
	TOCHandle realTOC;
	short	realSum;
	
	JunkSetScoreLo(tocH,sumNum,because,score);
	realTOC = GetRealTOC(tocH,sumNum,&realSum);
	if (realTOC && realTOC != tocH)
	{
		// do real mailbox also if working in virtual mailbox
		JunkSetScoreLo(realTOC,realSum,because,score);
		tocH = realTOC;
		sumNum = realSum;
	}
	SearchUpdateSum(tocH, sumNum, tocH, (*tocH)->sums[sumNum].serialNum, false, false);	//	Notify search window
	return noErr;
}

/************************************************************************
 * JunkSetScoreLo - change the score of some junk
 ************************************************************************/
OSErr JunkSetScoreLo(TOCHandle tocH,short sumNum,short because,short score)
{
	short junkThresh = GetRLong(JUNK_MAILBOX_THRESHHOLD);
	Boolean nowJunk = score >= junkThresh;
	Boolean wasJunk = (*tocH)->sums[sumNum].spamScore >= junkThresh;
	short oldBecause = (*tocH)->sums[sumNum].spamBecause;
	
	ASSERT(sumNum>=0 && sumNum<(*tocH)->count);
	if (sumNum<0 || sumNum >= (*tocH)->count) return fnfErr;
	
	(*tocH)->sums[sumNum].spamScore = score;
	(*tocH)->sums[sumNum].spamBecause = because;
	InvalTocBox(tocH,sumNum,blJunk);
	
	// Update stats here
	if (because==JUNK_BECAUSE_WHITE)
		UpdateNumStatWithTime(kStatWhiteList,1,(*tocH)->sums[sumNum].seconds);
	else if (because==JUNK_BECAUSE_PLUG)
	{
		if (nowJunk)
			UpdateNumStatWithTime(kStatScoredJunk,1,(*tocH)->sums[sumNum].seconds);
		else
			UpdateNumStatWithTime(kStatScoredNotJunk,1,(*tocH)->sums[sumNum].seconds);
	}
	else if (because==JUNK_BECAUSE_USER)
	{
		if (nowJunk && !wasJunk)
		{
			// something missed it.  What?
			if (oldBecause == JUNK_BECAUSE_WHITE)
				UpdateNumStatWithTime(kStatFalseWhiteList,1,(*tocH)->sums[sumNum].seconds);
			else if (oldBecause == JUNK_BECAUSE_PLUG)
				UpdateNumStatWithTime(kStatFalseNegatives,1,(*tocH)->sums[sumNum].seconds);
		}
		else if (!nowJunk && wasJunk)
		{
			// Did we misjunk it?
			if (oldBecause == JUNK_BECAUSE_PLUG)
			{
				UpdateNumStatWithTime(kStatFalsePositives,1,(*tocH)->sums[sumNum].seconds);
			}
		}
	}
	return noErr;
}

/************************************************************************
 * JunkIntro - introduce the junk feature
 ************************************************************************/
#define jiNoItem 2
short JunkIntro(void)
{
	extern ModalFilterUPP DlgFilterUPP;
	short item=0;
	
	if (ETLCountTranslators(EMSF_JUNK_MAIL))
	{
		SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNeedIntro);
		Nag (JUNK_INTRO_DLOG, nil, NagHitReturnItem, DlgFilterUPP, false, (uLong)&item);
		if (CommandPeriod || item==jiNoItem)
			SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxHold);
		ASSERT(item!=0);
	}

	return item;
}

/************************************************************************
 * NagHitReturnItem - hit proc that stuffs the item hit into the refcon, used as pointer to short
 ************************************************************************/
Boolean NagHitReturnItem(EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon)
{
	*(short*)dialogRefcon = itemHit;
	return (true);
}

/************************************************************************
 * JunkTrimOK - is it time to trim junk?
 ************************************************************************/
Boolean JunkTrimOK(void)
{
	long daysSince = (GMTDateTime()/3600-GetPrefLong(PREF_LAST_JUNK_TRIM)+8)/24;
	short interval = GetRLong(JUNK_TRIM_INTERVAL);

	// If we're quitting, follow the interval strictly
	// we also follow the interval strictly if the user has a hyper interval set
	if (AmQuitting || GetRLong(JUNK_MAILBOX_EMPTY_DAYS)==1)
		return interval && daysSince >= interval;
		
	// If the user might be in the middle of something, don't be quite so cheeky
	else
		return interval && daysSince >= 3*interval;
}

/************************************************************************
 * JunkReassignKeys - reassign command keys from filter to junk
 ************************************************************************/
void JunkReassignKeys(Boolean switchem)
{
	MenuHandle messageMH = GetMHandle(MESSAGE_MENU);
	MenuHandle specialMH = GetMHandle(SPECIAL_MENU);
	short key;
	
	if (switchem)
	{
		GetItemCmd(specialMH,AdjustSpecialMenuItem(SPECIAL_FILTER_ITEM),&key);
		if (key)
		{
			SetItemCmd(specialMH,AdjustSpecialMenuItem(SPECIAL_FILTER_ITEM),0);
			SetItemCmd(messageMH,MESSAGE_JUNK_ITEM,key);
			SetItemCmd(messageMH,MESSAGE_NOTJUNK_ITEM,key);
			SetMenuItemModifiers(messageMH,MESSAGE_NOTJUNK_ITEM,kMenuOptionModifier);
		}
	}
	else
	{
		GetItemCmd(messageMH,MESSAGE_JUNK_ITEM,&key);
		if (key)
		{
			SetItemCmd(messageMH,MESSAGE_JUNK_ITEM,0);
			SetItemCmd(specialMH,AdjustSpecialMenuItem(SPECIAL_FILTER_ITEM),key);
			SetItemCmd(messageMH,MESSAGE_NOTJUNK_ITEM,0);
		}
	}
	if (switchem) ClearPrefBit(PREF_JUNK_MAILBOX,bJunkPrefSwitchCmdJ);
	else SetPrefBit(PREF_JUNK_MAILBOX,bJunkPrefSwitchCmdJ);
}

/************************************************************************
 * JunkItemEnable - should the junk menu item be enabled?
 ************************************************************************/
Boolean JunkItemsEnable(MyWindowPtr win,Boolean not)
{
	WindowPtr winWP = GetMyWindowWindowPtr(win);
	TOCHandle tocH;
	short sumNum;
	FSSpec spec;
	
	if (!win || !winWP) return false;
	Zero(spec);
	
	switch (GetWindowKind(winWP))
	{
		case CBOX_WIN: return false;
		case COMP_WIN: return false;
		case MESS_WIN:
			if (JunkPrefAlwaysEnable()) return true;
			tocH = (*Win2MessH(win))->tocH;
			return not==BoxIsJunkBox(tocH);
		
		case MBOX_WIN:
			tocH = Win2TOC(win);
			if (!(win->hasSelection || MyWinHasSelection(win))) return false;
			if (JunkPrefAlwaysEnable()) return true;
			if (!(*tocH)->virtualTOC) return not==BoxIsJunkBox(tocH);
			
			// the non-trivial case here
 			for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--)
 			{
 				if ((*tocH)->sums[sumNum].selected)
 				{
 					if ((*tocH)->sums[sumNum].flags&FLAG_OUT) return false;
 					spec = GetMailboxSpec(tocH,sumNum);
 					if (not!=SpecIsJunkSpec(&spec)) return false;
 				}
 			}
 			
 			// if we found any selected messages, we'll have
 			// put their spec in here, and so we should enable
 			return *spec.name!=0;
 	}
 	
 	// failure
 	return false;
}

#pragma mark ** Call Junk Plugins **

/************************************************************************
 * ScoreOneMessage -
 * Score a message using each of the installed plugins
 * Store the high score (and the signature of the plugin)
 * into the TOC for the mailbox.
 ************************************************************************/
void ScoreOneMessage ( TLMHandle tList, TOCHandle tocH, short sumNum, TJunkType howToScore ) {
	OSErr	err = noErr;
	short highScore = -2;
	short highSig = 0;
	Handle text;
	
	ASSERT ( tList != NULL );
	ASSERT ( HandleCount ( tList ) > 0 );

	// don't score plugins if we've been asked not to
	if (!CanScoreToc(tocH))
		return;
	
	// Load the message ...
	if ((*tocH)->imapTOC)
		CacheIMAPMessageForSpamWatch(tocH, sumNum);
	else
		CacheMessage ( tocH, sumNum );

	if ( text = (*tocH)->sums[sumNum].cache ) {
		Handle	headers, body;
		char 	*ret;
		long	bodyOffset, textSz, hdrSz, envSz;
		
		ASSERT ( *text != NULL );
	//	Split the message into headers and body
	//	Error detection here would be a good thing!!

	//	The first line is always a UUCP envelope, followed by a <CR>
	//	It's not part of the message, so we strip it out.
		ret = strchr ( *text, '\015' );
		ASSERT ( ret != NULL );
		envSz = ret + 1 - *text;		// how much of the buffer to throw away

	//	The headers are seperated from the body by a <CR><CR> sequence
		bodyOffset	= BodyOffset ( text );		// first byte of the body
		textSz		= GetHandleSize ( text );	// total size of the message
		hdrSz		= bodyOffset - envSz;		// bytes in the header
		if ( bodyOffset == textSz ) {
		//	couldn't find "\n\n" - empty message body?
			body = NULL;
			}
		else {
			long bodySz = textSz - bodyOffset;
			ASSERT ( bodySz > 0 );			
			body = NewHandleClear ( bodySz + 1 );
			BlockMoveData ( *text + bodyOffset, *body, bodySz );			
			}
		
		ASSERT ( hdrSz > 0 );
		headers = NewHandleClear ( hdrSz + 1 );
		BlockMoveData ( *text + envSz, *headers, hdrSz );

	//	For each translator in the list, score the message
		if ( err == noErr ) {
			emsJunkScore	junkScore;
			emsResultStatus	junkStatus;
			emsMessageInfo	messageInfo;
			emsHeaderData	messageHdr;
			emsMIMEtype		mimeInfo;
			emsTranslator	transInfo;
		    emsJunkInfo		junkInfo;
			TLMPtr			aModule;
			short			i, numPlugins;
			
			Zero ( messageHdr );	messageHdr.size  = sizeof ( messageHdr );
			Zero ( messageInfo );	messageInfo.size = sizeof ( messageInfo );
			Zero ( mimeInfo );		mimeInfo.size	 = sizeof ( mimeInfo );
			Zero ( junkInfo );		junkInfo.size	 = sizeof ( junkInfo );
			messageInfo.fromAddressStatus	= emsAddressNotChecked;	// !!! for now
			messageInfo.header				= &messageHdr;
			messageInfo.textType			= &mimeInfo;
			messageInfo.text				= body;
			
			junkInfo.context	= EMSFJUNK_SCORE_ON_ARRIVAL;
			
			messageHdr.rawHeaders = headers;
		//	Need to fill in more of mimeInfo and messageInfo
		//	And the fromAddressStatus, too!
			HLock ((Handle) tList );
			
			numPlugins = HandleCount ( tList ) - 1;	// don't call the sentinel!
			for ( i = 0; i < numPlugins; ++i ) {
				aModule = &(*tList) [ i ];
				Zero ( junkScore );		junkScore.size	= sizeof ( junkScore );
				Zero ( junkStatus );	junkStatus.size = sizeof ( junkStatus );
				Zero ( transInfo );		transInfo.size	= sizeof ( transInfo );
				
				transInfo.id = aModule->id;
				
				if ( howToScore != kAutoScore ) {
				//	Fill in the old score info here
					junkScore.score		= (*tocH)->sums[sumNum].spamScore;
					junkInfo.context	= howToScore == kUserMarkJunk ? EMSFJUNK_MARK_IS_JUNK : EMSFJUNK_MARK_NOT_JUNK;
					junkInfo.pluginID	= (*tocH)->sums[sumNum].spamBecause;
					(void) ETLMarkJunk (  aModule, &transInfo, &junkInfo, &messageInfo, &junkScore, &junkStatus );
					}
				else {
					if ( noErr == (err = ETLScoreJunk ( aModule, &transInfo, &junkInfo, &messageInfo, &junkScore, &junkStatus ))) {
						if ( junkScore.score > highScore && highScore != -1 ) {
							highScore = junkScore.score;
							highSig = transInfo.id;	// !!! Is this right ??
							}
						else if ( junkScore.score == -1 ) {
							highScore = -1;
							}
						}
					}
				}
			HUnlock ((Handle) tList );
			}
		
		if ( body != NULL )		DisposeHandle ( body );
		if ( headers != NULL )	DisposeHandle ( headers );
		}

	// We want to record scores of zero or -1 that come out
	// of plug-ins.  The only time we don't put a score
	// on a message is when no plug-in ran successfully on it
	if ( highScore > -2 && howToScore == kAutoScore )
		// we're not recording id's right now
		// JunkSetScore ( tocH, sumNum, highSig, highScore );
		JunkSetScore ( tocH, sumNum, JUNK_BECAUSE_PLUG, MAX(0,highScore) );
		
	// cleanup after CacheIMAPMessage.  
	// Zap the cache, unless this message still needs to be filtered.
	if ((*tocH)->imapTOC)
		if (((*tocH)->sums[sumNum].flags&FLAG_UNFILTERED) == 0)
			ZapHandle((*tocH)->sums[sumNum].cache);
	}

/************************************************************************
 * JunkScoreBox - Score all the messages in a mailbox
 ************************************************************************/
void JunkScoreBox ( TOCHandle tocH, short first, short last, Boolean rescore ) {
	TLMHandle tList = NULL;
	short i;
	Boolean whiteCheck = JunkPrefWhiteList ();
	Boolean midCheck = JunkPrefWhiteListReplies();
	
	ASSERT ( tocH != NULL );
	UseFeature(featureJunk);
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );

	if ( first < 0 )
		first = 0;
	if ( last < 0 || last >= (*tocH)->count )
		last = (*tocH)->count - 1;
	
//	We want to run through all these messages even if we have no junk plugins
//	because we might want to mark some for whitelisting.
	for ( i = first; i <= last; ++i ) 
		if (rescore || !(*tocH)->sums[i].spamBecause) {
			if (midCheck)
			{
				if (WhiteListByMID(tocH,i))
				{
					JunkSetScore(tocH,i,JUNK_BECAUSE_WHITE,0);
					continue;
				}
			}
			if ( whiteCheck ) {
				EnsureFromHash ( tocH, i );
				if ((*tocH)->sums [ i ].fromHash != kNoMessageId )
					if ( HashAppearsInAliasFile ((*tocH)->sums[i].fromHash, nil )) {
						JunkSetScore ( tocH, i , JUNK_BECAUSE_WHITE, 0 );
						continue;
						}
				}
		//	Otherwise		
			CycleBalls ();
			if ( tList != NULL )
				ScoreOneMessage ( tList, tocH, i, kAutoScore );
			}

	if ( tList != NULL )
		DisposeHandle ((Handle) tList );
	}

/************************************************************************
 * JunkScoreIMAPBox - Score the messages in an IMAP mailbox.  
 *
 * Like JunkScoreBox, but handles IMAP nuances.  Specify first and last
 *	for a range to junk, (-1) for each to score the entire mailbox, and
 *	bUnfilteredOnly to do unfiltered only.
 *
 * Very rarely would one want to score an entire IMAP mailbox, certainly
 *	not after a mailcheck as we do in POP.  This routine is cirtually
 *	identical to its POP equivalent, but allows for scoring of messages
 *	about to be filtered as well.
 ************************************************************************/
void JunkScoreIMAPBox ( TOCHandle tocH, short first, short last, Boolean bUnfilteredOnly ) {
	TLMHandle tList = NULL;
	short i;
	Boolean whiteCheck = JunkPrefWhiteList ();
	
	UseFeature(featureJunk);
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );
	if ( tList != NULL ) {
		if ( first < 0 )
			first = 0;
		if ( last < 0 || last >= (*tocH)->count )
			last = (*tocH)->count - 1;
		
		for ( i = first; i <= last; ++i ) {
		
		// only do unfiltered messages if the caller has requested that
			if (bUnfilteredOnly && (((*tocH)->sums[i].flags&FLAG_UNFILTERED)==0))
				continue;
		
			if ( whiteCheck ) {
				EnsureFromHash ( tocH, i );
				if ((*tocH)->sums [ i ].fromHash != kNoMessageId )
				if ( HashAppearsInAliasFile ((*tocH)->sums[i].fromHash, nil )) {
					JunkSetScore ( tocH, i , JUNK_BECAUSE_WHITE, 0 );
					continue;
					}
				}
		//	Otherwise		
			CycleBalls ();
			ScoreOneMessage ( tList, tocH, i, false );
			}

		DisposeHandle ((Handle) tList );
		}
	}
	
/************************************************************************
 * JunkScoreSelected - Score the selected messages in a mailbox
 ************************************************************************/
void JunkScoreSelected ( TOCHandle tocH ) {
	TLMHandle tList = NULL;
	short i;
	Boolean whiteCheck = JunkPrefWhiteList ();
	
	ASSERT ( tocH != NULL );
	UseFeature(featureJunk);
	(void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL );

//	We want to run through all these messages even if we have no junk plugins
//	because we might want to mark some for whitelisting.
	for ( i = 0; i < (*tocH)->count; ++i ) {
		if ((*tocH)->sums [ i ].selected ) {
			if ( whiteCheck ) {
				EnsureFromHash ( tocH, i );
				if ((*tocH)->sums [ i ].fromHash != kNoMessageId )
					if ( HashAppearsInAliasFile ((*tocH)->sums[i].fromHash, nil )) {
						JunkSetScore ( tocH, i , JUNK_BECAUSE_WHITE, 0 );
						continue;
						}
				}
	//	Otherwise		
			CycleBalls ();
			if ( tList != NULL )
				ScoreOneMessage ( tList, tocH, i, kAutoScore );
			}
		}

	if ( tList != NULL )
		DisposeHandle ((Handle) tList );
	}


//	Can we actually score mail?
//	Really a shorthand for "are there any plugins that can score mail installed"?
Boolean	CanScoreJunk () {
	TLMHandle tList = NULL;
	OSErr err = ETLListAllTranslators( &tList, EMSF_JUNK_MAIL );
	if ( tList != NULL )
		DisposeHandle ((Handle) tList );
	return err == noErr;
	}

/************************************************************************
 * CanScoreToc - Can we score this tocH?
 *	False 	if it's an IMAP mailbox and bJunkPrefIMAPNoRunPlugins is set
 *	True 	in all other cases
 ************************************************************************/
Boolean CanScoreToc( TOCHandle tocH )
{
	Boolean bScore = true;
	PersHandle pers;
	
	if (tocH == NULL)
		return (false);
		
	pers = TOCToPers(tocH);
	if ((pers != NULL) && IsIMAPPers(pers))
	{
		PushPers(pers);
		bScore = !JunkPrefIMAPNoRunPlugins();
		PopPers();
	}
		
	return (bScore);
}

/************************************************************************
 * JunkMoveIMAPMessages - go through a virtual toc and move all selected
 *	IMAP messages to the junk or inbox folder
 *
 *	This is fairly tricky as 
 *		(a) the virtual toc contains messages in different IMAP mailboxes
 *		(b) the messages being moved may go to different destinations
 *		(c) IMAP transfers suck in that they can take a long time
 *		(d) there may be POP messages in the mailbox we need to ignore
 *
 *	This routine sets up one transfer operation per imap mailbox 
 *	represented in the virtual toc.
 ************************************************************************/
void JunkMoveIMAPMessages(TOCHandle tocH, Boolean isJunk)
{
	short i, j;
	TOCHandle realToc, destTocH;
	short realSum;
	MailboxNodeHandle dest;
	PersHandle pers;
	Accumulator a;
	Handle uids;
	
	for (i=0; i < (*tocH)->count; i++)
	{
		if (((*tocH)->sums[i].selected) && (((*tocH)->sums[i].opts&OPT_DELETED)==0))
		{
			// initialize
			destTocH = NULL;
			
			// is this selected message an IMAP message?
			realToc = GetRealTOC(tocH,i,&realSum);
			if (realToc && (*realToc)->imapTOC)
			{
				pers = TOCToPers(realToc);
				if (pers)
				{
					// where is this particular IMAP message going?
					if (!isJunk)
					{
						// we're not junking something in the junk mailbox
						if (BoxIsJunkBox(realToc))
						{
							// Put the message back into the proper inbox.
							if ((dest = LocateInboxForPers(pers)) != NULL)
								destTocH = TOCBySpec(&(*dest)->mailboxSpec);
						}	
					}
					else
					{
						// we're junking something in something other than the junk box
						if (!BoxIsJunkBox(realToc))
						{
							// Get the proper JUNK mailbox
							dest = GetIMAPJunkMailbox(pers, true, false);
							if (dest && (dest != TOCToMbox(realToc)))
								destTocH = TOCBySpec(&(*dest)->mailboxSpec);
						}
					}
				}
			}
			
			// build a list of messages to transfer
			AccuInit(&a);
			
			// properly handle all of the messages in this real toc
			for (j = i; j < (*tocH)->count; j++)
			{
				if (((*tocH)->sums[j].selected) && (((*tocH)->sums[j].opts&OPT_DELETED)==0))	
				{
					TOCHandle rt;
					short rs;
					
					rt = GetRealTOC(tocH, j, &rs);
					// is this message  in the same IMAP mailbox we're currently considering?
					if (rt && (rt == realToc) && ((*rt)->imapTOC))
					{
						// are we moving this message?
						if (destTocH)
						{
							// add it to the list of messages to transfer
							AccuAddPtr(&a, &((*rt)->sums[rs].uidHash), sizeof(long));
						
							// invalidate the summary in the search window
							(*tocH)->sums[j].u.virtualMess.virtualMBIdx = -1;
							InvalSum(tocH, j);
						}
				
						// do not reprocess this message
						(*tocH)->sums[j].selected = false;
					}
				}
			}
			
			// move the messages if they're going anywhere.
			if (destTocH)
			{
				AccuTrim(&a);
				uids = a.data;
				a.data = NULL;
				IMAPTransferMessages(realToc, destTocH, uids, false, false);
			}
			AccuZap(a);			
		}
	}
}

/************************************************************************
 * WhiteListByMID - are any of a message's references on a list of messages we sent?
 ************************************************************************/
Boolean WhiteListByMID(TOCHandle tocH, short sumNum)
{
	Handle text;
	
	if (SumFlagIsSet(tocH,sumNum,FLAG_KNOWS_ME)) return true;
	if (CacheMessage(tocH,sumNum)) return false;
	if (!OutgoingMIDList.offset) return false;
	
	if (text=(*tocH)->sums[sumNum].cache)
	{
		Handle references = nil;
		HNoPurge(text);
		if (!HandleHeadGetIdText(text,HeaderStrn+REFERENCES_HEAD,&references))
		{
			BinAddrHandle addresses=nil;
			Tr(references," \t\r\n",",,,,");
			if (!SuckAddresses(&addresses,references,false,false,false,nil))
			{
				UPtr address;
				
				for (address = *addresses; *address; address += *address+2)
				{
					if (AccuFindLong(&OutgoingMIDList,Hash(address))>=0)
					{
						SetSumFlag(tocH,sumNum,FLAG_KNOWS_ME);
						break;
					}
				}
				ZapHandle(addresses);
			}
			ZapHandle(references);
		}
		HPurge(text);
	}
	
	// did we win?
	return SumFlagIsSet(tocH,sumNum,FLAG_KNOWS_ME);
}
