/** 
   EOQualifier.m <title>EOQualifier</title>

   Copyright (C) 2000 Free Software Foundation, Inc.

   Author: Mirko Viviani <mirko.viviani@rccr.cremona.it>
   Date: February 2000

   $Revision: 1.13 $
   $Date: 2003/06/26 08:20:30 $

   <abstract></abstract>

   This file is part of the GNUstep Database Library.

   <license>
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
   </license>
**/

#include "config.h"

RCS_ID("$Id: EOQualifier.m,v 1.13 2003/06/26 08:20:30 mguesdon Exp $")

#include <stdio.h>
#include <string.h>

#ifndef NeXT_Foundation_LIBRARY
#include <Foundation/NSDictionary.h>
#include <Foundation/NSSet.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSCoder.h>
#include <Foundation/NSDebug.h>
#else
#include <Foundation/Foundation.h>
#endif

#include <EOControl/EOQualifier.h>
#include <EOControl/EODebug.h>

#include <gnustep/base/GSObjCRuntime.h>


@implementation NSNumber (EOQualifierExtras)

- (id)initWithString: (NSString *)string
{
    double   dVal;
    float    fVal;
    int      iVal;

    dVal = [string doubleValue];
    fVal = [string floatValue];
    iVal = [string intValue];

    if (dVal == iVal)
      {
	return [self initWithInt: iVal];
      }
    else if (dVal == fVal)
      {
	return [self initWithFloat: fVal];
      }
    else
      {
	return [self initWithDouble: dVal];
      }
}
@end

@implementation EOQualifier

/**
 * Returns an autoreleased qualifier which is constructed by calling
 * [EOQualifier+qualifierWithQualifierFormat:varargList:]
 */
+ (EOQualifier *)qualifierWithQualifierFormat: (NSString *)qualifierFormat, ...
{
  EOQualifier *qualifier = nil;

  if(qualifierFormat)
    {
      va_list ap;

      va_start(ap, qualifierFormat);

      qualifier = [EOQualifier qualifierWithQualifierFormat: qualifierFormat
                               varargList: ap];

      va_end(ap);
    }

  return qualifier;
}

+ (EOQualifier *)qualifierWithQualifierFormat: (NSString *)format
				    arguments: (NSArray *)args
{
  [self notImplemented: _cmd];
  return nil;
}

- (void)dealloc
{
#ifdef DEBUG
//  NSDebugFLog(@"Dealloc EOQualifier %p. ThreadID=%p",
//              (void*)self,(void*)objc_thread_id());
#endif

  [super dealloc];

#ifdef DEBUG
//  NSDebugFLog(@"Stop Dealloc EOQualifier %p. ThreadID=%p",
//              (void*)self,(void*)objc_thread_id());
#endif
}

static NSString *getOperator(const char **cFormat, const char **s)
{
  NSString *operator;

  while (**s && isspace(**s))
    (*s)++;

  *cFormat = *s;

  if (isalnum(**s))
    {
      while (**s && !isspace(**s) && **s != '%' && **s != '\'')
        {
	  (*s)++;
        }

      operator = [NSString stringWithCString: *cFormat length: *s - *cFormat];
    }
  else
    {
      while (**s && !isalnum(**s) && !isspace(**s) && **s != '%' && **s != '\'')
        {
	  NSDebugLog(@"avoid gcc 3.1.1 bug which optimizes to segfault", "");
	  (*s)++;
        }

      operator = [NSString stringWithCString: *cFormat length: *s - *cFormat];
    }

  *cFormat = *s;

  return operator;
}
        
static id getKey(const char **cFormat, const char **s, BOOL *isKeyValue,
		 va_list *args)
{
  NSMutableString *key, *classString = nil;
  char quoteChar;
  BOOL quoted = NO;

  while (**s && isspace(**s))
    (*s)++;

  if (isKeyValue)
    {
      if (**s == '(')
        {
	  (*s)++; *cFormat = *s;

	  while (**s && **s != ')')
	    (*s)++;

	  if (!*s); //TODO exception

	  classString = [NSString stringWithCString: *cFormat
				  length: *s - *cFormat];

	  (*s)++; *cFormat = *s;
        }
  
      if (!strncmp("nil", *s, 3))
	{
	  char value = *(*s+3);

	  if (value == 0 || value == ' ')
	    {
	      *cFormat = *s = *s+3;
	      return nil;
	    }
	}
    }

  quoteChar = **s;
  if (quoteChar && (quoteChar == '"' || quoteChar == '\''))
    {
      quoted = YES;
      (*s)++;
    }
  
  *cFormat = *s;
  
  if (quoted)
    {
      while (**s && **s != quoteChar)
	(*s)++;

      key = [NSString stringWithCString: *cFormat length: *s - *cFormat];
      (*s)++; // skip closing quote
    }
  else
    {
      key = [NSMutableString stringWithCapacity:8];

      while (**s && (isalnum(**s) || **s == '@' || **s == '#' || **s == '_'
		     || **s == '$' || **s == '%' || **s == '.'))
        {
	  if (**s == '%')
	    {
	      const char *argString;
	      NSString *argObj;
	      //float argFloat;
	      double argFloat; // `float' is promoted to `double' when passed through `...' (so you should pass `double' not `float' to `va_arg')

	      int argInt;

	      if (isKeyValue)
	        {
		  *isKeyValue = YES;
		}

	      switch (*(*s+1))
		{
		case '@':
		  argObj = va_arg(*args, id);

		  if (isKeyValue && *isKeyValue == YES && quoted == NO
		      && classString == nil)
		    {
		      *cFormat = *s = *s+2;
		      return argObj;
		    }
		  else
		    {
		      if (*cFormat != *s)
			[key appendString: [NSString stringWithCString: *cFormat
						     length: *s - *cFormat]];

		      [key appendString: [argObj description]];
		      *cFormat = *s+2;
		      (*s)++;
		    }
		  break;

		case 's':
		  argString = va_arg(*args, const char *);

		  if (isKeyValue && *isKeyValue == YES && quoted == NO
		      && classString == nil)
		    {
		      *cFormat = *s = *s + 2;
		      return [NSString stringWithCString: argString];
		    }
		  else
		    {
		      if (*cFormat != *s)
			[key appendString: [NSString stringWithCString: *cFormat
						     length: *s - *cFormat]];

		      [key appendString: [NSString
					   stringWithCString: argString]];
		      *cFormat = *s + 2;
		      (*s)++;
		    }
		  break;

		case 'd':
		  argInt = va_arg(*args, int);

		  if (isKeyValue && *isKeyValue == YES && quoted == NO
		      && classString == nil)
		    {
		      *cFormat = *s = *s + 2;
		      return [NSNumber numberWithInt: argInt];
		    }
		  else
		    {
		      if(*cFormat != *s)
			[key appendString: [NSString stringWithCString: *cFormat
						     length: *s - *cFormat]];

		      [key appendString: [NSString stringWithFormat: @"%d",
						   argInt]];
		      *cFormat = *s + 2;
		      (*s)++;
		    }
		  break;

		case 'f':
		  argFloat = va_arg(*args, double);// `float' is promoted to `double' when passed through `...' (so you should pass `double' not `float' to `va_arg')

		  if (isKeyValue && *isKeyValue == YES && quoted == NO
		      && classString == nil)
		    {
		      *cFormat = *s = *s + 2;
		      return [NSNumber numberWithFloat: argFloat];
		    }
		  else
		    {
		      if (*cFormat != *s)
			[key appendString: [NSString stringWithCString: *cFormat
						     length: *s - *cFormat]];

		      [key appendString: [NSString stringWithFormat: @"%f",
						   argFloat]];
		      *cFormat = *s + 2;
		      (*s)++;
		    }
		  break;

		case '%':
		  *cFormat = *s + 2;
		  (*s)++;
		  [key appendString: [NSString stringWithCString: *cFormat
					       length: *s - *cFormat]];
		  break;

		default:
		  [NSException raise: NSInvalidArgumentException
			       format: @"%@ -- %@: unrecognized character (%c) in the conversion specification", @"qualifierParser", @"EOQualifier", *(*s + 1)];
		  break;
		}
	    }

	  (*s)++;
        }

      if (*cFormat != *s)
	[key appendString: [NSString stringWithCString: *cFormat
				     length: *s - *cFormat]];
    }

  if (isKeyValue)
    {
      *isKeyValue = quoted;

      if (classString)
        {
          key = AUTORELEASE([[NSClassFromString(classString) alloc]
			      initWithString: key]);
        }
    }
    
  *cFormat = *s;

  return key;
}

static BOOL isNotQualifier(const char **cFormat, const char **s)
{
  while (**s && isspace(**s))
    (*s)++;

  *cFormat = *s;

  if (!strncasecmp(*s, "not", 3))
    {
      switch ((*s)[3])
        {
	case ' ':
	case '(':
	case 0:
	  *cFormat = *s = *s+3;
	  return YES;
        }
    }

  return NO;
}

static Class whichQualifier(const char **cFormat, const char **s)
{
  while (**s && isspace(**s))
    (*s)++;

  *cFormat = *s;

  if (!strncasecmp(*s, "and", 3))
    {
      switch ((*s)[3])
        {
	case ' ':
	case '(':
	case 0:
	  *cFormat = *s = *s+3;
	  return [EOAndQualifier class];
        }
    }
  else if (!strncasecmp(*s, "or", 2))
    {
      switch ((*s)[2])
        {
	case ' ':
	case '(':
	case 0:
	  *cFormat = *s = *s+2;
	  return [EOOrQualifier class];
        }
    }

  return Nil;
}

+ (EOQualifier *)qualifierWithQualifierFormat: (NSString *)format
				   varargList: (va_list)args
{
  const char *s;
  const char *cFormat;
  NSMutableArray *bracketStack = nil;
  NSMutableArray *qualifierArray = nil;
  NSMutableArray *parentQualifiers = nil;
  EOQualifier *qualifier = nil;
  NSString *leftKey;
  NSString *rightKey;
  NSString *operator;
  SEL operatorSelector = NULL;
  BOOL isKeyValue = NO;
  BOOL notQual;
  Class lastQualifierClass = Nil;
  Class qualifierClass = Nil;

  bracketStack = [NSMutableArray array];
  parentQualifiers = [NSMutableArray array];

  cFormat = s = [format cString];

  while (*s)
    {
      while (*s && isspace(*s))
        (s)++;

      while (*s == '(' )
      {
	NSMutableDictionary *state;

	state = [NSMutableDictionary dictionaryWithCapacity:4];
	if (lastQualifierClass != NULL)
	  {
	    [state setObject: lastQualifierClass forKey: @"lastQualifierClass"];
	    lastQualifierClass = NULL;
	  }
	if (qualifierArray != nil)
	  {
	    [state setObject: qualifierArray forKey: @"qualifierArray"];
	    qualifierArray = nil;
	  }
	if (qualifierClass != nil)
	  {
	    [state setObject: qualifierClass forKey: @"qualifierClass"];
	    qualifierClass = nil;
	  }
	[state setObject: parentQualifiers forKey: @"parentQualifiers"];
	parentQualifiers = [NSMutableArray new];

	[bracketStack addObject:state];

	(s)++; // skip '('
	while (*s && isspace(*s))
	  (s)++;
      }
      
      notQual = isNotQualifier(&cFormat, &s);
      leftKey = getKey(&cFormat, &s, NULL, &args);
      operator = getOperator(&cFormat, &s);
      rightKey = getKey(&cFormat, &s, &isKeyValue, &args);

      operatorSelector = [EOQualifier operatorSelectorForString: operator];
      if (!operatorSelector)
        [NSException raise: NSInvalidArgumentException
		     format: @"%@ -- %@ 0x%x: no operator or unknown operator: '%@'",
                     NSStringFromSelector(_cmd),
		     NSStringFromClass([self class]), self,
                     operator];

      EOFLOGObjectLevelArgs(@"EOQualifier",
			    @"leftKey=%@ operatorSelector=%s rightKey=%@ class=%@",
			    leftKey,
			    GSNameFromSelector(operatorSelector),
			    rightKey,
			    isKeyValue?@"EOKeyValueQualifier":@"EOKeyComparisonQualifier");

      if (isKeyValue)
	qualifier = [EOKeyValueQualifier qualifierWithKey: leftKey
                                         operatorSelector: operatorSelector
                                         value: rightKey];
      else
	qualifier = [EOKeyComparisonQualifier
		      qualifierWithLeftKey: leftKey
		      operatorSelector: operatorSelector
		      rightKey: rightKey];
      
      EOFLOGObjectLevelArgs(@"EOQualifier",
			    @"qualifier=%@",
			    qualifier);

      if (notQual)
	qualifier = [EONotQualifier qualifierWithQualifier: qualifier];

      EOFLOGObjectLevelArgs(@"EOQualifier",
			    @"qualifier=%@",
			    qualifier);

      while (*s && isspace(*s))
        (s)++;

      while (*s == ')' )
      {
        NSMutableDictionary *state;

        /* clean up inner qualifier */
        if (qualifierArray != nil)
	  {
	    [qualifierArray addObject:qualifier];
	    qualifier = AUTORELEASE([[qualifierClass alloc]
				      initWithQualifierArray: qualifierArray]);
	    qualifierArray = nil;
	  }

	while ([parentQualifiers count] != 0)
	  {
	    id       parent;
	    NSArray *quals;
	    
	    parent = [parentQualifiers lastObject];
	    quals = [[parent qualifiers] arrayByAddingObject: qualifier];
	    qualifier = AUTORELEASE([[[parent class] alloc]
					initWithQualifierArray: quals]);
	    [parentQualifiers removeLastObject];
	  }

	DESTROY(parentQualifiers);

	/* pop bracketStack */
	state = [bracketStack lastObject];
	qualifierArray = [state objectForKey:@"qualifierArray"];
	lastQualifierClass = [state objectForKey:@"lastQualifierClass"];
	qualifierClass = [state objectForKey:@"qualifierClass"];
	parentQualifiers = [state objectForKey:@"parentQualifiers"];

	[bracketStack removeLastObject];

	(s)++; // skip ')'
        while (*s && isspace(*s))
          (s)++;
      }

      qualifierClass = whichQualifier(&cFormat, &s);
      EOFLOGObjectLevelArgs(@"EOQualifier", @"qualifierClass=%@",
			    qualifierClass);

      if ([bracketStack count]==0)
        {
          if (qualifierClass == Nil)
            break;
        }
      if (lastQualifierClass == Nil)
        {
          qualifierArray = [NSMutableArray arrayWithObject: qualifier];
        }
      else if (lastQualifierClass == qualifierClass)
        {
	  [qualifierArray addObject: qualifier];
        }
      else /* lastQualifierClass set and != qualifierClass */
        {
	  [parentQualifiers addObject:
	     AUTORELEASE([[lastQualifierClass alloc]
			     initWithQualifierArray: qualifierArray])];

	  qualifierArray = [NSMutableArray arrayWithObject: qualifier];
        }

      lastQualifierClass = qualifierClass;
    }

  /* this is reached after the break */
  if (lastQualifierClass != Nil)
    {
      if (qualifier == nil)
        [NSException raise: NSInvalidArgumentException
		     format: @"%@ -- %@ 0x%x: missing qualifier",
                     NSStringFromSelector(_cmd),
		     NSStringFromClass([self class]), self];

      [qualifierArray addObject: qualifier];

      qualifier = AUTORELEASE([[lastQualifierClass alloc]
		     initWithQualifierArray: qualifierArray]);

      EOFLOGObjectLevelArgs(@"EOQualifier", 
			    @"qualifier=%@",
			    qualifier);
    }

  while ([parentQualifiers count] != 0)
    {
      id       parent;
      NSArray *quals;

      parent = [parentQualifiers lastObject];
      quals = [[parent qualifiers] arrayByAddingObject: qualifier];
      qualifier = AUTORELEASE([[[parent class] alloc]
				  initWithQualifierArray: quals]);
      [parentQualifiers removeLastObject];
    }

  return qualifier;
}

+ (EOQualifier *)qualifierToMatchAllValues: (NSDictionary *)values
{
  NSEnumerator *keyEnumerator;
  NSString *key;
  NSMutableArray *array;

  array = [NSMutableArray arrayWithCapacity: [values count]];

  keyEnumerator = [values keyEnumerator];
  while ((key = [keyEnumerator nextObject]))
    [array addObject: [EOKeyValueQualifier
			qualifierWithKey: key
			operatorSelector: EOQualifierOperatorEqual
			value: [values objectForKey: key]]];

  if ([array count] == 1)
    return [array objectAtIndex: 0];

  return [EOAndQualifier qualifierWithQualifierArray: array];
}

+ (EOQualifier *)qualifierToMatchAnyValue:(NSDictionary *)values
{
  NSEnumerator *keyEnumerator;
  NSString *key;
  NSMutableArray *array;

  array = [NSMutableArray arrayWithCapacity: [values count]];

  keyEnumerator = [values keyEnumerator];
  while ((key = [keyEnumerator nextObject]))
    [array addObject:[EOKeyValueQualifier
		       qualifierWithKey: key
		       operatorSelector: EOQualifierOperatorEqual
		       value: [values objectForKey: key]]];

  if ([array count] == 1)
    return [array objectAtIndex: 0];

  return [EOOrQualifier qualifierWithQualifierArray: array];
}

- (NSException *)validateKeysWithRootClassDescription: (EOClassDescription *)classDesc
{
  return [self notImplemented: _cmd]; //TODO
}

+ (NSArray *)allQualifierOperators
{ // rivedere
  return [NSArray arrayWithObjects:@"=", @"!=", @"<=", @"<", @">=", @">", @"doesContain", @"like", @"caseInsensitiveLike", nil];
}

+ (NSArray *)relationalQualifierOperators
{ // rivedere
  return [NSArray arrayWithObjects:@"=", @"!=", @"<=", @"<", @">=", @">", nil];
}

+ (NSString *)stringForOperatorSelector: (SEL)selector
{
  if (sel_eq(selector, EOQualifierOperatorEqual))
    return @"=";
  else if (sel_eq(selector, EOQualifierOperatorNotEqual))
    return @"!=";
  else if (sel_eq(selector, EOQualifierOperatorLessThan))
    return @"<";
  else if (sel_eq(selector, EOQualifierOperatorGreaterThan))
    return @">";
  else if (sel_eq(selector, EOQualifierOperatorLessThanOrEqualTo))
    return @"<=";
  else if (sel_eq(selector, EOQualifierOperatorGreaterThanOrEqualTo))
    return @">=";
  else if (sel_eq(selector, EOQualifierOperatorContains))
    return @"doesContain";
  else if (sel_eq(selector, EOQualifierOperatorLike))
    return @"like";
  else if (sel_eq(selector, EOQualifierOperatorCaseInsensitiveLike))
    return @"caseInsensitiveLike";

  return nil;
}

+ (SEL)operatorSelectorForString: (NSString *)string
{
  if ([string isEqualToString: @"="])
    return EOQualifierOperatorEqual;
  else if ([string isEqualToString: @"=="])
    return EOQualifierOperatorEqual;
  else if ([string isEqualToString: @"<="])
    return EOQualifierOperatorLessThanOrEqualTo;
  else if ([string isEqualToString: @"<"])
    return EOQualifierOperatorLessThan;
  else if ([string isEqualToString: @">="])
    return EOQualifierOperatorGreaterThanOrEqualTo;
  else if ([string isEqualToString: @">"])
    return EOQualifierOperatorGreaterThan;
  else if ([string isEqualToString: @"<>"])
    return EOQualifierOperatorNotEqual;
  else if ([string isEqualToString: @"!="])
    return EOQualifierOperatorNotEqual;
  else if ([string isEqualToString: @"doesContain"])
    return EOQualifierOperatorContains;
  else if ([string isEqualToString: @"like"])
    return EOQualifierOperatorLike;
  else if ([string isEqualToString: @"caseInsensitiveLike"])
    return EOQualifierOperatorCaseInsensitiveLike;
  else 
    {
      NSWarnMLog(@"No operator selector for string '%@'.", string);
      return (SEL)nil; // ????
    }
}

/**
 * NSCopying protocol
 * EOQualifiers are immutable.  Returns the receiver after retaining it.<br\>
 * If you wish to gain memory locality, you should recrate the qualifier
 * from scratch insuring that all referenced objects are also local to the new
 * zone.
 */
- (id)copyWithZone: (NSZone *)zone
{
  return RETAIN(self);
}

- (EOQualifier *)qualifierByApplyingBindings: (id)bindings
{
  return [self qualifierWithBindings: bindings
	       requiresAllVariables: NO];
}

- (EOQualifier *)qualifierByApplyingBindingsAllVariablesRequired: (id)bindings
{
  return [self qualifierWithBindings: bindings
	       requiresAllVariables: YES];
}

- (EOQualifier *)qualifierWithBindings: (NSDictionary *)bindings
		  requiresAllVariables: (BOOL)requiresAll
{
  // TODO
  [self notImplemented: _cmd];
  return nil;
}

- (NSArray *)bindingKeys
{
  // TODO
  [self notImplemented: _cmd];
  return nil;
}

//NO
- (BOOL)evaluateWithObject: (id)object
{
  [self notImplemented: _cmd];
  return NO;
}

- (NSString *)keyPathForBindingKey: (NSString *)key
{
  // TODO
  [self notImplemented: _cmd];
  return nil;
}

- (void) _addBindingsToDictionary: (id)param0
{
  [self notImplemented: _cmd]; //TODO
}

- (EOQualifier *)qualifierMigratedFromEntity: (EOEntity *)entity 
                            relationshipPath: (NSString *)relationshipPath
{
  return [self notImplemented: _cmd]; //TODO
}

- (id) _qualifierMigratedToSubEntity: (id)param0
                    fromParentEntity: (id)param1
{
  return [self notImplemented: _cmd]; //TODO
}

- (BOOL) usesDistinct
{
  [self notImplemented: _cmd]; //TODO
  return NO;
}

@end


@implementation EOQualifierVariable

+ (EOQualifierVariable *)variableWithKey: (NSString *)key
{
  return AUTORELEASE([[self alloc] initWithKey: key]);
}

- (EOQualifierVariable *)initWithKey: (NSString *)key
{
  if ((self = [super init]))
    {
      ASSIGN(_key, key);
    }

  return self;
}

- (NSString *)key
{
  return _key;
}

- (void)encodeWithCoder: (NSCoder *)coder
{
  [coder encodeObject: _key];
}

- (id)initWithCoder: (NSCoder *)coder
{
  if ((self = [super init]))
    {
      _key = RETAIN([coder decodeObject]);
    }

  return self;
}

- (id)valueByApplyingBindings: (id)bindings
{
  return [self notImplemented: _cmd]; //TODO
}

- (id)requiredValueByApplyingBindings: (id)bindings
{
  return [self notImplemented: _cmd]; //TODO
}

- (id) initWithKeyValueUnarchiver: (EOKeyValueUnarchiver *)unarchiver
{
  return [self notImplemented: _cmd]; //TODO
}

- (void)encodeWithKeyValueArchiver: (EOKeyValueArchiver *)archiver
{
  [self notImplemented: _cmd]; //TODO
}

@end


@implementation NSObject (EORelationalSelectors)

- (BOOL)isEqualTo: (id)object
{
  return [self isEqual: object];
}

- (BOOL)isLessThanOrEqualTo: (id)object
{
  NSComparisonResult res = [self compare: object];

  return (res == NSOrderedAscending || res == NSOrderedSame ? YES : NO);
}

- (BOOL)isLessThan: (id)object
{
  return ([self compare:object] == NSOrderedAscending ? YES : NO);
}

- (BOOL)isGreaterThanOrEqualTo: (id)object
{
  NSComparisonResult res = [self compare:object];

  return (res == NSOrderedDescending || res == NSOrderedSame ? YES : NO);
}

- (BOOL)isGreaterThan: (id)object
{
  return ([self compare: object] == NSOrderedDescending ? YES : NO);
}

- (BOOL)isNotEqualTo: (id)object
{
  return ([self isEqualTo: object] ? NO : YES);
}

- (BOOL)doesContain: (id)object
{
  if ([self isKindOfClass: [NSArray class]])
    return [(NSArray *)self containsObject: object];

  return NO;
}

- (BOOL)isLike: (NSString *)object
{
  return NO;
}

- (BOOL)isCaseInsensitiveLike: (NSString *)object
{
  return NO;
}

@end


@implementation NSString (EORelationalSelectors)

- (BOOL)isLike: (NSString *)object
{
  NSEmitTODO();  //TODO
  return [self isEqual: object] == NSOrderedSame;
}

- (BOOL)isCaseInsensitiveLike: (NSString *)object
{
  NSEmitTODO();  //TODO
  return [[self uppercaseString]
	   isEqual: [object uppercaseString]] == NSOrderedSame;
}

@end

@implementation NSArray (EOQualifierExtras)

#define MAX_STACK_OBJECTS 20

- (NSArray *)filteredArrayUsingQualifier: (EOQualifier *)qualifier
{
  unsigned    max = [self count];

  if (max != 0)
    {
      unsigned  i;
      id        obj[max>MAX_STACK_OBJECTS?0:max];
      id       *objPStart;
      id       *objP;
      id        anObject;
      SEL       oaiSEL = @selector(objectAtIndex:);
      IMP       oaiIMP = [self methodForSelector:oaiSEL];
      SEL       ewoSEL = @selector(evaluateWithObject:);
      BOOL      (*ewoIMP)(id, SEL, id);

      ewoIMP = (BOOL (*)(id, SEL, id))[qualifier methodForSelector:ewoSEL];

      objPStart = objP = (max > MAX_STACK_OBJECTS)?
	GSAutoreleasedBuffer(sizeof(id)*max):obj;

      for(i=0; i < max; i++)
	{
	  anObject = (*oaiIMP)(self, oaiSEL, i);

	  if((*ewoIMP)(qualifier, ewoSEL, anObject))
	    {
	      *objP++=anObject;
	    }
	}
      return [NSArray arrayWithObjects: objPStart count: objP - objPStart];
    }
  else
    {
      return self;
    }
}

#undef MAX_STACK_OBJECTS

@end
