/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * 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 Communicator client code.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *  Simon Fraser <sfraser@netscape.com>
 */



#include <StringCompare.h>

#include "nsAEUtils.h"
#include "nsAECompare.h"

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareTexts(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	Boolean	result = false;
	
	short	compareResult;
	char  	*lhs;
	char  	*rhs;
	long		lhsSize;
	long		rhsSize;
	char		h1State, h2State;
	
	lhsSize = GetHandleSize(desc1->dataHandle);
	h1State = HGetState(desc1->dataHandle);
	HLock(desc1->dataHandle);
	lhs = *(desc1->dataHandle);
	
	rhsSize = GetHandleSize(desc2->dataHandle);
	h2State = HGetState(desc2->dataHandle);
	HLock(desc2->dataHandle);
	rhs = *(desc2->dataHandle);

	compareResult = ::CompareText(lhs, rhs, lhsSize, rhsSize, nil);

	switch (oper) 
	{
		case kAEEquals:
			result = (compareResult == 0);
			break;
		
		case kAELessThan:
			result = (compareResult < 0);
			break;
		
		case kAELessThanEquals:
			result = (compareResult <= 0);
			break;
		
		case kAEGreaterThan:
			result = (compareResult > 0);
			break;
		
		case kAEGreaterThanEquals:
			result = (compareResult >= 0);
			break;
		
		case kAEBeginsWith:
			if (rhsSize > lhsSize)
			{
				result = false;
			}
			else
			{
				// compare only the number of characters in rhs
				// begin comparing at the beginning of lhs
				compareResult = CompareText(lhs, rhs, rhsSize, rhsSize, nil);
				result = (compareResult == 0);
			}
			break;
			
		case kAEEndsWith:
			if (rhsSize > lhsSize)
			{
				result = false;
			}
			else
			{
				// compare only the number of characters in rhs
				// begin comparing rhsSize characters from the end of lhs
				// start 
				
				lhs += (lhsSize - rhsSize);
				compareResult = CompareText(lhs, rhs, rhsSize, rhsSize, nil);
				result = (compareResult == 0);
			}
			break;

		case kAEContains:
			// Here I use an inefficient search strategy, but we're dealing with small amounts
			// of text and by using CompareText(), we're doing the same style of comparison
			// as in the other cases above.
			
			result = false;
			while (lhsSize >= rhsSize)
			{
				compareResult = CompareText(lhs, rhs, rhsSize, rhsSize, nil);
				if (compareResult == 0)
				{
					result = true;
					break;
				}
				lhs++;
				lhsSize--;
			}
			break;

		default:
			ThrowOSErr(errAEBadTestKey);
	}

	HSetState(desc1->dataHandle, h1State);
	HSetState(desc2->dataHandle, h2State);
	
	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareEnumeration(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr		err	= noErr;
	Boolean		result = false;
	long	 		lhs;
	long   		rhs;
	StAEDesc 		charDesc;
	
	// Make each number is a long integer (in case it's a short integer or other integer type) 
	// before extracting the data */
	
	err = AECoerceDesc(desc1, typeChar, &charDesc);
	ThrowIfOSErr(err);

	lhs = **(long **)(charDesc.dataHandle);
	AEDisposeDesc(&charDesc);
	
	err = AECoerceDesc(desc2, typeChar, &charDesc);
	ThrowIfOSErr(err);

	rhs = **(long **)charDesc.dataHandle;
	AEDisposeDesc(&charDesc);
	
	switch (oper) 
	{
		case kAEEquals:
			result = (lhs == rhs);	// equality is the only test that makes sense for enumerators
			break;
		
		default:
			ThrowOSErr(errAEBadTestKey);
	}

	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareInteger(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr		err	= noErr;
	Boolean		result = false;
	long	 		lhs;
	long   		rhs;
	StAEDesc 		longDesc;
	
	// Make each number is a long integer (in case it's a short integer or other integer type) 
	// before extracting the data */
	
	err = AECoerceDesc(desc1, typeLongInteger, &longDesc);
	ThrowIfOSErr(err);

	lhs = **(long **)(longDesc.dataHandle);
	AEDisposeDesc(&longDesc);
	
	err = AECoerceDesc(desc2, typeLongInteger, &longDesc);
	ThrowIfOSErr(err);

	rhs = **(long **)longDesc.dataHandle;
	AEDisposeDesc(&longDesc);
	
	switch (oper) 
	{
		case kAEEquals:
			result = (lhs == rhs);
			break;
		
		case kAELessThan:
			result = (lhs < rhs);
			break;
		
		case kAELessThanEquals:
			result = (lhs <= rhs);
			break;
		
		case kAEGreaterThan:
			result = (lhs > rhs);
			break;
		
		case kAEGreaterThanEquals:
			result = (lhs >= rhs);
			break;
		
		default:
			ThrowOSErr(errAEBadTestKey);
	}

	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareFixed(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr		err	= noErr;
	Boolean		result = false;
	Fixed		lhs;
	Fixed 		rhs;
	
	err = DescToFixed(desc1, &lhs);
	ThrowIfOSErr(err);
		
	err = DescToFixed(desc2, &rhs);
	ThrowIfOSErr(err);
	
	switch (oper) 
	{
		case kAEEquals:
			result = (lhs == rhs);
			break;
		
		case kAELessThan:
			result = (lhs < rhs);
			break;
		
		case kAELessThanEquals:
			result = (lhs <= rhs);
			break;
		
		case kAEGreaterThan:
			result = (lhs > rhs);
			break;
		
			case kAEGreaterThanEquals:
			result = (lhs >= rhs);
			break;
		
		default:
			ThrowOSErr(errAEBadTestKey);
	}
	
	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareFloat(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr	err	= noErr;
	Boolean	result = false;
	float		 lhs;
	float 	 rhs;
	
	err = DescToFloat(desc1, &lhs);
	ThrowIfOSErr(err);
		
	err = DescToFloat(desc2, &rhs);
	ThrowIfOSErr(err);
	
	switch (oper) 
	{
		case kAEEquals:
			result = (lhs == rhs);
			break;
		
		case kAELessThan:
			result = (lhs < rhs);
			break;
		
		case kAELessThanEquals:
			result = (lhs <= rhs);
			break;
		
		case kAEGreaterThan:
			result = (lhs > rhs);
			break;
		
			case kAEGreaterThanEquals:
			result = (lhs >= rhs);
			break;
		
		default:
			ThrowOSErr(errAEBadTestKey);
	}
	
	return result;
}

// ----------------------------------------------------------------------------------------
// Apple events defines a boolean as a 1-byte value containing 1 for TRUE and 0 for FALSE

Boolean AEComparisons::CompareBoolean(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr	err 	= noErr;
	Boolean	result = false;
	
	Boolean 	bool1	= ((**(char **)desc1->dataHandle) != 0);
	Boolean 	bool2	= ((**(char **)desc2->dataHandle) != 0);
		
	if (oper == kAEEquals) 
		result = (bool1 == bool2);
	else
		ThrowOSErr(errAEBadTestKey);		// No other tests make sense

	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareRGBColor(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr	err = noErr;
	Boolean	result = false;
	
	RGBColor lhs;
	RGBColor rhs;
	
	err = DescToRGBColor(desc1, &lhs);
	ThrowIfOSErr(err);
		
	err = DescToRGBColor(desc2, &rhs);
	ThrowIfOSErr(err);

	if (oper == kAEEquals) 
		result = EqualRGB(lhs, rhs);
	else
		ThrowOSErr(errAEBadTestKey);		// No other tests make sense

	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::ComparePoint(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr	err = noErr;
	Boolean	result = false;
	
	Point		lhs;
	Point		rhs;
	
	err = DescToPoint(desc1, &lhs);
	ThrowIfOSErr(err);
		
	err = DescToPoint(desc2, &rhs);
	ThrowIfOSErr(err);
		
	switch (oper)
	{
		case kAEEquals:
			result = (lhs.h = rhs.h && lhs.v == rhs.v);
			break;
			
		case kAELessThan:
			result = (lhs.h < rhs.h && lhs.v < rhs.v);
			break;
		
		case kAELessThanEquals:
			result = (lhs.h <= rhs.h && lhs.v <= rhs.v);
			break;
		
		case kAEGreaterThan:
			result = (lhs.h > rhs.h && lhs.v > rhs.v);
			break;
		
		case kAEGreaterThanEquals:
			result = (lhs.h >= rhs.h && lhs.v >= rhs.v);
			break;
		
		default:
			ThrowOSErr(errAEBadTestKey);		// No other tests make sense
	}

	return result;
}

// ----------------------------------------------------------------------------------------

Boolean AEComparisons::CompareRect(DescType oper, const AEDesc *desc1, const AEDesc *desc2)
{
	OSErr	err = noErr;
	Boolean	result = false;
	Rect		lhs;
	Rect		rhs;
	
	err = DescToRect(desc1, &lhs);
	ThrowIfOSErr(err);
		
	err = DescToRect(desc2, &rhs);
	ThrowIfOSErr(err);
		
	switch (oper)
	{
		// compare size AND location
		case kAEEquals:	
			result = ((lhs.top == rhs.top) && (lhs.left == rhs.left) && (lhs.bottom == rhs.bottom) && (lhs.right == rhs.right));
			break;
			
		// compare size only on the rest of the tests
		case kAELessThan:	
			result = (((lhs.bottom - lhs.top) < (rhs.bottom - rhs.top)) && ((lhs.right - lhs.left) < (lhs.right - rhs.left)));
			break;
		
		case kAELessThanEquals:
			result = (((lhs.bottom - lhs.top) < (rhs.bottom - rhs.top)) && ((lhs.right - lhs.left) < (lhs.right - rhs.left)));
			break;
		
		case kAEGreaterThan:
			result = (((lhs.bottom - lhs.top) < (rhs.bottom - rhs.top)) && ((lhs.right - lhs.left) < (lhs.right - rhs.left)));
			break;
		
		case kAEGreaterThanEquals:
			result = (((lhs.bottom - lhs.top) < (rhs.bottom - rhs.top)) && ((lhs.right - lhs.left) < (lhs.right - rhs.left)));
			break;
		
		case kAEContains:
			// Note: two identical Rects contain each other, according to this test:
			result = ((lhs.top <= rhs.top) && (lhs.left <= rhs.left) && (lhs.bottom >= rhs.bottom) && (lhs.right >= rhs.right));
			break;
			
		default:
			ThrowOSErr(errAEBadTestKey);		// No other tests make sense
	}

	return result;
}

#pragma mark -

/*----------------------------------------------------------------------------
	TryPrimitiveComparison 
	
----------------------------------------------------------------------------*/

Boolean AEComparisons::TryPrimitiveComparison(DescType comparisonOperator, const AEDesc *desc1, const AEDesc *desc2)
{
	Boolean 	result = false;
	
	// This has to handle all the data types used in the application's
	// object model implementation.
	switch (desc1->descriptorType) 
	{
		case typeChar:
			result = CompareTexts(comparisonOperator, desc1, desc2);
			break;
		
		case typeShortInteger:		// also covers typeSMInt		'shor'
		case typeLongInteger:		// also covers typeInteger	'long'
		case typeMagnitude:			//						'magn'
			result = CompareInteger(comparisonOperator, desc1, desc2);
			break;

		case typeEnumerated:
			result = CompareEnumeration(comparisonOperator, desc1, desc2);
			break;
		
		case typeFixed:
			result = CompareFixed(comparisonOperator, desc1, desc2);
			break;

		case typeFloat:
			result = CompareFloat(comparisonOperator, desc1, desc2);
			break;
		
		case typeBoolean:
			result = CompareBoolean(comparisonOperator, desc1, desc2);
			break;
				
		case typeRGBColor:
			result = CompareRGBColor(comparisonOperator, desc1, desc2);
			break;
				
		case typeQDRectangle:
			result = CompareRect(comparisonOperator, desc1, desc2);
			break;
				
		case typeQDPoint:
			result = ComparePoint(comparisonOperator, desc1, desc2);
			break;
				
		default:
			ThrowOSErr(errAEWrongDataType);
	}

	return result;
}
