/* Implementation for Objective-C Collection object

   Copyright (C) 1993 R. Andrew McCallum <mccallum@cs.rochester.edu>
   Dept. of Computer Science, U. of Rochester, Rochester, NY  14627

   This file is part of the GNU Objective-C Collection library.

   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; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ 

#include <coll/Collection.h>
#include <coll/CollectionPrivate.h>
#include <objc/objc-api.h>
#include <stdio.h>		/* for vfprintf() in "-warning:" */
#include <stdarg.h>

const elt COLL_NO_ELEMENT = ((elt)(~0L));

/* I'd like this to be made public */
int 
objc_sizeof_type(const char* type)
{
  switch(*type) {
  case _C_ID: return sizeof(id);
    break;

  case _C_CLASS:
    return sizeof(Class*);
    break;

  case _C_SEL:
    return sizeof(SEL);
    break;

  case _C_CHR:
    return sizeof(char);
    break;
    
  case _C_UCHR:
    return sizeof(unsigned char);
    break;

  case _C_SHT:
    return sizeof(short);
    break;

  case _C_USHT:
    return sizeof(unsigned short);
    break;

  case _C_INT:
  case _C_LNG:
    return sizeof(int);
    break;

  case _C_UINT:
  case _C_ULNG:
    return sizeof(unsigned int);
    break;

  case _C_ATOM:
  case _C_CHARPTR:
    return sizeof(char*);
    break;

  default:
    fprintf(stderr, "objc_write_type: cannot parse typespec: %s\n", type);
    abort();
  }
}

@implementation Collection
  
+ initialize
{
    if (self == [Collection class])
      {
	/* This should be checked at compile time.  Why do I get a parse
	   error when I use sizeof() in a #if () statement? */
	if (sizeof(elt) != sizeof(void*))
	  [self error:"(sizeof(elt) == sizeof(void*)) not satisfied"];
	[self setVersion:-1];	/* alpha release */
      }
    return self;
}

// INITIALIZING AND FREEING;
  
/* This is the designated initializer of this class */
- initDescription:(const char *)type
{
  [super init];
  if (objc_sizeof_type(type) > sizeof(elt))
    [self error:
	  "Collection can't store elements with size greater than (elt)"];
  OBJC_MALLOC(_description, char, strlen(type)+1);
  strcpy(_description, type);
  /* This is not portable... Change this switch. */
  switch (*_description) 
    {
    case _C_CHARPTR: 
    case _C_ATOM: 
      _compare_func = (compare_func_type)coll_compare_strings;
      break;
      
    case _C_ID: 
    case _C_CLASS:		/* isEqual: on classes works well? */
      _compare_func = (compare_func_type)coll_compare_objects;
      break;
      
    case _C_PTR: 
    case _C_SEL:
      _compare_func = (compare_func_type)coll_compare_ptrs;
      break;
      
    case _C_INT: 
    case _C_UINT: 
    case _C_FLT:		/* Yipes */
      _compare_func = (compare_func_type)coll_compare_ints;
      break;

    case _C_LNG: 
    case _C_ULNG: 
      _compare_func = (compare_func_type)coll_compare_long_ints;
      break;
      
    default : 
      [self error:"Element type (%s) not supported.\n", _description];
    }
  return self;
}

- initDescription:(const char *)type with: (unsigned)numElements, ...
{
  va_list ap;

  [self initDescription:type];
  va_start(ap, numElements);
  // We should use objc_msg_lookup here?;
  while (numElements--)
    [self addElement:va_arg(ap, elt)];
  va_end(ap);
  return self;
}

- init
{
  return [self initDescription:"@"];
}

- initWith: (unsigned)numElements, ...
{
  va_list ap;

  [self init];
  va_start(ap, numElements);
  // We should use objc_msg_lookup here?;
  while (numElements--)
    [self addElement:va_arg(ap, elt)];
  va_end(ap);
  return self;
}

- free
{
  OBJC_FREE(_description);
  return [super free];
}

- freeContents
{
  if (CONTAINS_OBJECTS)
    [self makeObjectsPerform:@selector(free)];
  [self empty];
  return self;
}


// ADDING;

- addElement: (elt)newElement
{
  return [self subclassResponsibility:_cmd];
}

- addElementIfAbsent: (elt)newElement
{
  if (![self includesElement:newElement])
    return [self addElement:newElement];
  return nil;
}

- addContentsOf: (id <Collecting>)aCollection
{
  id (*addElementImp)(id,SEL,elt) = (id(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(addElement:));

  /* Should all these doIt()'s be 'static inline' ? */
  void doIt(elt e) 
    {
      addElementImp(self, @selector(addElement:), e);
    }

  [aCollection withElementsCall:doIt];
  return self;
}

- addContentsOfIfAbsent: (id <Collecting>)aCollection
{
  id (*addElementImp)(id,SEL,elt) = (id(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(addElement:));
  BOOL (*includesElementImp)(id,SEL,elt) = (BOOL(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(includesElement:));

  void doIt(elt e) 
    {
      if (!includesElementImp(self, @selector(includesElement), e))
	addElementImp(self, @selector(addElement:), e);
    }

  [aCollection withElementsCall:doIt];
  return self;
}

- addCount: (unsigned)count, ...
{
  va_list ap;

  va_start(ap, count);
  while (count--)
    [self addElement:va_arg(ap, elt)];
  va_end(ap);
  return self;
}


// REMOVING AND REPLACING;

- (elt) removeElement: (elt)oldElement
{
  return [self subclassResponsibility:_cmd];
}

- (elt ) removeAllOccurrencesOfElement: (elt )oldElement
{
  elt r;
  BOOL (*includesElementImp)(id,SEL,elt) = (BOOL(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(includesElement:));
  elt (*removeElementImp)(id,SEL,elt) = (elt(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(removeElement:));

  while (includesElementImp(self, @selector(includesElement:), oldElement))
    {
      r = removeElementImp(self, @selector(removeElement:), oldElement);
    }
  return r;
}

- removeContentsIn: (id <Collecting>)aCollection
{
  BOOL (*includesElementImp)(id,SEL,elt) = (BOOL(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(includesElement:));
  elt (*removeElementImp)(id,SEL,elt) = (elt(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(removeElement:));

  void doIt(elt e)
    {
      if (includesElementImp(self, @selector(includesElement:), e))
	removeElementImp(self, @selector(removeElement:), e);
    }

  [aCollection withElementsCall:doIt];
  return self;
}

- removeContentsNotIn: (id <Collecting>)aCollection
{
  BOOL (*includesElementImp)(id,SEL,elt) = (BOOL(*)(id,SEL,elt))
    objc_msg_lookup(aCollection, @selector(includesElement:));
  elt (*removeElementImp)(id,SEL,elt) = (elt(*)(id,SEL,elt))
    objc_msg_lookup(self, @selector(removeElement:));

  void doIt(elt e)
    {
      if (!includesElementImp(aCollection, @selector(includesElement:), e))
	removeElementImp(self, @selector(removeElement:), e);
    }

  [self withElementsCall:doIt];
  return self;
}

/* remember this has to be overridden for IndexedCollection's */
- (elt) replaceElement: (elt )oldElement with: (elt )newElement
{
  elt ret;
  ret = [self removeElement:oldElement];
  [self addElement:newElement];
  return ret;
}

- (elt) replaceAllOccurrencesOfElement: (elt)oldElement with: (elt)newElement
{
  elt ret;
  // Use objc_msg_lookup here also;
  if (_compare_func(oldElement.void_ptr_t, newElement.void_ptr_t))
    return self;
  while ([self includesElement:oldElement])
    ret = [self replaceElement:oldElement with:newElement];
  return ret;
}

- uniqueContents
{
  // Use objc_msg_lookup here also;
  void doIt(elt e)
    {
      while ([self occurrencesOfElement:e] > 1)
	[self removeElement:e];
    }
  [self withElementsCall:doIt];
  return self;
}

- empty
{
  // Use objc_msg_lookup here also;
  void doIt(elt e)
    {
      [self removeElement:e];
    }
  [[[self shallowCopy] withElementsCall:doIt] free];
  return self;
}

// TESTING;

- (BOOL) isEmpty
{
  return ([self count] == 0);
}

/* Potentially inefficient, may be overridden in subclasses */
- (BOOL) includesElement: (elt)anElement
{
  BOOL test(elt e)
    {
      if (_compare_func(anElement.void_ptr_t, e.void_ptr_t))
	return YES;
      else
	return NO;
    }

  return [self trueForAnyByCalling:test];
}

- (BOOL) includesSubsetOf: (id <Collecting>)aCollection
{
  BOOL test(elt e) 
    { 
      return [self includesElement:e]; 
    }
  return [aCollection trueForAllByCalling:test];
}

- (BOOL) includesSameContents: (id <Collecting>)aCollection
{
  // not very efficient;
  return ([self includesSubsetOf:aCollection] &&
	  [aCollection includesSubsetOf:self]);
}

/* Is this what we want? */
- (BOOL) isEqual: anObject
{
  if ([self class] == [anObject class] &&
      [self includesSameContents: anObject])
    return YES;
  else
    return NO;
}


- (BOOL) isDisjointFrom: (id <Collecting>)aCollection
{
  // Use objc_msg_lookup here also;
  BOOL flag = YES;
  void doIt(elt e)
    {
      if (![aCollection includesElement:e])
	flag = NO;
    }
  [self withElementsCall:doIt whileTrue:&flag];
  return !flag;
}

- (BOOL) trueForAllByCalling: (BOOL(*)(elt))aFunc
{
  BOOL flag = YES;
  void doIt(elt e)
    {
      if (!(aFunc(e)))
	flag = NO;
    }
  [self withElementsCall:doIt whileTrue:&flag];
  return flag;
}

- (BOOL) trueForAnyByCalling: (BOOL(*)(elt))aFunc;
{
  BOOL flag = YES;
  void doIt(elt e)
    {
      if (aFunc(e))
	flag = NO;
    }
  [self withElementsCall:doIt whileTrue:&flag];
  return !flag;
}

/* Inefficient, so should be overridden in subclasses. */
- (unsigned) count
{
  unsigned n = 0;
  void doIt(elt e)
    {
      n++;
    }
  [self withElementsCall:doIt];
  return n;
}

- (unsigned) occurrencesOfElement: (elt)anElement
{
  unsigned count = 0;
  void doIt(elt e) 
    {
      if (_compare_func(anElement.void_ptr_t, e.void_ptr_t))
	count++;
    }
  [self withElementsCall:doIt];
  return count;
}

- (unsigned) occurrencesOfObject: anObject
{
  return [self occurrencesOfElement:anObject];
}

- (const char *) contentsDescription
{
  return _description;
}

- (BOOL) containsObjects
{
  return CONTAINS_OBJECTS;
}


// ENUMERATING;

- (BOOL) getNextElement:(elt *)anElementPtr withEnumState: (void**)enumState
{
  [self subclassResponsibility:_cmd];
  return NO;
}

/* Getting objects one at a time.  Pass NULL enumState to start.
   It will return COLL_NO_ELEMENT when done. */
- nextObject: (void**)enumState
{
  elt o;

  if ([self getNextElement:&o withEnumState:enumState])
    return o.id_t;
  else
    return COLL_NO_OBJECT;
}

- withElementsCall: (void(*)(elt))aFunc whileTrue:(BOOL *)flag
{
  void *enumState = 0;
  elt e;

  while (*flag && [self getNextElement:&e withEnumState:&enumState])
    aFunc(e);
  return self;
}

- withElementsCall: (void(*)(elt))aFunc
{
  BOOL flag = YES;
  return [self withElementsCall:aFunc whileTrue:&flag];
}


// COPYING;

- shallowCopy
{
  return [self shallowCopyAs:[self species]];
}

/* the copy to be filled by -shallowCopySelect, -shallowCopyCollect, etc... */
- emptyCopyAs: aCollectionClass
{
  return [[aCollectionClass alloc] initDescription:_description];
}

/* the right thing for ordered objects also? */
- shallowCopyAs: (id <Collecting>)aCollectionClass
{
  id newColl = [self emptyCopyAs:aCollectionClass];
  id (*addElementImp)(id,SEL,elt) = (id(*)(id,SEL,elt))
    objc_msg_lookup(newColl, @selector(addElement:));

  void doIt(elt e) 
    {
      addElementImp(newColl, @selector(addElement:), e);
    }

  [self withElementsCall:doIt];
  return newColl;
}

- deepen
{
  /* could use objc_msg_lookup here too */
  void doIt(elt o)
    {
      [self replaceElement:o with:[[o.id_t shallowCopy] deepen]];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return self;
  /* Is this shallowCopy necessary?  I think so, unfortunately. */
  [[[self shallowCopy] withElementsCall:doIt] free];
  return self;
}

- species
{
  return [self class];
}


// COPYING ENUMERATORS;

- shallowCopySelectByCalling: (BOOL(*)(elt))aFunc
{
  id newColl = [self emptyCopyAs:[self species]];
  id (*addElementImp)(id,SEL,elt) = (id(*)(id,SEL,elt))
    objc_msg_lookup(newColl, @selector(addElement:));

  void doIt(elt e) 
    {
      if (aFunc(e))
	addElementImp(newColl, @selector(addElement:), e);
    }

  [self withElementsCall:doIt];
  return newColl;
}

- shallowCopyRejectByCalling: (BOOL(*)(elt))aFunc
{
  id newColl = [self emptyCopyAs:[self species]];
  id (*addElementImp)(id,SEL,elt) = (id(*)(id,SEL,elt))
    objc_msg_lookup(newColl, @selector(addElement:));
  void doIt(elt e) 
    {
      if (!aFunc(e))
	addElementImp(newColl, @selector(addElement:), e);
    }
  return newColl;
}

- shallowCopyCollectByCalling: (elt (*)(elt))aFunc
{
  id newColl = [self emptyCopyAs:[self species]];
  id (*addElementImp)(id,SEL,elt) = (id(*)(id,SEL,elt))
    objc_msg_lookup(newColl, @selector(addElement:));
  void doIt(elt e) 
    {
      addElementImp(newColl, @selector(addElement:), aFunc(e));
    }
  return newColl;
}


// NON-COPYING ENUMERATORS;

- (elt ) detectElementByCalling: (BOOL(*)(elt))aFunc
{
  BOOL flag = YES;
  elt detectedElement;
  void doIt(elt e)
    {
      if (aFunc(e)) {
	flag = NO;
	detectedElement = e;
      }
    }
  detectedElement = COLL_NO_ELEMENT;
  [self withElementsCall:doIt whileTrue:&flag];
  return detectedElement;
}

- (elt) maxElementByCalling: (int(*)(elt,elt))aFunc
{
  elt max;
  BOOL firstTime = YES;
  void doIt(elt e)
    {
      if (firstTime)
	{
	  firstTime = NO;
	  max = e;
	}
      else
	{
	  if (aFunc(e,max) > 0)
	    max = e;
	}
    }
  [self withElementsCall:doIt];
  return max;
}

- (elt) minElementByCalling: (int(*)(elt,elt))aFunc
{
  elt min;
  BOOL firstTime = YES;
  void doIt(elt e)
    {
      if (firstTime)
	{
	  firstTime = NO;
	  min = e;
	}
      else
	{
	  if (aFunc(e,min) < 0)
	    min = e;
	}
    }
  [self withElementsCall:doIt];
  return min;
}



- inject: (void*)initialData byCalling: (void* (*)(void*,elt))aFunc
{
  void doIt(elt e)
    {
	initialData = aFunc(initialData, e);
    }
  [self withElementsCall:doIt];
  return self;
}


// SMALLTALK BLOCKS AS SELECTORS;

- (BOOL) trueForAllByPerforming: (SEL)aBoolSel in: selObject
{
  BOOL (*aBoolSelImp)(id,SEL,id) = (BOOL(*)(id,SEL,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t);
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAllByCalling:test];
}

- (BOOL) trueForAllByPerforming: (SEL)aBoolSel in: selObject with: argObject
{
  BOOL (*aBoolSelImp)(id,SEL,id,id) = (BOOL(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t, argObject);
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAllByCalling:test];
}

- (BOOL) trueForAllByPerforming: (SEL)aBoolSel
{
  BOOL test(elt e)
    {
      /* Is this the correct casting magic? */
      return (unsigned)[e.id_t perform:aBoolSel];
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAllByCalling:test];
}

- (BOOL) trueForAllByPerforming: (SEL)aBoolSel with: argObject
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t  perform:aBoolSel with:argObject];
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAllByCalling:test];
}

- (BOOL) trueForAnyByPerforming: (SEL)aBoolSel in: selObject
{
  BOOL (*aBoolSelImp)(id,SEL,id) = (BOOL(*)(id,SEL,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t);
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAnyByCalling:test];
}

- (BOOL) trueForAnyByPerforming: (SEL)aBoolSel in: selObject with: argObject
{
  BOOL (*aBoolSelImp)(id,SEL,id,id) = (BOOL(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t, argObject);
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAnyByCalling:test];
}

- (BOOL) trueForAnyByPerforming: (SEL)aBoolSel
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t perform:aBoolSel];
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAnyByCalling:test];
}

- (BOOL) trueForAnyByPerforming: (SEL)aBoolSel with: argObject
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t perform:aBoolSel with:argObject];
    }

  if (OBJECTS_REQUIRED_WARNING())
    return NO;
  return [self trueForAnyByCalling:test];
}

- makeObjectsPerform: (SEL)aSel
{
  void doIt(elt e)
    {
      [e.id_t perform:aSel];
    }

  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  [self withElementsCall:doIt];
  return self;
}

- makeObjectsPerform: (SEL)aSel with: argObject
{
  void doIt(elt e)
    {
      [e.id_t perform:aSel with:argObject];
    }

  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  [self withElementsCall:doIt];
  return self;
}

- shallowCopySelectByPerforming: (SEL)aBoolSel in: selObject
{
  BOOL (*aBoolSelImp)(id,SEL,id) = (BOOL(*)(id,SEL,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t);
    }
  return [self shallowCopySelectByCalling:test];
}

- shallowCopySelectByPerforming: (SEL)aBoolSel in: selObject with: argObject
{
  BOOL (*aBoolSelImp)(id,SEL,id,id) = (BOOL(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t, argObject);
    }
  return [self shallowCopySelectByCalling:test];
}

- shallowCopySelectByPerforming: (SEL)aBoolSel
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t perform:aBoolSel];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopySelectByCalling:test];
}

- shallowCopySelectByPerforming: (SEL)aBoolSel with: argObject
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t perform:aBoolSel with:argObject];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopySelectByCalling:test];
}

- shallowCopyRejectByPerforming: (SEL)aBoolSel in: selObject
{
  BOOL (*aBoolSelImp)(id,SEL,id) = (BOOL(*)(id,SEL,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t);
    }
  return [self shallowCopyRejectByCalling:test];
}

- shallowCopyRejectByPerforming: (SEL)aBoolSel in: selObject with: argObject
{
  BOOL (*aBoolSelImp)(id,SEL,id,id) = (BOOL(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(elt e)
    {
      return aBoolSelImp(selObject, aBoolSel, e.id_t, argObject);
    }
  return [self shallowCopyRejectByCalling:test];
}

- shallowCopyRejectByPerforming: (SEL)aBoolSel
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t perform:aBoolSel];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopyRejectByCalling:test];
}

- shallowCopyRejectByPerforming: (SEL)aBoolSel with: argObject
{
  BOOL test(elt e)
    {
      return (unsigned)[e.id_t perform:aBoolSel with:argObject];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopyRejectByCalling:test];
}

- shallowCopyCollectByPerforming: (SEL)aSel in: selObject
{
  id (*aSelImp)(id,SEL,id) = (id(*)(id,SEL,id))
    objc_msg_lookup(selObject, aSel);
  elt returnIt(elt e)
    {
      return (elt)aSelImp(selObject, aSel, e.id_t);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopyCollectByCalling:returnIt];
}

- shallowCopyCollectByPerforming: (SEL)aSel in: selObject with: argObject
{
  id (*aSelImp)(id,SEL,id,id) = (id(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aSel);
  elt returnIt(elt e)
    {
      return (elt)aSelImp(selObject, aSel, e.id_t, argObject);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopyCollectByCalling:returnIt];
}

- shallowCopyCollectByPerforming: (SEL)aSel
{
  elt returnIt(elt e)
    {
      return (elt)[e.id_t perform:aSel];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopyCollectByCalling:returnIt];
}

- shallowCopyCollectByPerforming: (SEL)aSel with: argObject
{
  elt returnIt(elt e)
    {
      return (elt)[e.id_t perform:aSel with:argObject];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self shallowCopyCollectByCalling:returnIt];
}

- detectByPerforming: (SEL)aBoolSel in: selObject
{
  BOOL (*aBoolSelImp)(id,SEL,id) = (BOOL(*)(id,SEL,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(id e)
    {
      return aBoolSelImp(selObject, aBoolSel, e);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self detectObjectByCalling:test];
}

- detectByPerforming: (SEL)aBoolSel in: selObject with: argObject
{
  BOOL (*aBoolSelImp)(id,SEL,id,id) = (BOOL(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aBoolSel);
  BOOL test(id e)
    {
      return aBoolSelImp(selObject, aBoolSel, e, argObject);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self detectObjectByCalling:test];
}

- detectByPerforming: (SEL)aBoolSel
{
  BOOL test(id e)
    {
      return (unsigned)[e perform:aBoolSel];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self detectObjectByCalling:test];
}

- detectByPerforming: (SEL)aBoolSel with: argObject
{
  BOOL test(id e)
    {
      return (unsigned)[e perform:aBoolSel with:argObject];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self detectObjectByCalling:test];
}

- maxObjectByCalling: (int(*)(id,id))aFunc
{
  id max;
  BOOL firstTime = YES;
  void doIt(id e)
    {
      if (firstTime)
	{
	  firstTime = NO;
	  max = e;
	}
      else
	{
	  if (aFunc(e,max) > 0)
	    max = e;
	}
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  [self withObjectsCall:doIt];
  return max;
}

- minObjectByCalling: (int(*)(id,id))aFunc
{
  id min;
  BOOL firstTime = YES;
  void doIt(id e)
    {
      if (firstTime)
	{
	  firstTime = NO;
	  min = e;
	}
      else
	{
	  if (aFunc(e,min) < 0)
	    min = e;
	}
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  [self withObjectsCall:doIt];
  return min;
}

- maxByPerforming: (SEL)aSortingSel
{
  int compare(id e1, id e2)
    {
      return (int)[e1 perform:aSortingSel with:e2];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self maxObjectByCalling:compare];
}

- maxByPerforming: (SEL)aSortingSel in: selObject
{
  int (*aSortingSelImp)(id,SEL,id,id) = (int(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aSortingSel);
  int compare(id e1, id e2)
    {
      return aSortingSelImp(selObject, aSortingSel, e1, e2);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self maxObjectByCalling:compare];
}

- minByPerforming: (SEL)aSortingSel
{
  int compare(id e1, id e2)
    {
      return (int)[e1 perform:aSortingSel with:e2];
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self minObjectByCalling:compare];
}

- minByPerforming: (SEL)aSortingSel in: selObject
{
  int (*aSortingSelImp)(id,SEL,id,id) = (int(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aSortingSel);
  int compare(id e1, id e2)
    {
      return aSortingSelImp(selObject, aSortingSel, e1, e2);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self minObjectByCalling:compare];
}

- inject: initialArgObject byPerforming: (SEL)aSel in: selObject
{
  id (*aSelImp)(id,SEL,id,id) = (id(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aSel);
  void* returnIt(void* d, elt e)
    {
      return aSelImp(selObject, aSel, (id)d, e.id_t);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self inject:initialArgObject byCalling:returnIt];
}


// OBJECT-COMPATIBLE MESSAGE NAMES

// ADDING;

- addObject: newObject
{
  return [self addElement:newObject];
}

- addObjectIfAbsent: newObject
{
  return [self addElementIfAbsent:newObject];
}

// REMOVING AND REPLACING;

- removeObject: oldObject
{
  return [self removeElement:oldObject].id_t;
}

- removeAllOccurrencesOfObject: oldObject
{
  return (id)[self removeAllOccurrencesOfObject:oldObject];
}

- replaceObject: oldObject with: newObject
{
  return [self replaceElement:oldObject with:newObject].id_t;
}

- replaceAllOccurrencesOfObject: oldObject with: newObject
{
  return [self replaceAllOccurrencesOfElement:oldObject with:newObject].id_t;
}

// TESTING;

- (BOOL) includesObject: anObject
{
  return [self includesElement:anObject];
}


// ENUMERATING

- withObjectsCall: (void(*)(id))aFunc
{
  void doIt(elt e)
    {
      aFunc(e.id_t);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self withElementsCall:doIt];
}

- withObjectsCall: (void(*)(id))aFunc whileTrue:(BOOL *)flag
{
  void doIt(elt e)
    {
      aFunc(e.id_t);
    }
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self withElementsCall:doIt whileTrue:flag];
}

- withObjectsPerform: (SEL)aSel in: selObject
{
  id (*aSelImp)(id,SEL,id) = (id(*)(id,SEL,id))
    objc_msg_lookup(selObject, aSel);
  void doIt(elt e)
    {
      aSelImp(selObject, aSel, e.id_t);
    }

  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  [self withElementsCall:doIt];
  return self;
}

- withObjectsPerform: (SEL)aSel in: selObject with: argObject
{
  id (*aSelImp)(id,SEL,id,id) = (id(*)(id,SEL,id,id))
    objc_msg_lookup(selObject, aSel);
  void doIt(elt e)
    {
      aSelImp(selObject, aSel, e.id_t, argObject);
    }

  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  [self withElementsCall:doIt];
  return self;
}

// NON-COPYING ENUMERATORS;

- detectObjectByCalling: (BOOL(*)(id))aFunc
{
  if (OBJECTS_REQUIRED_WARNING())
    return nil;
  return [self detectElementByCalling:(BOOL(*)(elt))aFunc].id_t;
}

/* This printing stuff will change when we get Stream objects */

+ printElement: (elt)anElement description: (const char *)type
{
  switch (*type)
    {
    case _C_CHARPTR: 
    case _C_ATOM: 
      printf("\"%s\"", anElement.char_ptr_t);
      break;
      
    case _C_ID: 
    case _C_CLASS:
      printf("%s:0x%x", [anElement.id_t name], anElement.unsigned_int_t);
      break;
      
    case _C_PTR: 
      printf("0x%x", anElement.unsigned_int_t);
      break;
      
    case _C_SEL:
      printf("%s", sel_get_name(anElement.SEL_t));
      break;
      
    case _C_INT: 
      printf("%d", anElement.int_t);
      break;

    case _C_UINT: 
    case _C_LNG: 
    case _C_ULNG: 
      printf("%d", anElement.unsigned_int_t);
      break;

    case _C_FLT:
      printf("%f", anElement.float_t);
      break;
      
    default : 
      printf("unknown?");
    }
  return self;
}

- printElement: (elt)anElement
{
  [[self class] printElement:anElement description:_description];
  return self;
}

- printForDebugger
{
  void doIt(elt e)
    {
      [self printElement:e];
      printf(" ");
    }
  [self withElementsCall:doIt];
  printf(" :%s\n", [self name]);
  return self;
}

- oldprintForDebugger
{
  void *s = 0;
  elt e;

  while ([self getNextElement:&e withEnumState:&s])
    [self printElement:e];
  printf(" :%s\n", [self name]);
  return self;
}


// WARNING;

/* We should have a more general warning mechanism. */

static classWarn = YES;		/* print warnings or not */

- warning:(const char *)aString,...
{
#define FMT "warning: %s (%s)\n %s\n"
  char fmt[(strlen(FMT)+strlen(object_get_class_name(self))
            +((aString!=0)?strlen(aString):0)+8)];
  va_list ap;

  if (classWarn)
    {
      sprintf(fmt, FMT, object_get_class_name(self),
	      object_is_instance(self)?"instance":"class",
	      (aString!=0)?aString:"");
      va_start(ap, aString);
      vfprintf(stderr, fmt, ap);
      va_end(ap);
    }
  return self;
#undef FMT
}


@end


@implementation Collection (ErrorReporting)

// ERROR REPORTING;

- errorElementNotFound: (elt)anElement inMethod: (SEL)aSel
{
  [self warning:"in %s, couldn't find element 0x%x", 
	sel_get_name(aSel), anElement.unsigned_int_t];
  return self;
}

- errorObjectsRequiredInMethod: (SEL)aSel
{
  [self warning:"in %s, requires contents to be objects",
	sel_get_name(aSel)];
  return self;
}

@end

