/* 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 "schizo.h"
#define FILE_NUM 82
/* Copyright (c) 1996 by QUALCOMM Incorporated */
static int PersCompare(PersHandle *p1, PersHandle *p2);
static void PersSwap(PersHandle *n1, PersHandle *n2);
static void UpdatePersList(void);

#pragma segment Main
typedef struct
{
	PropToken property;
	PersHandle pers;
} PersSpecifier;

/**********************************************************************
 * PersAnyPasswords - do we have any passwords?
 **********************************************************************/
Boolean PersAnyPasswords(void)
{
	PersHandle pers;
	
	for (pers=PersList;pers;pers=(*pers)->next)
		if (*(*pers)->password || *(*pers)->secondPass) return(True);
	return(False);
}
#pragma segment Schizo

/**********************************************************************
 * PersFillPw - grab passwords for a personality
 **********************************************************************/
OSErr PersFillPw(PersHandle pers,uLong whichOnes)
{
	Str255 s;
	OSErr err = noErr;
	Str127 uName, hName, persName;
	
	*s = 0;
	
#ifdef HAVE_KEYCHAIN
	if (PrefIsSet(PREF_KEYCHAIN))
	{
		err = FindPersKCPassword(pers,s,sizeof(s));
		if (err==userCanceledErr) return err;
		PSCopy((*pers)->password,s);
	}
#endif //HAVE_KEYCHAIN

	if (!*(*pers)->password)
	{
		GetPassStuff(persName,uName,hName);
		GetPassword(persName,uName,hName,s,sizeof(s),ENTER);
		(*pers)->dirty = true;
		PSCopy((*pers)->password,s);
		if (*s)
		{
			if (PrefIsSet(PREF_SAVE_PASSWORD)) PersSavePw(pers);
			PSCopy((*pers)->password,s);	// savepw wipes it out; put it back, for now
		}
	}
	
	return(*(*pers)->password ? noErr:userCanceledErr);
}

/**********************************************************************
 * PersSavePw - save passwords for a personality
 **********************************************************************/
OSErr PersSavePw(PersHandle pers)
{
	OSErr err = noErr;
	Str31 pass, secondPass;
	Str255 scratch;
	
#ifdef HAVE_KEYCHAIN
	if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
	{
		err = UpdatePersKCPassword(pers);
		WriteZero((*pers)->password,sizeof((*pers)->password));
	}
	else
#endif //HAVE_KEYCHAIN
	if (pers==PersList)
	{
		PSCopy(pass,(*PersList)->password);
		PSCopy(secondPass,(*PersList)->secondPass);
 		if (!EqualString(pass,GetPref(scratch,PREF_PASS_TEXT),True,True) ||
	     	!EqualString(secondPass,GetPref(scratch,PREF_AUXPW),True,True))
		{
			SetPref(PREF_PASS_TEXT,pass);
			SetPref(PREF_AUXPW,secondPass);
			MyUpdateResFile(SettingsRefN);
		}
	}
	else
	{
		err = PersSave(pers);
	}
	return(err);
}

/**********************************************************************
 * PersSaveAll - save all personalities
 **********************************************************************/
OSErr PersSaveAll(void)
{
	OSErr err=noErr;
	PersHandle pers;
	Boolean multiplePersonalities = false;
	
	for (pers=PersList;pers;pers=(*pers)->next)
		if (err=PersSave(pers)) break;
	return(err);
}
	

/**********************************************************************
 * 
 **********************************************************************/
OSErr PersSave(PersHandle pers)
{
	OSErr err = noErr;
	Str63 scratch;
	PersHandle oldCur = CurPers;
	Str63 oldPass;
	
	*oldPass = 0;
	
	if ((*pers)->dirty && (*pers)->resId)
	{
		// swap personality into current
		CurPers = pers;
		// make sure we don't unintentionally save the password
		if (!PrefIsSet(PREF_SAVE_PASSWORD))
		{
			PSCopy(oldPass,(*pers)->password);
			WriteZero(LDRef(pers)->password,sizeof((*pers)->password));
			UL(pers);
		}
#ifdef HAVE_KEYCHAIN
		else if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
		{
			WriteZero((*pers)->password,sizeof((*pers)->password));
		}
#endif //HAVE_KEYCHAIN

		// remove old, add new
		ZapResource(PERS_RTYPE,(*pers)->resId);
		AddMyResource((Handle)pers,PERS_RTYPE,(*pers)->resId,"");
		if (!(err=ResError()))
		{
			err = MyUpdateResFile(SettingsRefN);
			DetachResource((Handle)pers);
		}
		if (!err) (*pers)->dirty = False;
		else
		{
			PSCopy(scratch,(*pers)->name);
			Aprintf(OK_ALRT,err,PERS_SAVE_ERR,scratch);
		}
		
		// put stuff back
		CurPers = oldCur;
		if (*oldPass) PSCopy((*pers)->password,oldPass);
	}
	return(err);
}

/**********************************************************************
 * PersNew - create a new personality
 **********************************************************************/
PersHandle PersNew(void)
{
	PersHandle pers, newPers=nil;
	short n;
	short resEnd;
	Str63 untitled;

	UseFeature (featureMultiplePersonalities);
		
	// find an unused name
	for (n=PersCount()+1;;n++)
	{
		GetRString(untitled,UNTITLED);
		PLCat(untitled,n);
		if (!FindPersByName(untitled)) break;
	}
	
	// Allocate the last couple bytes of resource id's
	n = 0;
	do
	{
		n++;
		Bytes2Hex(((UPtr)&n)+1,1,(UPtr)&resEnd);
		for (pers=(*PersList)->next;pers;pers=(*pers)->next)
			if ((*pers)->resEnd==resEnd) break;
	}
	while (pers);
	
	// out of personalities?
	if (n>100) {WarnUser(YOU_ARE_PSYCHO,0);return(nil);}
	
	// create the personality
	if (newPers=NewZH(Personality))
	{
		PersSetName(newPers,untitled);
		n = MyUniqueID(PERS_RTYPE);
		(*newPers)->resId = n;
		(*newPers)->resEnd = resEnd;
		PersSave(newPers);
		LL_Queue(PersList,newPers,(PersHandle));
		PersZapResources('STR ',resEnd);
		PushPers(CurPers);
		CurPers = newPers;
		SetStrOverride(PREF3_STRN+PREF_STUPID_USER%100,"");
		SetStrOverride(PREF3_STRN+PREF_STUPID_HOST%100,"");
		PopPers();
		UpdatePersList();
	}
	else
		WarnUser(MEM_ERR,MemError());
	return(newPers);
}

/**********************************************************************
 * PersDelete - kill a personality
 **********************************************************************/
OSErr PersDelete(PersHandle pers)
{
	if (HasFeature (featureMultiplePersonalities))
	{
		if (pers==PersList) return(errAENotModifiable);
		UseFeature (featureMultiplePersonalities);
		
#ifdef HAVE_KEYCHAIN
		if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
		{
			DeletePersKCPassword(pers);
		}
#endif //HAVE_KEYCHAIN
		
		LL_Remove(PersList,pers,(PersHandle));
		
		ZapSettingsResource(PERS_RTYPE,(*pers)->resId);
		PersZapResources('STR ',(*pers)->resEnd);
		UpdatePersList();
		AuditPersDelete((*pers)->persId);
	}
	return(noErr);
}


/**********************************************************************
 * PersZapResources - zap resources for a personality
 **********************************************************************/
void PersZapResources(OSType type,short resEnd)
{
	Handle res;
	
	// Kill old resources
	type = PERS_TYPE(type,resEnd);
	while (res=Get1IndResource(type,1)) {RemoveResource(res); if (!ResError()) DisposeHandle(res);}
}

/**********************************************************************
 * PersType - find an alternate type for a resource based on the personality
 **********************************************************************/
OSType PersType(OSType theType,PersHandle pers)
{
	if (pers && (*pers)->persId)
		theType = (theType&0xffff0000) | (*pers)->resEnd;
	return(theType);
}

/**********************************************************************
 * FindPersById - find a personality, given a personality id
 **********************************************************************/
PersHandle FindPersById(uLong persId)
{
	PersHandle pers;
	for (pers=PersList;pers;pers=(*pers)->next)
		if ((*pers)->persId==persId) break;
	
	return(pers);
}

/**********************************************************************
 * FindPersById - find a personality, given a personality name
 **********************************************************************/
PersHandle FindPersByName(PStr name)
{
	if (EqualStrRes(name,DOMINANT)) return(PersList);
	return(FindPersById(Hash(name)));
}

#pragma segment Ends
/**********************************************************************
 * InitPersonalities - read personalities into the personality queue
 **********************************************************************/
void InitPersonalities(void)
{
	PersHandle pers = NewZH(Personality);
	Str63 dom;
	uLong ticks = TickCount();
	long hash;
	short n;
	Boolean multiplePersonalities = false;
	
	/*
	 * first, the dominant personality
	 */
	if (!pers) DieWithError(MEM_ERR,MemError());
	LL_Queue(PersList,pers,(PersHandle));
	GetRString(dom,DOMINANT);
	PSCopy((*pers)->name,dom);
#ifdef	IMAP
	(*pers)->mailboxTree = 0;
	(*pers)->imapRefresh = 0;
#endif
	
	CurPers = pers;
	
	/*
	 * Next, any others
	 */
	if (HasFeature (featureMultiplePersonalities)) {
		for (n=1;pers=(PersHandle)Get1IndResource(PERS_RTYPE,n);n++)
		{
			if (!*pers || !GetHandleSize(pers))
			{
				RemoveResource(pers);
				if (!ResError()) n--;	// removed one, retry
				ZapHandle(pers);
			}
			else if ((*pers)->version > PERS_VERS)
				ReleaseResource((Handle)pers);
			else
			{
				DetachResource((Handle)pers);
				SetHandleBig((Handle)pers,sizeof(Personality));
				if (MemError()) DieWithError(MEM_ERR,MemError());
				(*pers)->next = nil;
				LL_Queue(PersList,pers,(PersHandle));
				(*pers)->dirty = False;
				(*pers)->checkTicks = 0;
				(*pers)->proxy = nil;
#ifdef	IMAP
				(*pers)->mailboxTree = 0;
				(*pers)->imapRefresh = 0;
#endif
				// The popd resources shouldn't be here if this is
				// an imap personality.  Kill them.
				if (IsIMAPPers(pers))
				{
					ZapResource(PERS_POPD_TYPE(pers),POPD_ID);
					ZapResource(PERS_POPD_TYPE(pers),DELETE_ID);
					ZapResource(PERS_POPD_TYPE(pers),FETCH_ID);
				}
				
				if (pers!=PersList)
				{
					PSCopy(dom,(*pers)->name);
					hash = Hash(dom);
					ASSERT(hash==(*pers)->persId);
					(*pers)->persId = hash;
					
					// make sure we commit this particular setting
					PushPers(pers);
					GetPrefNoDominant(dom,PREF_SUBMISSION_PORT);
					if (!*dom) SetPref(PREF_SUBMISSION_PORT,NoStr);
					PopPers();					
				}
			}
		}
		if (n > 1)
			UseFeature (featureMultiplePersonalities);
		UpdatePersList();
	}

	CurPers = PersList;	// let's make sure, shall we?
	
	return;
}

/**********************************************************************
 * PushPers - push current personality on the stack and set
 **********************************************************************/
void PushPers(PersHandle newCur)
{
	if (PersStack || !StackInit(sizeof(PersHandle),&PersStack))
		StackPush(&CurPers,PersStack);
	if (newCur) CurPers = newCur;
}

/**********************************************************************
 * PopPers - pop the top personality off the stack and set
 **********************************************************************/
void PopPers(void)
{
	ASSERT(PersStack && (*PersStack)->elCount);
	if (PersStack) StackPop(&CurPers,PersStack);
}

/**********************************************************************
 * PersSetName - set the name of a personality
 **********************************************************************/
OSErr PersSetName(PersHandle pers,PStr name)
{
	uLong hash = Hash(name);
	PersHandle oldPers;
	
	if (oldPers=FindPersByName(name))
	{
		if (pers==oldPers) return(noErr);
		else return(WarnUser(USED_PERSONALITY,dupFNErr));
	}
	PSCopy((*pers)->name,name);
	if ((*pers)->persId)
		AuditPersRename((*pers)->persId,hash);
	else
		AuditPersCreate(hash);
	(*pers)->persId = hash;
	(*pers)->dirty = True;
	if (HasFeature (featureMultiplePersonalities)) {
		if (pers != PersList)
			UseFeature (featureMultiplePersonalities);
		UpdatePersList();
	}
	
	// update IMAP mailboxes with new pers id
	if (IsIMAPPers(pers))
		IMAPPersIDChanged(pers, (*pers)->mailboxTree);	
	
	return(noErr);
}

/**********************************************************************
 * SetPersProperty - set a personality's properties via ae's
 **********************************************************************/
OSErr SetPersProperty(AEDescPtr token,AEDescPtr descP)
{
	short err=noErr;
	Str255 str;
	PersSpecifier persSpec;
	
	AEGetDescData(token,&persSpec,sizeof(persSpec));
	switch (persSpec.property.propertyId)
	{
		case pName:
			if (HasFeature (featureMultiplePersonalities) && persSpec.pers!=PersList)
			{
				UseFeature (featureMultiplePersonalities);
				GetAEPStr(str,descP);
				PersSetName(persSpec.pers,str);
				NotifyPersonalitiesWin();
			}
			else
				return(errAENotModifiable);
			break;
		
		case formUniqueID:
			return(errAENotModifiable);
			break;
		
		default:
			err = errAENoSuchObject;
			break;
	}
	return(err);
}

/**********************************************************************
 * 
 **********************************************************************/
OSErr GetPersProperty(AEDescPtr token,AppleEvent *reply,long refCon)
{
#pragma unused(refCon)
	OSErr err;
	Str255 s;
	PersSpecifier persSpec;
	
	AEGetDescData(token,&persSpec,sizeof(persSpec));	
	switch (persSpec.property.propertyId)
	{
		case pName:
			err = AEPutPStr(reply,keyAEResult,PCopy(s,(*persSpec.pers)->name));
			break;
		case formUniqueID:
			err = AEPutLong(reply,keyAEResult,(*persSpec.pers)->persId);
			break;
		default:
			err = errAENoSuchObject;
			break;
	}
	return(err);
}

/**********************************************************************
 * AECreatePersonality - create a personality
 **********************************************************************/
OSErr AECreatePersonality(DescType theClass,AEDescPtr inContainer,AppleEvent *event, AppleEvent *reply)
{
	OSErr err;
	PersHandle pers;
	long n;
	AEDesc obj;
	
	if (!HasFeature (featureMultiplePersonalities))
		return (paramErr);
		
	UseFeature (featureMultiplePersonalities);
	
	NullADList(&obj,nil);
	
	n = PersCount()+1;
	
	if (pers = PersNew())
	{
		err = AEPersObj(pers,&obj);
		if (!err) err = AEPutParamDesc(reply,keyAEResult,&obj);
		if (!err) NotifyPersonalitiesWin();
	}
	else err = MemError() ? MemError() : errAEEventNotHandled;
	
	DisposeADList(&obj,nil);
	return(err);
}

/************************************************************************
 * AEPersObj - build an object for a personality
 ************************************************************************/
OSErr AEPersObj(PersHandle pers,AEDescPtr obj)
{
	OSErr err;
	long n;
	AEDesc desc, root;
	
	if (!HasFeature (featureMultiplePersonalities))
		return (paramErr);
	
	if (pers != PersList)
		UseFeature (featureMultiplePersonalities);

	NullADList(&desc,obj,&root,nil);
	
	n = Pers2Index(pers);
		
	if (!(err = AECreateDesc(typeChar,(*pers)->name+1,*(*pers)->name,&desc)))
	{
		err = CreateObjSpecifier(cEuPersonality,&root,formName,&desc,False,obj);
	}
	
	DisposeADList(&desc,&root,nil);
	if (err) DisposeADList(obj,nil);
	return(err);
}

/************************************************************************
 * AESetPers - set a personality to an object
 ************************************************************************/
OSErr AESetPers(TOCHandle tocH,short sumNum,AEDescPtr descP)
{
	AEDesc token;
	OSErr err;
	PersHandle	pers;

	if (!HasFeature (featureMultiplePersonalities))
		return (paramErr);
	
	err = AEResolve(descP,kAEIDoMinimum,&token);
	if (err) return(err);
	if (token.descriptorType!=cEuPersonality) return(errAEWrongDataType);
	AEGetDescData(&token,&pers,sizeof(pers));
	if (pers != PersList)
		UseFeature (featureMultiplePersonalities);
	return SetPers(tocH,sumNum,pers,True);
}


/**********************************************************************
 * PersCount - how many personalities?
 **********************************************************************/
long PersCount(void)
{
	long n=0;
	PersHandle pers;
	
	for (pers=PersList;pers;pers=(*pers)->next) n++;
	return(n);
}


/**********************************************************************
 * DisposePersonalities - kill the current personality list
 **********************************************************************/
void DisposePersonalities(void)
{
	PersHandle pers;
	
	while (pers=PersList)
	{
		LL_Remove(PersList,pers,(PersHandle));
		ZapHandle(pers);
	}
	CurPers = PersList = nil;
}

/**********************************************************************
 * SetPers - set the personality of a message
 **********************************************************************/
OSErr SetPers(TOCHandle tocH,short sumNum,PersHandle pers,Boolean stationery)
{
	WindowPtr	messWinWP;
	MessHandle messH = (*tocH)->sums[sumNum].messH;
	Boolean opened = messH==nil;
	Str255 addr;
	OSErr err = noErr;
	uLong sigId;
	ControlHandle cntl;
	Boolean redirected = ((*tocH)->sums[sumNum].opts & OPT_REDIRECTED)!=0;
	
	if ((*tocH)->sums[sumNum].persId==(*pers)->persId) return(noErr);	// nothing to do

	if (pers!=PersList)
		UseFeature (featureMultiplePersonalities);
	
	(*tocH)->sums[sumNum].persId = (*pers)->persId;
	TOCSetDirty(tocH,true);
	
	SetBGColorsByPers(messH);
	
#ifdef THREADING_ON
	if ((*tocH)->which==OUT && ((*tocH)->sums[sumNum].state!=SENT && (*tocH)->sums[sumNum].state!=BUSY_SENDING))
#else
	if ((*tocH)->which==OUT && (*tocH)->sums[sumNum].state!=SENT)
#endif
	{
		if (stationery && !redirected)  // if stationery not allowed, don't copy sig
		{
			PushPers(pers);
			sigId = SigValidate(GetPrefLong(PREF_SIGFILE));
			PopPers();
			SetSig(tocH,sumNum,sigId);
		}
		
		if (opened)
		{
			(void) OpenComp(tocH,sumNum,nil,nil,False,False);
			messH = (*tocH)->sums[sumNum].messH;
		}
		if (!messH) return(errAENoSuchObject);
		messWinWP = GetMyWindowWindowPtr((*messH)->win);
		PushPers(pers);
		GetReturnAddr(addr,True);
		PeteCalcOff(TheBody);
		if (redirected)
			err = RedirectAnnotation(messH);
		else
			err = SetMessText(messH,FROM_HEAD,addr+1,*addr);

		if (stationery) ApplyDefaultStationery((*messH)->win,False,False);
		PeteKillUndo(TheBody);
		PopPers();
		if (opened)
		{
			err = !SaveComp((*messH)->win) ? err : True;
			CloseMyWindow(messWinWP);
		}
		else
		{
			if (HasFeature (featureMultiplePersonalities)) {
				CheckNone(GetMHandle(PERS_HIER_MENU));
				if (cntl=FindControlByRefCon((*messH)->win,'pers'))
				{
					short item = pers==PersList ? 1 : Pers2Index(pers)+2;
					if (item!=GetControlValue(cntl))
					{
						SetControlMaximum(cntl,PersCount()+1);
						SetControlValue(cntl,item);
					}
				}
			}
		}
	}
	return(err);
}

/**********************************************************************
 * SetBGColorsByPers - set the bg colors by the personality of a message
 **********************************************************************/
void SetBGColorsByPers(MessHandle messH)
{
	// change the bg color?
	if (messH)
	{
		PETEHandle pte;
		RGBColor color;
		
		PushPers(PERS_FORCE(MESS_TO_PERS(messH)));
		GetRColor(&color,BACK_COLOR);
		PopPers();
		if (!SAME_COLOR(color,(*messH)->win->backColor))
		{
			Rect r = {-REAL_BIG/2,-REAL_BIG/2,REAL_BIG/2,REAL_BIG/2};
			(*messH)->win->backColor = color;
			InvalWindowRect(GetMyWindowWindowPtr((*messH)->win),&r);
			for (pte=(*messH)->win->pteList;pte;pte=PeteNext(pte))
				PETESetDefaultBGColor(PETE,pte,&color);
		}
	}
}

/**********************************************************************
 * PersCheckTicks - find the earliest check interval
 **********************************************************************/
uLong PersCheckTicks(void)
{
	PersHandle pers;
	uLong ticks=0;
	
	for (pers=PersList;pers;pers=(*pers)->next)
		if ((*pers)->autoCheck)
		{
			if (!ticks) ticks = (*pers)->checkTicks+(*pers)->ivalTicks;
			else ticks = MIN(ticks,(*pers)->checkTicks+(*pers)->ivalTicks);
		}
	return(ticks);
}

/**********************************************************************
 * PersSkipNextCheck - skip the next check.
 **********************************************************************/
void PersSkipNextCheck(void)
{
	PersHandle pers;
	uLong ticks=0;
	
	// Reset all mail checks that were about to happen
	for (pers=PersList;pers;pers=(*pers)->next)
		if ((*pers)->autoCheck)
		{
			// if this check was about to happen, pretend it did.
			if ((*pers)->checkTicks+(*pers)->ivalTicks <= TickCount()+45*60)
				(*pers)->checkTicks = TickCount();
		}
}

/**********************************************************************
 * PersSetAutoCheck - set the autocheck flag
 **********************************************************************/
void PersSetAutoCheck(void)
{
	long ival;
	Str255 s;
	Boolean is;
	
	PushPers(CurPers);
	for (CurPers=PersList;CurPers;CurPers=(*CurPers)->next)
	{
		if (PrefIsSet(PREF_AUTO_CHECK) && (ival=GetPrefLong(PREF_INTERVAL)) && *GetPOPPref(s))
		{
			(*CurPers)->autoCheck = True;
			(*CurPers)->ivalTicks = TICKS2MINS * ival;
		}
		else
		{
			(*CurPers)->autoCheck = False;
			(*CurPers)->ivalTicks = 0;
		}
		is = *GetPOPPref(s) && s[1]=='!';
		(*CurPers)->uupcIn = is?1:0;
		is = *GetPref(s,PREF_SMTP) && s[1]=='!';
		(*CurPers)->uupcOut = is?1:0;
	}
	PopPers();
}

/**********************************************************************
 * Pers2Index - return the index of a personality
 **********************************************************************/
short Pers2Index(PersHandle goalPers)
{
	short index=0;
	PersHandle pers;
	
	if (!goalPers) return(-1);
	for (pers=PersList;pers && pers!=goalPers;pers=(*pers)->next) index++;
	return(pers?index:-1);
}

/**********************************************************************
 * Index2Pers - return the indexed personality
 **********************************************************************/
PersHandle Index2Pers(short n)
{
	PersHandle pers;
	
	for (pers=PersList;n-- && pers;pers=(*pers)->next);
	return(pers);
}
	

/************************************************************************
 * CheckPers - put a check next to the current message's personality
 ************************************************************************/
void CheckPers(MyWindowPtr win,Boolean all)
{
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	MenuHandle mh;
	MessHandle messH;
	TOCHandle tocH;
	short n;
	Boolean out;
	short kind;
	PersHandle pers, messPers;
	
	//No personalities menu in Light
	if (!HasFeature (featureMultiplePersonalities))
		return;
	
	mh = GetMHandle(PERS_HIER_MENU);
	messH = win?(MessHandle) GetMyWindowPrivateData(win):nil;
	tocH = win?(TOCHandle) GetMyWindowPrivateData(win):nil;
	out=False;
	kind = winWP ? GetWindowKind(winWP) : 0;
	
	/*
	 * uncheck old one
	 */
	CheckNone(mh);
	EnableItem(mh,0);
	if (all) return;
	
	if (IsMyWindow(winWP))
	{
		/*
		 * set n to current state
		 */
		n = 0;
		if (kind==MESS_WIN || kind==COMP_WIN)
		{
			messPers = PERS_FORCE(MESS_TO_PERS(messH));
			out = kind==COMP_WIN;
		}
		else if ((kind==MBOX_WIN || kind==CBOX_WIN) && win->hasSelection)
		{
			messPers = PERS_FORCE(FindPersById((*tocH)->sums[FirstMsgSelected(tocH)].persId));
			out = kind==CBOX_WIN;
		}
		else
		{
			DisableItem(mh,0);
			return;
		}
		
		/*
		 * check the appropriate item
		 */
		if (messPers==PersList) n = 1;
		else
		{
			n = 3;
			for (pers=(*PersList)->next;pers;pers=(*pers)->next)
				if (pers==messPers) break;
				else n++;
		}
		SetItemMark(mh,n,checkMark);
	}
	CalcMenuSize(mh);
}

/************************************************************************
 * UpdatePersList - sort personalities and rebuild personalities menu
 ************************************************************************/
static void UpdatePersList(void)
{
	PersHandle pers,**hPersList;
	short	count;
	
	//No personalities menu in Light
	if (!HasFeature (featureMultiplePersonalities))
		return;
	
	count = PersCount();
	if (count <= 2)
		;	//	Not enough items to sort
	else if (hPersList=NuHandleClear((count+2)*sizeof(PersHandle)))
	{
		PersHandle	*pPersList;
		short				idx;
		
		pPersList=LDRef(hPersList);
		for (pers=PersList;pers;pers=(*pers)->next)
			*pPersList++ = pers;
		*pPersList = nil;	//	Used later for rebuilding linked list
		QuickSort((void *)*hPersList,sizeof(PersHandle),1,count-1,(void*)PersCompare,(void*)PersSwap);
		
		//	Rebuild personalities list
		pPersList=*hPersList;
		for(idx=0;idx<count;idx++)
			(*pPersList[idx])->next = pPersList[idx+1];
	}
	BuildPersMenu();
}

/************************************************************************
 * PersCompare - compare two personality names
 ************************************************************************/
static int PersCompare(PersHandle *p1, PersHandle *p2)
{
	if (*p1==0) return 1;
	if (*p2==0) return -1;
	return(StringComp((**p1)->name,(**p2)->name));
}


/************************************************************************
 * PersSwap - swap two personality handles
 ************************************************************************/
static void PersSwap(PersHandle *n1, PersHandle *n2)
{
	PersHandle temp = *n2;
	*n2 = *n1;
	*n1 = temp;
}
	

enum 
{
	kOKItem = 1,
	kCancelItem,
	kListItem,
	kIconItem,
	kPromptItem
};

/************************************************************************
 * PersChoose - choose a personality from a dialog list
 ************************************************************************/
PersHandle PersChoose(StringPtr prompt)
{
	MyWindowPtr	dlogWin;
	DialogPtr dlog;
	short res;
	extern ModalFilterUPP DlgFilterUPP;
	PersHandle pers,result;
	short type;
	Rect r;
	ControlHandle cntl;
	ListHandle list;
	Point c;
	Str63 name;

	// Dominant personality only in Light
	if (!HasFeature (featureMultiplePersonalities))
		return (PersList);
		
	if (PersCount()==1) return PersList;	//	Only 1 personality, return it
	
	UseFeature (featureMultiplePersonalities);
	
	SetDialogFont (SmallSysFontID());
	dlogWin = GetNewMyDialog(CHOOSE_PERSONALITY_DLOG,nil,nil,InFront);
	SetDialogFont(0);
	dlog = GetMyWindowDialogPtr(dlogWin);
	if (!dlog) { WarnUser(MEM_ERR,ResError()); return nil; }
	
	SetPort_(GetMyWindowCGrafPtr(dlogWin));
	ConfigFontSetup(dlogWin);
	
	if (prompt)
		//	Set prompt
		SetDIText(dlog,kPromptItem,prompt);		

	// add personality list
	GetDialogItem(dlog,kListItem,&type,(void*)&cntl,&r);
	if (list = LCGetList(cntl))
	{
		(*list)->selFlags = lOnlyOne;

		//	The following may not have been set up appropriately on LNew
		c.h = r.right - GROW_SIZE - 1;
		c.v = dlogWin->vPitch + 2;
		LCellSize(c, list);
		(*list)->indent.v = dlogWin->vPitch-2;

		c.h = c.v = 0;
		LAddRow(PersCount(),0,list);
		for (pers=PersList;pers;pers=(*pers)->next)
		{
			PSCopy(name,(*pers)->name);
			LSetCell(name+1,*name,c,list);
			c.v++;
		}
	}
	
	ReplaceAllControls(dlog);
	
	// put up the alert
	StartMovableModal(dlog);
	ShowWindow(GetDialogWindow(dlog));
	SetMyCursor(arrowCursor);
	
	do
	{
		MovableModalDialog(dlog,DlgFilterUPP,&res);
	}
	while (res>kCancelItem);

	result = (res!=kCancelItem && FirstSelected(&c,list)) ? Index2Pers(c.v) : nil;
	// close
	EndMovableModal(dlog);
	DisposeDialog_(dlog);
	
	return result;
}
