/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

#include "msgCore.h"
#include "xp_core.h"
#include "nsMsgSearchCore.h"
#include "nsMsgUtils.h"
#include "nsIMsgDatabase.h"
#include "nsMsgSearchTerm.h"
#include "nsMsgBodyHandler.h"
#include "nsMsgResultElement.h"

//---------------------------------------------------------------------------
// nsMsgSearchTerm specifies one criterion, e.g. name contains phil
//---------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//-------------------- Implementation of nsMsgSearchTerm -----------------------
//-----------------------------------------------------------------------------


typedef struct
{
	nsMsgSearchAttribute	attrib;
	const char			*attribName;
} nsMsgSearchAttribEntry;

nsMsgSearchAttribEntry SearchAttribEntryTable[] =
{
    {nsMsgSearchAttrib::Subject,		"subject"},
    {nsMsgSearchAttrib::Sender,		"from"},
    {nsMsgSearchAttrib::Body,		"body"},
    {nsMsgSearchAttrib::Date,		"date"},
    {nsMsgSearchAttrib::Priority,	"priority"},
    {nsMsgSearchAttrib::MsgStatus,	"status"},	
    {nsMsgSearchAttrib::To,			"to"},
    {nsMsgSearchAttrib::CC,			"CC"},
    {nsMsgSearchAttrib::ToOrCC,		"to or CC"}
};

// Take a string which starts off with an attribute
// return the matching attribute. If the string is not in the table, then we can conclude that it is an arbitrary header
nsresult NS_MsgGetAttributeFromString(const char *string, PRInt16 *attrib)
{
	if (NULL == string || NULL == attrib)
		return NS_ERROR_NULL_POINTER;
	PRBool found = PR_FALSE;
	for (int idxAttrib = 0; idxAttrib < (int)(sizeof(SearchAttribEntryTable) / sizeof(nsMsgSearchAttribEntry)); idxAttrib++)
	{
		if (!PL_strcasecmp(string, SearchAttribEntryTable[idxAttrib].attribName))
		{
			found = PR_TRUE;
			*attrib = SearchAttribEntryTable[idxAttrib].attrib;
			break;
		}
	}
	if (!found)
		*attrib = nsMsgSearchAttrib::OtherHeader; // assume arbitrary header if we could not find the header in the table
	return NS_OK;      // we always succeed now
}

nsresult NS_MsgGetStringForAttribute(PRInt16 attrib, const char **string)
{
	if (NULL == string)
		return NS_ERROR_NULL_POINTER;
	PRBool found = PR_FALSE;
	for (int idxAttrib = 0; idxAttrib < (int)(sizeof(SearchAttribEntryTable) / sizeof(nsMsgSearchAttribEntry)); idxAttrib++)
	{
		// I'm using the idx's as aliases into MSG_SearchAttribute and 
		// MSG_SearchOperator enums which is legal because of the way the
		// enums are defined (starts at 0, numItems at end)
		if (attrib == SearchAttribEntryTable[idxAttrib].attrib)
		{
			found = PR_TRUE;
			*string = SearchAttribEntryTable[idxAttrib].attribName;
			break;
		}
	}
	// we no longer return invalid attribute. If we cannot find the string in the table, 
	// then it is an arbitrary header. Return success regardless if found or not
//	return (found) ? SearchError_Success : SearchError_InvalidAttribute;
	return NS_OK;
}

typedef struct
{
	nsMsgSearchOperator	op;
	const char			*opName;
} nsMsgSearchOperatorEntry;

nsMsgSearchOperatorEntry SearchOperatorEntryTable[] =
{
	{nsMsgSearchOp::Contains,	"contains"},
    {nsMsgSearchOp::DoesntContain,"doesn't contain"},
    {nsMsgSearchOp::Is,           "is"},
    {nsMsgSearchOp::Isnt,		"isn't"},
    {nsMsgSearchOp::IsEmpty,		"is empty"},
	{nsMsgSearchOp::IsBefore,    "is before"},
    {nsMsgSearchOp::IsAfter,		"is after"},
    {nsMsgSearchOp::IsHigherThan, "is higher than"},
    {nsMsgSearchOp::IsLowerThan,	"is lower than"},
    {nsMsgSearchOp::BeginsWith,  "begins with"},
	{nsMsgSearchOp::EndsWith,	"ends with"}
};

nsresult NS_MsgGetOperatorFromString(const char *string, PRInt16 *op)
{
	if (NULL == string || NULL == op)
		return NS_ERROR_NULL_POINTER;
	
	PRBool found = PR_FALSE;
	for (unsigned int idxOp = 0; idxOp < sizeof(SearchOperatorEntryTable) / sizeof(nsMsgSearchOperatorEntry); idxOp++)
	{
		// I'm using the idx's as aliases into MSG_SearchAttribute and 
		// MSG_SearchOperator enums which is legal because of the way the
		// enums are defined (starts at 0, numItems at end)
		if (!PL_strcasecmp(string, SearchOperatorEntryTable[idxOp].opName))
		{
			found = PR_TRUE;
			*op = SearchOperatorEntryTable[idxOp].op;
			break;
		}
	}
	return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
}

nsresult NS_MsgGetStringForOperator(PRInt16 op, const char **string)
{
	if (NULL == string)
		return NS_ERROR_NULL_POINTER;
	PRBool found = PR_FALSE;
	for (unsigned int idxOp = 0; idxOp < sizeof(SearchOperatorEntryTable) / sizeof(nsMsgSearchOperatorEntry); idxOp++)
	{
		// I'm using the idx's as aliases into MSG_SearchAttribute and 
		// MSG_SearchOperator enums which is legal because of the way the
		// enums are defined (starts at 0, numItems at end)
		if (op == SearchOperatorEntryTable[idxOp].op)
		{
			found = PR_TRUE;
			*string = SearchOperatorEntryTable[idxOp].opName;
			break;
		}
	}

	return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
}

void NS_MsgGetUntranslatedStatusName (uint32 s, nsCString *outName)
{
	char *tmpOutName = NULL;
#define MSG_STATUS_MASK (MSG_FLAG_READ | MSG_FLAG_REPLIED | MSG_FLAG_FORWARDED | MSG_FLAG_NEW)
	PRUint32 maskOut = (s & MSG_STATUS_MASK);

	// diddle the flags to pay attention to the most important ones first, if multiple
	// flags are set. Should remove this code from the winfe.
	if (maskOut & MSG_FLAG_NEW) 
		maskOut = MSG_FLAG_NEW;
	if ( maskOut & MSG_FLAG_REPLIED &&
		 maskOut & MSG_FLAG_FORWARDED ) 
		maskOut = MSG_FLAG_REPLIED|MSG_FLAG_FORWARDED;
	else if ( maskOut & MSG_FLAG_FORWARDED )
		maskOut = MSG_FLAG_FORWARDED;
	else if ( maskOut & MSG_FLAG_REPLIED ) 
		maskOut = MSG_FLAG_REPLIED;

	switch (maskOut)
	{
	case MSG_FLAG_READ:
		tmpOutName = "read";
		break;
	case MSG_FLAG_REPLIED:
		tmpOutName = "replied";
		break;
	case MSG_FLAG_FORWARDED:
		tmpOutName = "forwarded";
		break;
	case MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED:
		tmpOutName = "replied and forwarded";
		break;
	case MSG_FLAG_NEW:
		tmpOutName = "new";
		break;
	default:
		// This is fine, status may be "unread" for example
        break;
	}

	if (tmpOutName)
		*outName = tmpOutName;
}


PRInt32 NS_MsgGetStatusValueFromName(char *name)
{
	if (PL_strcmp("read", name))
		return MSG_FLAG_READ;
	if (PL_strcmp("replied", name))
		return MSG_FLAG_REPLIED;
	if (PL_strcmp("forwarded", name))
		return MSG_FLAG_FORWARDED;
	if (PL_strcmp("replied and forwarded", name))
		return MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED;
	if (PL_strcmp("new", name))
		return MSG_FLAG_NEW;
	return 0;
}


// Needed for DeStream method.
nsMsgSearchTerm::nsMsgSearchTerm()
{
}

#if 0
nsMsgSearchTerm::nsMsgSearchTerm (
	nsMsgSearchAttribute attrib, 
	nsMsgSearchOperator op, 
	nsMsgSearchValue *val,
	PRBool booleanAND,
	char * arbitraryHeader) 
{
	m_operator = op;
	m_booleanOp = (booleanAND) ? nsMsgSearchBooleanOp::BooleanAND : nsMsgSearchBooleanOp::BooleanOR;
	if (attrib == nsMsgSearchAttrib::OtherHeader && arbitraryHeader)
		m_arbitraryHeader = arbitraryHeader;
	m_attribute = attrib;

	nsMsgResultElement::AssignValues (val, &m_value);
}
#endif

nsMsgSearchTerm::nsMsgSearchTerm (
	nsMsgSearchAttribute attrib, 
	nsMsgSearchOperator op, 
	nsMsgSearchValue *val,
	nsMsgSearchBooleanOperator boolOp,
	char * arbitraryHeader) 
{
	m_operator = op;
	m_attribute = attrib;
	m_booleanOp = boolOp;
	if (attrib == nsMsgSearchAttrib::OtherHeader && arbitraryHeader)
		m_arbitraryHeader = arbitraryHeader;
	nsMsgResultElement::AssignValues (val, &m_value);
}



nsMsgSearchTerm::~nsMsgSearchTerm ()
{
	if (IS_STRING_ATTRIBUTE (m_attribute))
		PR_Free(m_value.u.string);
}

// Perhaps we could find a better place for this?
// Caller needs to free.
/* static */char *nsMsgSearchTerm::EscapeQuotesInStr(const char *str)
{
	int	numQuotes = 0;
	for (const char *strPtr = str; *strPtr; strPtr++)
		if (*strPtr == '"')
			numQuotes++;
	int escapedStrLen = PL_strlen(str) + numQuotes;
	char	*escapedStr = (char *) PR_Malloc(escapedStrLen + 1);
	if (escapedStr)
	{
		char *destPtr;
		for (destPtr = escapedStr; *str; str++)
		{
			if (*str == '"')
				*destPtr++ = '\\';
			*destPtr++ = *str;
		}
		*destPtr = '\0';
	}
	return escapedStr;
}


nsresult nsMsgSearchTerm::OutputValue(nsCString &outputStr)
{
	if (IS_STRING_ATTRIBUTE(m_attribute))
	{
		PRBool	quoteVal = PR_FALSE;
		// need to quote strings with ')' - filter code will escape quotes
		if (PL_strchr(m_value.u.string, ')'))
		{
			quoteVal = PR_TRUE;
			outputStr += "\"";
		}
		if (PL_strchr(m_value.u.string, '"'))
		{
			char *escapedString = nsMsgSearchTerm::EscapeQuotesInStr(m_value.u.string);
			if (escapedString)
			{
				outputStr += escapedString;
				PR_Free(escapedString);
			}

		}
		else
		{
			outputStr += m_value.u.string;
		}
		if (quoteVal)
			outputStr += "\"";
	}
	else
	{
	    switch (m_attribute)
		{
		case nsMsgSearchAttrib::Date:
		{
			PRExplodedTime exploded;
			PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded);

			// wow, so tm_mon is 0 based, tm_mday is 1 based.
			char dateBuf[100];
			PR_FormatTimeUSEnglish (dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
			outputStr += dateBuf;
			break;
		}
		case nsMsgSearchAttrib::MsgStatus:
		{
			nsCAutoString status;
			NS_MsgGetUntranslatedStatusName (m_value.u.msgStatus, &status);
			outputStr += status;
			break;
		}
		case nsMsgSearchAttrib::Priority:
		{
			nsAutoString priority;
			NS_MsgGetUntranslatedPriorityName( m_value.u.priority, 
											 &priority);
			outputStr += nsCAutoString(priority);
			break;
		}
		default:
			NS_ASSERTION(PR_FALSE, "trying to output invalid attribute");
			break;
		}
	}
	return NS_OK;
}

nsresult nsMsgSearchTerm::EnStreamNew (nsCString &outStream)
{
	const char	*attrib, *operatorStr;
	nsCAutoString	outputStr;
	nsresult	ret;

	ret = NS_MsgGetStringForAttribute(m_attribute, &attrib);
	if (ret != NS_OK)
		return ret;

	if (m_attribute == nsMsgSearchAttrib::OtherHeader)  // if arbitrary header, use it instead!
	{
		outputStr = "\"";
		outputStr += m_arbitraryHeader;
		outputStr += "\"";
	}
	else
		outputStr = attrib;

	outputStr += ',';

	ret = NS_MsgGetStringForOperator(m_operator, &operatorStr);
	if (ret != NS_OK)
		return ret;

	outputStr += operatorStr;
	outputStr += ',';

	OutputValue(outputStr);
	outStream = outputStr;
	return NS_OK;
}

// fill in m_value from the input stream.
nsresult nsMsgSearchTerm::ParseValue(char *inStream)
{
	if (IS_STRING_ATTRIBUTE(m_attribute))
	{
		PRBool	quoteVal = PR_FALSE;
		while (IS_SPACE(*inStream))
			inStream++;

		// need to remove pair of '"', if present
		if (*inStream == '"')
		{
			quoteVal = PR_TRUE;
			inStream++;
		}
		int valueLen = PL_strlen(inStream);
		if (quoteVal && inStream[valueLen - 1] == '"')
			valueLen--;

		m_value.u.string = (char *) PR_Malloc(valueLen + 1);
		PL_strncpy(m_value.u.string, inStream, valueLen + 1);
		m_value.u.string[valueLen] = '\0';
	}
	else
	{
	    switch (m_attribute)
		{
		case nsMsgSearchAttrib::Date:
			PR_ParseTimeString (inStream, PR_FALSE, &m_value.u.date);
			break;
		case nsMsgSearchAttrib::MsgStatus:
			m_value.u.msgStatus = NS_MsgGetStatusValueFromName(inStream);
			break;
		case nsMsgSearchAttrib::Priority:
			NS_MsgGetPriorityFromString(inStream, &m_value.u.priority);
			break;
		default:
			NS_ASSERTION(PR_FALSE, "invalid attribute parsing search term value");
			break;
		}
	}
	m_value.attribute = m_attribute;
	return NS_OK;
}

// find the operator code for this operator string.
nsMsgSearchOperator nsMsgSearchTerm::ParseOperator(char *inStream)
{
	PRInt16				operatorVal;
	nsresult		err;

	while (IS_SPACE(*inStream))
		inStream++;

	char *commaSep = PL_strchr(inStream, ',');

	if (commaSep)
		*commaSep = '\0';

	err = NS_MsgGetOperatorFromString(inStream, &operatorVal);
	return (nsMsgSearchOperator) operatorVal;
}

// find the attribute code for this comma-delimited attribute. 
nsMsgSearchAttribute nsMsgSearchTerm::ParseAttribute(char *inStream)
{
	nsCAutoString			attributeStr;
	PRInt16				attributeVal;
	nsresult		err;

	while (IS_SPACE(*inStream))
		inStream++;

	// if we are dealing with an arbitrary header, it may be quoted....
	PRBool quoteVal = PR_FALSE;
	if (*inStream == '"')
	{
		quoteVal = PR_TRUE;
		inStream++;
	}

	char *separator;
	if (quoteVal)      // arbitrary headers are quoted...
		separator = PL_strchr(inStream, '"');
	else
		separator = PL_strchr(inStream, ',');
	
	if (separator)
		*separator = '\0';

	err = NS_MsgGetAttributeFromString(inStream, &attributeVal);
	nsMsgSearchAttribute attrib = (nsMsgSearchAttribute) attributeVal;
	
	if (attrib == nsMsgSearchAttrib::OtherHeader)  // if we are dealing with an arbitrary header....
		m_arbitraryHeader =  inStream;
	
	return attrib;
}

// De stream one search term. If the condition looks like
// condition = "(to or CC, contains, r-thompson) AND (body, doesn't contain, fred)"
// This routine should get called twice, the first time
// with "to or CC, contains, r-thompson", the second time with
// "body, doesn't contain, fred"

nsresult nsMsgSearchTerm::DeStreamNew (char *inStream, PRInt16 /*length*/)
{
	char *commaSep = PL_strchr(inStream, ',');
	m_attribute = ParseAttribute(inStream);  // will allocate space for arbitrary header if necessary
	if (!commaSep)
		return NS_ERROR_INVALID_ARG;
	char *secondCommaSep = PL_strchr(commaSep + 1, ',');
	if (commaSep)
		m_operator = ParseOperator(commaSep + 1);
	if (secondCommaSep)
		ParseValue(secondCommaSep + 1);
	return NS_OK;
}


void nsMsgSearchTerm::StripQuotedPrintable (unsigned char *src)
{
	// decode quoted printable text in place

	unsigned char *dest = src;
	int srcIdx = 0, destIdx = 0;
	
	while (src[srcIdx] != 0)
	{
		if (src[srcIdx] == '=')
		{
			unsigned char *token = &src[srcIdx];
			unsigned char c = 0;

			// decode the first quoted char
			if (token[1] >= '0' && token[1] <= '9')
				c = token[1] - '0';
			else if (token[1] >= 'A' && token[1] <= 'F')
				c = token[1] - ('A' - 10);
			else if (token[1] >= 'a' && token[1] <= 'f')
				c = token[1] - ('a' - 10);
			else
			{
				// first char after '=' isn't hex. copy the '=' as a normal char and keep going
				dest[destIdx++] = src[srcIdx++]; // aka token[0]
				continue;
			}
			
			// decode the second quoted char
			c = (c << 4);
			if (token[2] >= '0' && token[2] <= '9')
				c += token[2] - '0';
			else if (token[2] >= 'A' && token[2] <= 'F')
				c += token[2] - ('A' - 10);
			else if (token[2] >= 'a' && token[2] <= 'f')
				c += token[2] - ('a' - 10);
			else
			{
				// second char after '=' isn't hex. copy the '=' as a normal char and keep going
				dest[destIdx++] = src[srcIdx++]; // aka token[0]
				continue;
			}

			// if we got here, we successfully decoded a quoted printable sequence,
			// so bump each pointer past it and move on to the next char;
			dest[destIdx++] = c; 
			srcIdx += 3;

		}
		else
			dest[destIdx++] = src[srcIdx++];
	}

	dest[destIdx] = src[srcIdx]; // null terminate
}

// Looks in the MessageDB for the user specified arbitrary header, if it finds the header, it then looks for a match against
// the value for the header. 
nsresult nsMsgSearchTerm::MatchArbitraryHeader (nsMsgSearchScopeTerm *scope, PRUint32 offset, PRUint32 length /* in lines*/, const char *charset,
														nsIMsgDBHdr *msg, nsIMsgDatabase* db, const char * headers, 
														PRUint32 headersSize, PRBool ForFiltering, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;
	*pResult = PR_FALSE;
	nsresult err = NS_OK;
	PRBool result;

	nsMsgBodyHandler * bodyHandler = new nsMsgBodyHandler (scope, offset,length, msg, db, headers, headersSize, ForFiltering);
	if (!bodyHandler)
		return NS_ERROR_OUT_OF_MEMORY;

	bodyHandler->SetStripHeaders (PR_FALSE);

	if (MatchAllBeforeDeciding())
		result = PR_TRUE;
	else
		result = PR_FALSE;

	const int kBufSize = 512; // max size of a line??
	char * buf = (char *) PR_Malloc(kBufSize);
	if (buf)
	{
		PRBool searchingHeaders = PR_TRUE;
		while (searchingHeaders && bodyHandler->GetNextLine(buf, kBufSize))
		{
			char * buf_end = buf + PL_strlen(buf);
			int headerLength = m_arbitraryHeader.Length();
			if (m_arbitraryHeader.Equals(buf))
			{
				char * headerValue = buf + headerLength; // value occurs after the header name...
				if (headerValue < buf_end && headerValue[0] == ':')  // + 1 to account for the colon which is MANDATORY
					headerValue++; 

				// strip leading white space
				while (headerValue < buf_end && IS_SPACE(*headerValue))
					headerValue++; // advance to next character
				
				// strip trailing white space
				char * end = buf_end - 1; 
				while (end > headerValue && IS_SPACE(*end)) // while we haven't gone back past the start and we are white space....
				{
					*end = '\0';	// eat up the white space
					end--;			// move back and examine the previous character....
				}
					
				if (headerValue < buf_end && *headerValue) // make sure buf has info besides just the header
				{
					nsCAutoString headerStr (headerValue);
					PRBool result2;
					err = MatchString(&headerStr, charset, PR_FALSE, &result2);  // match value with the other info...
					if (result != result2) // if we found a match
					{
						searchingHeaders = PR_FALSE;   // then stop examining the headers
						result = result2;
					}
				}
				else
					NS_ASSERTION(PR_FALSE, "error matching arbitrary headers"); // mscott --> i'd be curious if there is a case where this fails....
			}
			if (EMPTY_MESSAGE_LINE(buf))
				searchingHeaders = PR_FALSE;
		}
		delete bodyHandler;
		PR_Free(buf);
		*pResult = result;
		return err;
	}
	else
	{
		delete bodyHandler;
		return NS_ERROR_OUT_OF_MEMORY;
	}
}

nsresult nsMsgSearchTerm::MatchBody (nsMsgSearchScopeTerm *scope, PRUint32 offset, PRUint32 length /*in lines*/, const char *folderCharset,
										   nsIMsgDBHdr *msg, nsIMsgDatabase* db, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;
	nsresult err = NS_OK;
	PRBool result = PR_FALSE;
	*pResult = PR_FALSE;

	// Small hack so we don't look all through a message when someone has
	// specified "BODY IS foo"
	if ((length > 0) && (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt))
		length = PL_strlen (m_value.u.string);

	nsMsgBodyHandler * bodyHan  = new nsMsgBodyHandler (scope, offset, length, msg, db);
	if (!bodyHan)
		return NS_ERROR_OUT_OF_MEMORY;

	const int kBufSize = 512; // max size of a line???
	char *buf = (char*) PR_Malloc(kBufSize);
	if (buf)
	{
		PRBool endOfFile = PR_FALSE;  // if retValue == 0, we've hit the end of the file
		uint32 lines = 0;

		PRBool getConverter = PR_FALSE;
#ifdef DO_I18N
		CCCDataObject conv = INTL_CreateCharCodeConverter();
		PRInt16 win_csid = INTL_DocToWinCharSetID(foldcsid);
		PRInt16 mail_csid = INTL_DefaultMailCharSetID(win_csid);    // to default mail_csid (e.g. JIS for Japanese)
		if ((nsnull != conv) && INTL_GetCharCodeConverter(mail_csid, win_csid, conv)) 
			getConverter = PR_TRUE;
#endif // DO_I18N
		// Change the sense of the loop so we don't bail out prematurely
		// on negative terms. i.e. opDoesntContain must look at all lines
		PRBool boolContinueLoop;
		if (MatchAllBeforeDeciding())
			result = boolContinueLoop = PR_TRUE;
		else
			result = boolContinueLoop = PR_FALSE;

		// If there's a '=' in the search term, then we're not going to do
		// quoted printable decoding. Otherwise we assume everything is
		// quoted printable. Obviously everything isn't quoted printable, but
		// since we don't have a MIME parser handy, and we want to err on the
		// side of too many hits rather than not enough, we'll assume in that
		// general direction. Blech. ### FIX ME 
		// bug fix #88935: for stateful csids like JIS, we don't want to decode
		// quoted printable since it contains '='.
		PRBool isQuotedPrintable =  /*!(mail_csid & STATEFUL) && */
										(PL_strchr (m_value.u.string, '=') == nsnull);

		while (!endOfFile && result == boolContinueLoop)
		{
			if (bodyHan->GetNextLine(buf, kBufSize) >= 0)
			{
				// Do in-place decoding of quoted printable
				if (isQuotedPrintable)
					StripQuotedPrintable ((unsigned char*)buf);

				nsCString compare(buf);
				if (getConverter) 
				{	
#ifdef DO_I18N
					// In here we do I18N conversion if we get the converter
					char *newBody = nsnull;
					newBody = (char *)INTL_CallCharCodeConverter(conv, (unsigned char *) buf, (int32) PL_strlen(buf));
					if (newBody && (newBody != buf))
					{
						// CharCodeConverter return the char* to the orginal string
						// we don't want to free body in that case 
						compare = newBody;
					}
#endif
				}
				char startChar = (char) compare.CharAt(0);
				if (startChar != CR && startChar != LF)
				{
					err = MatchString (&compare, nsnull, PR_TRUE, &result);
					lines++; 
				}
			}
			else 
				endOfFile = PR_TRUE;
		}
#ifdef DO_I18N
		if(conv) 
			INTL_DestroyCharCodeConverter(conv);
#endif
		PR_FREEIF(buf);
		delete bodyHan;
	}
	else
		err = NS_ERROR_OUT_OF_MEMORY;
	*pResult = result;
	return err;
}


// *pResult is PR_FALSE when strings don't match, PR_TRUE if they do.
nsresult nsMsgSearchTerm::MatchString (nsCString *stringToMatch, const char *charset, PRBool body, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;
	PRBool result = PR_FALSE;

	nsresult err = NS_OK;
	nsCAutoString n_str;
	const char* n_header = nsnull;
	if(nsMsgSearchOp::IsEmpty != m_operator)	// Save some performance for opIsEmpty
	{
#ifdef DO_I18N
		n_str = INTL_GetNormalizeStr(csid , (unsigned char*)m_value.u.string);	// Always new buffer unless not enough memory
		if (!body)
			n_header = INTL_GetNormalizeStrFromRFC1522(csid , (unsigned char*)stringToMatch);	// Always new buffer unless not enough memory
		else
			n_header = INTL_GetNormalizeStr(csid , (unsigned char*)stringToMatch);	// Always new buffer unless not enough memory

		NS_ASSERTION(n_str, "failed get normalized string");
		NS_ASSERTION(n_header, "failed get normalized header");
#else
		n_header = *stringToMatch;
		n_str = m_value.u.string;
#endif // DO_I18N
	}
	switch (m_operator)
	{
	case nsMsgSearchOp::Contains:
		if ((nsnull != n_header) && ((n_str.GetBuffer())[0]) && /* INTL_StrContains(csid, n_header, n_str) */
			stringToMatch->Find(n_str, PR_TRUE) != -1)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::DoesntContain:
		if ((nsnull != n_header) && ((n_str.GetBuffer())[0]) &&  /* !INTL_StrContains(csid, n_header, n_str) */
			stringToMatch->Find(n_str, PR_TRUE) == -1)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::Is:
		if(n_header)
		{
			if ((n_str.GetBuffer())[0])
			{
				if (n_str.Equals(*stringToMatch, PR_TRUE /*ignore case*/) /* INTL_StrIs(csid, n_header, n_str)*/ )
					result = PR_TRUE;
			}
			else if (n_header[0] == '\0') // Special case for "is <the empty string>"
				result = PR_TRUE;
		}
		break;
	case nsMsgSearchOp::Isnt:
		if(n_header)
		{
			if ((n_str.GetBuffer())[0])
			{
				if (!n_str.Equals(*stringToMatch, PR_TRUE)/* INTL_StrIs(csid, n_header, n_str)*/ )
					result = PR_TRUE;
			}
			else if (n_header[0] != '\0') // Special case for "isn't <the empty string>"
				result = PR_TRUE;
		}
		break;
	case nsMsgSearchOp::IsEmpty:
		if (stringToMatch->Length() == 0)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::BeginsWith:
#ifdef DO_I18N_YET
		if((nsnull != n_str) && (nsnull != n_header) && INTL_StrBeginWith(csid, n_header, n_str))
			result = PR_TRUE;
#else
		// ### DMB - not the  most efficient way to do this.
		if (stringToMatch->Find(n_str, PR_TRUE) == 0)
			result = PR_TRUE;
#endif
		break;
	case nsMsgSearchOp::EndsWith: 
#ifdef DO_I18N_YET
		{
		if((nsnull != n_str) && (nsnull != n_header) && INTL_StrEndWith(csid, n_header, n_str))
			result = PR_TRUE;
		}
#else
		NS_ASSERTION(PR_FALSE, "not implemented yet");
#endif
		break;
	default:
		NS_ASSERTION(PR_FALSE, "invalid operator matching search results");
	}

	*pResult = result;
	return err;
}

PRBool nsMsgSearchTerm::MatchAllBeforeDeciding ()
{
	if (m_operator == nsMsgSearchOp::DoesntContain || m_operator == nsMsgSearchOp::Isnt)
		return PR_TRUE;
	return PR_FALSE;
}


nsresult nsMsgSearchTerm::MatchRfc822String (const char *string, const char *charset, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;
	*pResult = PR_FALSE;
	PRBool result;
	nsresult err = InitHeaderAddressParser();
	if (!NS_SUCCEEDED(err))
		return err;
	// Isolate the RFC 822 parsing weirdnesses here. MSG_ParseRFC822Addresses
	// returns a catenated string of null-terminated strings, which we walk
	// across, tring to match the target string to either the name OR the address

	char *names = nsnull, *addresses = nsnull;

	// Change the sense of the loop so we don't bail out prematurely
	// on negative terms. i.e. opDoesntContain must look at all recipients
	PRBool boolContinueLoop;
	if (MatchAllBeforeDeciding())
		result = boolContinueLoop = PR_TRUE;
	else
		result = boolContinueLoop = PR_FALSE;

	PRUint32 count;
	nsresult parseErr = m_headerAddressParser->ParseHeaderAddresses(charset, string, &names, &addresses, &count) ;

	if (NS_SUCCEEDED(parseErr) && count > 0)
	{
		NS_ASSERTION(names, "couldn't get names");
		NS_ASSERTION(addresses, "couldn't get addresses");
		if (!names || !addresses)
			return err;

		nsCAutoString walkNames(names);
		nsCAutoString walkAddresses(addresses);
		PRInt32 namePos = 0;
		PRInt32 addressPos = 0;
		for (PRUint32 i = 0; i < count && result == boolContinueLoop; i++)
		{
			err = MatchString (&walkNames, charset, PR_FALSE, &result);
			if (boolContinueLoop == result)
				err = MatchString (&walkAddresses, charset, PR_FALSE, &result);

			namePos += walkNames.Length() + 1;
			addressPos += walkAddresses.Length() + 1;
			walkNames = names + namePos;
			walkAddresses = addresses + addressPos;;
		}

		PR_FREEIF(names);
		PR_FREEIF(addresses);
	}
	*pResult = result;
	return err;
}


nsresult nsMsgSearchTerm::GetLocalTimes (PRTime a, PRTime b, PRExplodedTime &aExploded, PRExplodedTime &bExploded)
{
	PR_ExplodeTime(a, PR_LocalTimeParameters, &aExploded);
	PR_ExplodeTime(b, PR_LocalTimeParameters, &bExploded);
	return NS_OK;
}


nsresult nsMsgSearchTerm::MatchDate (PRTime dateToMatch, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;

	nsresult err = NS_OK;
	PRBool result = PR_FALSE;
	nsTime t_date(dateToMatch);
	
	switch (m_operator)
	{
	case nsMsgSearchOp::IsBefore:
		if (t_date < nsTime(m_value.u.date))
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::IsAfter:
		{
			nsTime adjustedDate = nsTime(m_value.u.date);
			adjustedDate += 60*60*24; // we want to be greater than the next day....
			if (t_date > adjustedDate)
				result = PR_TRUE;
		}
		break;
	case nsMsgSearchOp::Is:
		{
			PRExplodedTime tmToMatch, tmThis;
			if (NS_OK == GetLocalTimes (dateToMatch, m_value.u.date, tmToMatch, tmThis))
			{
				if (tmThis.tm_year == tmToMatch.tm_year &&
					tmThis.tm_month == tmToMatch.tm_month &&
					tmThis.tm_mday == tmToMatch.tm_mday)
					result = PR_TRUE;
			}
		}
		break;
	case nsMsgSearchOp::Isnt:
		{
			PRExplodedTime tmToMatch, tmThis;
			if (NS_OK == GetLocalTimes (dateToMatch, m_value.u.date, tmToMatch, tmThis))
			{
				if (tmThis.tm_year != tmToMatch.tm_year ||
					tmThis.tm_month != tmToMatch.tm_month ||
					tmThis.tm_mday != tmToMatch.tm_mday)
					result = PR_TRUE;
			}
		}
		break;
	default:
		NS_ASSERTION(PR_FALSE, "invalid compare op for dates");
	}
	*pResult = result;
	return err;
}


nsresult nsMsgSearchTerm::MatchAge (PRTime msgDate, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;

	PRBool result = PR_FALSE;
	nsresult err = NS_OK;

#ifdef DO_AGE_YET
	time_t now = XP_TIME();
	time_t matchDay = now - (m_value.u.age * 60 * 60 * 24);
	struct tm * matchTime = localtime(&matchDay);
	
	// localTime axes previous results so save these.
	int day = matchTime->tm_mday;
	int month = matchTime->tm_mon;
	int year = matchTime->tm_year;

	struct tm * msgTime = localtime(&msgDate);

	switch (m_operator)
	{
	case nsMsgSearchOp::IsGreaterThan: // is older than 
		if (msgDate < matchDay)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::IsLessThan: // is younger than 
		if (msgDate > matchDay)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::Is:
		if (matchTime && msgTime)
			if ((day == msgTime->tm_mday) 
				&& (month == msgTime->tm_mon)
				&& (year == msgTime->tm_year))
				result = PR_TRUE;
		break;
	default:
		NS_ASSERTION(PR_FALSE, "invalid compare op comparing msg age");
	}
#endif // DO_AGE_YET
	*pResult = result;
	return err;
}


nsresult nsMsgSearchTerm::MatchSize (PRUint32 sizeToMatch, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;

	PRBool result = PR_FALSE;
	switch (m_operator)
	{
	case nsMsgSearchOp::IsHigherThan:
		if (sizeToMatch > m_value.u.size)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::IsLowerThan:
		if (sizeToMatch < m_value.u.size)
			result = PR_TRUE;
		break;
	default:
		break;
	}
	*pResult = result;
	return NS_OK;
}


nsresult nsMsgSearchTerm::MatchStatus (PRUint32 statusToMatch, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;

	nsresult err = NS_OK;
	PRBool matches = PR_FALSE;

	if (statusToMatch & m_value.u.msgStatus)
		matches = PR_TRUE;

	switch (m_operator)
	{
	case nsMsgSearchOp::Is:
		if (matches)
			*pResult = PR_TRUE;
		break;
	case nsMsgSearchOp::Isnt:
		if (!matches)
			*pResult = PR_TRUE;
		break;
	default:
		*pResult = PR_FALSE;
		err = NS_ERROR_FAILURE;
		NS_ASSERTION(PR_FALSE, "invalid comapre op for msg status");
	}

	return err;	
}


nsresult nsMsgSearchTerm::MatchPriority (nsMsgPriority priorityToMatch, PRBool *pResult)
{
	if (!pResult)
		return NS_ERROR_NULL_POINTER;

	nsresult err = NS_OK;
	PRBool result=NS_OK;

	// Use this ugly little hack to get around the fact that enums don't have
	// integer compare operators
	int p1 = (int) priorityToMatch;
	int p2 = (int) m_value.u.priority;

	switch (m_operator)
	{
	case nsMsgSearchOp::IsHigherThan:
		if (p1 > p2)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::IsLowerThan:
		if (p1 < p2)
			result = PR_TRUE;
		break;
	case nsMsgSearchOp::Is:
		if (p1 == p2)
			result = PR_TRUE;
		break;
	default:
		result = PR_FALSE;
		err = NS_ERROR_FAILURE;
		NS_ASSERTION(PR_FALSE, "invalid match operator");
	}
	*pResult = result;
	return err;
}

// Lazily initialize the rfc822 header parser we're going to use to do
// header matching.
nsresult nsMsgSearchTerm::InitHeaderAddressParser()
{
	nsresult res = NS_OK;
	NS_DEFINE_CID(kMsgHeaderParserCID, NS_MSGHEADERPARSER_CID);
    
	if (!m_headerAddressParser)
	{
    m_headerAddressParser = do_GetService(kMsgHeaderParserCID, &res);
	}
	return res;
}

//-----------------------------------------------------------------------------
// nsMsgSearchScopeTerm implementation
//-----------------------------------------------------------------------------
nsMsgSearchScopeTerm::nsMsgSearchScopeTerm (nsMsgSearchScopeAttribute attribute, nsIMsgFolder *folder)
{
	m_attribute = attribute;
	m_folder = folder;
	m_searchServer = PR_TRUE;
	m_fileStream = nsnull;
}

nsMsgSearchScopeTerm::nsMsgSearchScopeTerm ()
{
	m_searchServer = PR_TRUE;
	m_fileStream = nsnull;
}

nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm ()
{
}

// ### purely temporary
static PRBool NET_IsOffline()
{
	return PR_FALSE;
}

PRBool nsMsgSearchScopeTerm::IsOfflineNews()
{
	switch (m_attribute)
	{
	case nsMsgSearchScope::Newsgroup:
	case nsMsgSearchScope::AllSearchableGroups:
		if (NET_IsOffline() || !m_searchServer)
			return PR_TRUE;
		else
			return PR_FALSE;
	case nsMsgSearchScope::OfflineNewsgroup:
		return PR_TRUE;
	default:
		return PR_FALSE;
	}
}

PRBool nsMsgSearchScopeTerm::IsOfflineMail ()
{
	// Find out whether "this" mail folder is online or offline
	NS_ASSERTION(m_folder, "scope doesn't have folder");
//	if (m_folder->GetType() == FOLDER_IMAPMAIL && !NET_IsOffline() && m_searchServer)    // make sure we are not in offline IMAP (mscott)
//		return PR_FALSE;
	return PR_TRUE;  // if POP or IMAP in offline mode
}

PRBool nsMsgSearchScopeTerm::IsOfflineIMAPMail()
{
	// Find out whether "this" mail folder is an offline IMAP folder
	NS_ASSERTION(m_folder, "scope doesn't have folder");
//	if (m_folder->GetType() == FOLDER_IMAPMAIL && (NET_IsOffline() || !m_searchServer))
//		return PR_TRUE;
	return PR_FALSE;       // we are not an IMAP folder that is offline
}

nsresult nsMsgSearchScopeTerm::GetMailPath(nsIFileSpec **aFileSpec)
{
	return (m_folder) ? m_folder->GetPath(aFileSpec) : NS_ERROR_NULL_POINTER;
}

nsresult nsMsgSearchScopeTerm::TimeSlice ()
{
	return NS_OK;
}


nsresult nsMsgSearchScopeTerm::InitializeAdapter (nsMsgSearchTermArray &termList)
{
	return NS_OK;
}


char *nsMsgSearchScopeTerm::GetStatusBarName ()
{
	return nsnull;
}


//-----------------------------------------------------------------------------
// nsMsgResultElement implementation
//-----------------------------------------------------------------------------


nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter *adapter)
{
	m_adapter = adapter;
}


nsMsgResultElement::~nsMsgResultElement () 
{
	for (int i = 0; i < m_valueList.Count(); i++)
	{
		nsMsgSearchValue *value = m_valueList.ElementAt(i);
#ifdef HUH_WHATS_THIS
		if (value->attribute == nsMsgSearchAttrib::JpegFile)
		{
			char *url = XP_PlatformFileToURL (value->u.string);
			char *tmp = url + PL_strlen("file://");
			XP_FileRemove (tmp, xpMailFolder /*###phil hacky*/);
			XP_FREE(url);
		}
#endif
		nsMsgResultElement::DestroyValue (value);
	}
}


nsresult nsMsgResultElement::AddValue (nsMsgSearchValue *value)
{ 
	m_valueList.AppendElement (value); 
	return NS_OK;
}


nsresult nsMsgResultElement::DestroyValue (nsMsgSearchValue *value)
{
	if (IS_STRING_ATTRIBUTE(value->attribute))
	{
		NS_ASSERTION(value->u.string, "got null result value string");
		PR_Free (value->u.string);
	}
	delete value;
	return NS_OK;
}


nsresult nsMsgResultElement::AssignValues (nsMsgSearchValue *src, nsMsgSearchValue *dst)
{
	// Yes, this could be an operator overload, but nsMsgSearchValue is totally public, so I'd
	// have to define a derived class with nothing by operator=, and that seems like a bit much
	nsresult err = NS_OK;
	switch (src->attribute)
	{
	case nsMsgSearchAttrib::Priority:
		dst->attribute = src->attribute;
		dst->u.priority = src->u.priority;
		break;
	case nsMsgSearchAttrib::Date:
		dst->attribute = src->attribute;
		dst->u.date = src->u.date;
		break;
	case nsMsgSearchAttrib::MsgStatus:
		dst->attribute = src->attribute;
		dst->u.msgStatus = src->u.msgStatus;
		break;
	case nsMsgSearchAttrib::MessageKey:
		dst->attribute = src->attribute;
		dst->u.key = src->u.key;
		break;
	case nsMsgSearchAttrib::AgeInDays:
		dst->attribute = src->attribute;
		dst->u.age = src->u.age;
		break;
	default:
		if (src->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes)
		{
			NS_ASSERTION(IS_STRING_ATTRIBUTE(src->attribute), "assigning non-string result");
			dst->attribute = src->attribute;
			dst->u.string = PL_strdup(src->u.string);
			if (!dst->u.string)
				err = NS_ERROR_OUT_OF_MEMORY;
		}
		else
			err = NS_ERROR_INVALID_ARG;
	}
	return err;
}


nsresult nsMsgResultElement::GetValue (nsMsgSearchAttribute attrib, nsMsgSearchValue **outValue) const
{
	nsresult err = NS_OK;
	nsMsgSearchValue *value = NULL;
	*outValue = NULL;

	for (int i = 0; i < m_valueList.Count() && err != NS_OK; i++)
	{
		value = m_valueList.ElementAt(i);
		if (attrib == value->attribute)
		{
			*outValue = new nsMsgSearchValue;
			if (*outValue)
			{
				err = AssignValues (value, *outValue);
				err = NS_OK;
			}
			else
				err = NS_ERROR_OUT_OF_MEMORY;
		}
	}
#ifdef HAVE_SEARCH_PORT
	// No need to store the folderInfo separately; we can always get it if/when
	// we need it. This code is to support "view thread context" in the search dialog
	if (SearchError_ScopeAgreement == err && attrib == nsMsgSearchAttrib::FolderInfo)
	{
		nsMsgFolderInfo *targetFolder = m_adapter->FindTargetFolder (this);
		if (targetFolder)
		{
			*outValue = new nsMsgSearchValue;
			if (*outValue)
			{
				(*outValue)->u.folder = targetFolder;
				(*outValue)->attribute = nsMsgSearchAttrib::FolderInfo;
				err = NS_OK;
			}
		}
	}
#endif
	return err;
}


const nsMsgSearchValue *nsMsgResultElement::GetValueRef (nsMsgSearchAttribute attrib) const 
{
	nsMsgSearchValue *value =  NULL;
	for (int i = 0; i < m_valueList.Count(); i++)
	{
		value = m_valueList.ElementAt(i);
		if (attrib == value->attribute)
			return value;
	}
	return NULL;
}


nsresult nsMsgResultElement::GetPrettyName (nsMsgSearchValue **value)
{
	nsresult err = GetValue (nsMsgSearchAttrib::Location, value);
#ifdef HAVE_SEARCH_PORT
	if (NS_OK == err)
	{
		nsMsgFolderInfo *folder = m_adapter->m_scope->m_folder;
		nsMsgNewsHost *host = NULL;
		if (folder)
		{
			// Find the news host because only the host knows whether pretty
			// names are supported. 
			if (FOLDER_CONTAINERONLY == folder->GetType())
				host = ((nsMsgNewsFolderInfoContainer*) folder)->GetHost();
			else if (folder->IsNews())
				host = folder->GetNewsFolderInfo()->GetHost();

			// Ask the host whether it knows pretty names. It isn't strictly
			// necessary to avoid calling folder->GetPrettiestName() since it
			// does the right thing. But we do have to find the folder from the host.
			if (host && host->QueryExtension ("LISTPNAMES"))
			{
				folder = host->FindGroup ((*value)->u.string);
				if (folder)
				{
					char *tmp = nsCRT::strdup (folder->GetPrettiestName());
					if (tmp)
					{
						XP_FREE ((*value)->u.string);
						(*value)->u.string = tmp;
					}
				}
			}
		}
	}
#endif // HAVE_SEARCH_PORT
	return err;
}

int nsMsgResultElement::CompareByFolderInfoPtrs (const void *e1, const void *e2)
{
#ifdef HAVE_SEARCH_PORT
	nsMsgResultElement * re1 = *(nsMsgResultElement **) e1;
	nsMsgResultElement * re2 = *(nsMsgResultElement **) e2;

	// get the src folder for each one
	
	const nsMsgSearchValue * v1 = re1->GetValueRef(attribFolderInfo);
	const nsMsgSearchValue * v2 = re2->GetValueRef(attribFolderInfo);

	if (!v1 || !v2)
		return 0;

	return (v1->u.folder - v2->u.folder);
#else
	return -1;
#endif // HAVE_SEARCH_PORT
}



int nsMsgResultElement::Compare (const void *e1, const void *e2)
{
	int ret = 0;
#ifdef HAVE_SEARCH_PORT
	// Bad karma to cast away const, but they're my objects anway.
	// Maybe if we go through and const everything this should be a const ptr.
	nsMsgResultElement *re1 = *(nsMsgResultElement**) e1;
	nsMsgResultElement *re2 = *(nsMsgResultElement**) e2;

	NS_ASSERTION(re1->IsValid(), "invalid result element1 in resultElement::Compare");
	NS_ASSERTION(re2->IsValid(), "invalid result element2 in resultElement::Compare");

	nsMsgSearchAttribute attrib = re1->m_adapter->m_scope->m_frame->m_sortAttribute;

	const nsMsgSearchValue *v1 = re1->GetValueRef (attrib);
	const nsMsgSearchValue *v2 = re2->GetValueRef (attrib);

	if (!v1 || !v2)
		return ret; // search result doesn't contain the attrib we want to sort on

	switch (attrib)
	{
	case nsMsgSearchAttrib::Date:
		{
			// on Win 3.1, the requisite 'int' return type is a short, so use a 
			// real time_t for comparison
			time_t date = v1->u.date - v2->u.date;
			if (date)
				ret = ((long)date) < 0 ? -1 : 1;
			else
				ret = 0;
		}
		break;
	case nsMsgSearchAttrib::Priority:
		ret = v1->u.priority - v2->u.priority;
		break;
	case nsMsgSearchAttrib::MsgStatus:
		{
			// Here's a totally arbitrary sorting protocol for msg status
			uint32 s1, s2;

			s1 = v1->u.msgStatus & ~MSG_FLAG_REPLIED;
			s2 = v2->u.msgStatus & ~MSG_FLAG_REPLIED;
			if (s1 || s2)
				ret = s1 - s2;
			else
			{
				s1 = v1->u.msgStatus & ~MSG_FLAG_FORWARDED;
				s2 = v2->u.msgStatus & ~MSG_FLAG_FORWARDED;
				if (s1 || s2)
					ret = s1 - s2;
				else
				{
					s1 = v1->u.msgStatus & ~MSG_FLAG_READ;
					s2 = v2->u.msgStatus & ~MSG_FLAG_READ;
					if (s1 || s2)
						ret = s1 - s2;
					else
						// statuses don't contain any flags we're interested in, 
						// so they're equal as far as we care
						ret = 0;
				}
			}
		}
		break;
	default:
		if (attrib == nsMsgSearchAttrib::Subject)
		{
			// Special case for subjects, so "Re:foo" sorts under 'f' not 'r'
			const char *s1 = v1->u.string;
			const char *s2 = v2->u.string;
			msg_StripRE (&s1, NULL);
			msg_StripRE (&s2, NULL);
			ret = PL_strcasecomp (s1, s2);
		}
		else
			ret = strcasecomp (v1->u.string, v2->u.string);
	}
	// ### need different hack for this.
	// qsort's default sort order is ascending, so in order to get descending
	// behavior, we'll tell qsort a lie and reverse the comparison order.
	if (re1->m_adapter->m_scope->m_frame->m_descending && ret != 0)
		if (ret < 0)
			ret = 1;
		else
			ret = -1;

	// <0 --> e1 less than e2
	// 0  --> e1 equal to e2
	// >0 --> e1 greater than e2
#endif
	return ret;
}

#ifdef HAVE_SEARCH_PORT
MWContextType nsMsgResultElement::GetContextType ()
{
	MWContextType type=(MWContextType)0;
	switch (m_adapter->m_scope->m_attribute)
	{
	case nsMsgSearchScopeMailFolder:
		type = MWContextMailMsg;
		break;
	case nsMsgSearchScopeOfflineNewsgroup:    // added by mscott could be bug fix...
	case nsMsgSearchScopeNewsgroup:
	case nsMsgSearchScopeAllSearchableGroups:
		type = MWContextNewsMsg;
		break;
	case nsMsgSearchScopeLdapDirectory:
		type = MWContextBrowser;
		break;
	default:
		NS_ASSERTION(PR_FALSE, "invalid scope"); // should never happen
	}
	return type;
}

#endif
nsresult nsMsgResultElement::Open (void *window)
{
#ifdef HAVE_SEARCH_PORT
	// ###phil this is a little ugly, but I'm not inclined to invest more in it
	// until the libnet rework is done and I know what kind of context we'll end up with

	if (window)
	{
		if (m_adapter->m_scope->m_attribute != nsMsgSearchScopeLdapDirectory)
		{
			msgPane = (MSG_MessagePane *) window; 
			PR_ASSERT (MSG_MESSAGEPANE == msgPane->GetPaneType());
			return m_adapter->OpenResultElement (msgPane, this);
		}
		else
		{
			context = (MWContext*) window;
			PR_ASSERT (MWContextBrowser == context->type);
			msg_SearchLdap *thisAdapter = (msg_SearchLdap*) m_adapter;
			return thisAdapter->OpenResultElement (context, this);
		}
	}
#endif
	return NS_ERROR_NULL_POINTER;
}


