/* 
   NSSet.m

   Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>

   This file is part of libFoundation.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

#include <stdarg.h>
#include <stdio.h>

#include <Foundation/common.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSString.h>
#include <Foundation/NSSet.h>
#include <Foundation/NSException.h>
#include <Foundation/NSCoder.h>
#include <Foundation/exceptions/GeneralExceptions.h>

#include "NSConcreteSet.h"

/*
 * NSSet 
 */

@implementation NSSet

/* Allocating and Initializing a Set */

+ (id)allocWithZone:(NSZone*)zone
{
    return NSAllocateObject( (self == [NSSet class]) ? 
	    [NSConcreteSet class] : self, 0, zone);
}

+ (id)set
{
    return [[[self alloc] init] autorelease];
}

+ (id)setWithArray:(NSArray*)array
{
    return [[[self alloc] initWithArray:array] autorelease];
}

+ (id)setWithObject:(id)anObject
{
    return [[[self alloc] initWithObjects:anObject, nil] autorelease];
}

+ (id)setWithObjects:(id)firstObj,...
{
    id set;
    va_list va;
    va_start(va, firstObj);
    set = [[[self alloc] initWithObject:firstObj arglist:va] autorelease];
    va_end(va);
    return set;
}

+ (id)setWithObjects:(id*)objects count:(unsigned int)count
{
    return [[[self alloc] initWithObjects:objects count:count] autorelease];
}

+ (id)setWithSet:(NSSet*)aSet
{
    return [[[self alloc] initWithSet:aSet] autorelease];
}

- (id)init
{
    [self subclassResponsibility:_cmd];
    return self;
}

- (id)initWithArray:(NSArray*)array
{
    int i, n = [array count];
    id *objects = Malloc(sizeof(id)*n);

    for (i=0; i<n; i++)
	objects[i] = [array objectAtIndex:i];
	    
    [self initWithObjects:objects count:n];
    
    Free(objects);
    return self;
}

- (id)initWithObjects:(id)firstObj,...
{
    va_list va;
    va_start(va, firstObj);
    [self initWithObject:firstObj arglist:va];
    va_end(va);
    return self;
}

- (id)initWithObject:(id)firstObject arglist:(va_list)argList
{
    id object;
    id *objs;
    va_list va = argList;
    int count = 0;
    
    for (object = firstObject; object; object = va_arg(va,id))
	count++;
    
    objs = Malloc(sizeof(id)*count);
    
    for (count=0, object=firstObject; object; object=va_arg(argList,id)) {
	objs[count] = object;
	count++;
    }

    [self initWithObjects:objs count:count];

    Free(objs);
    return self;
}

- (id)initWithObjects:(id*)objects count:(unsigned int)count
{
    [self subclassResponsibility:_cmd];
    return self;
}

- (id)initWithSet:(NSSet*)set copyItems:(BOOL)flag;
{
    NSEnumerator* keys = [set objectEnumerator];
    id key;
    id *objs;
    unsigned i = 0;
    
    objs = Malloc(sizeof(id)*[set count]);

    while((key = [keys nextObject]))
	objs[i++] = flag ? [[key copyWithZone:NULL] autorelease] : key;
    
    [self initWithObjects:objs count:i];
    
    Free(objs);
    return self;
}

- (id)initWithSet:(NSSet*)aSet
{
    return [self initWithSet:aSet copyItems:NO];
}

/* Querying the Set */

- (NSArray*)allObjects
{
    id array;
    id *objs = Malloc(sizeof(id)*[self count]);
    id keys = [self objectEnumerator];
    id key;
    unsigned i = 0;

    while ((key = [keys nextObject]))
	objs[i++] = key;
    array = [[[NSArray alloc] initWithObjects:objs count:[self count]]
		autorelease];
	    
    Free(objs);
    return array;
}

- (id)anyObject
{
    return [[self objectEnumerator] nextObject];
}

- (BOOL)containsObject:(id)anObject
{
    return [self member:anObject] ? YES : NO;
}

- (unsigned int)count
{
    [self subclassResponsibility:_cmd];
    return 0;
}

- (id)member:(id)anObject
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSEnumerator*)objectEnumerator
{
    [self subclassResponsibility:_cmd];
    return nil;
}

/* Sending Messages to Elements of the Set */

- (void)makeObjectsPerform:(SEL)aSelector
{
    id keys = [self objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	[key perform:aSelector];
}

- (void)makeObjectsPerform:(SEL)aSelector withObject:(id)anObject
{
    id keys = [self objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	[key perform:aSelector withObject:anObject];
}

/* Comparing Sets */

- (BOOL)intersectsSet:(NSSet*)otherSet
{
    id keys = [self objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	if ([otherSet containsObject:key])
	    return YES;
    return NO;
}

- (BOOL)isEqualToSet:(NSSet*)otherSet
{
    id keys = [self objectEnumerator];
    id key;

    if ([self count] != [otherSet count])
	return NO;
    
    while ((key = [keys nextObject]))
	if (![otherSet containsObject:key])
	    return NO;
    
    return YES;
}

- (BOOL)isSubsetOfSet:(NSSet*)otherSet
{
    id keys = [self objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	if ([otherSet containsObject:key])
	    return NO;
    return YES;
}

/* Creating a String Description of the Set */

- (NSString*)descriptionWithLocale:(NSDictionary*)locale
{
    return [self descriptionWithLocale:locale indent:0];
}

- (NSString*)description
{
    return [self descriptionWithLocale:nil indent:0];
}

- (NSString*)descriptionWithLocale:(NSDictionary*)locale
   indent:(unsigned int)indent;
{
    NSMutableString* description = [NSMutableString stringWithCString:"(\n"];
    unsigned int indent1 = indent + 4;
    NSMutableString* indentation
	    = [NSString stringWithFormat:
			[NSString stringWithFormat:@"%%%dc", indent1], ' '];
    unsigned int count = [self count];
    SEL sel = @selector(appendString:);
    IMP imp = [description methodForSelector:sel];

    if(count) {
	id pool = [NSAutoreleasePool new];
	id object;
	id stringRepresentation;
	id enumerator = [self objectEnumerator];

	object = [enumerator nextObject];
	if ([object respondsToSelector:
		@selector(descriptionWithLocale:indent:)])
	    stringRepresentation = [object descriptionWithLocale:locale
		indent:indent1];
	else if ([object respondsToSelector:
		@selector(descriptionWithLocale:)])
	    stringRepresentation = [object descriptionWithLocale:locale];
	else
	    stringRepresentation = [object stringRepresentation];

	(*imp)(description, sel, indentation);
	(*imp)(description, sel, stringRepresentation);

	while((object = [enumerator nextObject])) {
	    if ([object respondsToSelector:
		    @selector(descriptionWithLocale:indent:)])
		stringRepresentation = [object descriptionWithLocale:locale
		    indent:indent1];
	    else if ([object respondsToSelector:
		    @selector(descriptionWithLocale:)])
		stringRepresentation = [object descriptionWithLocale:locale];
	    else
		stringRepresentation = [object stringRepresentation];

	    (*imp)(description, sel, @",\n");
	    (*imp)(description, sel, indentation);
	    (*imp)(description, sel, stringRepresentation);
	}
	[pool release];
    }

    (*imp)(description, sel, indent
	    ? [NSMutableString stringWithFormat:
			[NSString stringWithFormat:@"\n%%%dc)", indent], ' ']
	    : [NSMutableString stringWithCString:"\n)"]);
    return description;
}

/* From adopted/inherited protocols */

- (unsigned int)hash
{
    return [self count];
}

- (BOOL)isEqual:(id)anObject
{
    if ([anObject isKindOfClass:isa] == NO)
	    return NO;
    return [self isEqualToSet:anObject];
}

- (id)copyWithZone:(NSZone*)zone
{
    if (NSShouldRetainWithZone(self, zone))
	return [self retain];
    else
	return [[NSSet allocWithZone:zone] initWithSet:self copyItems:YES];
}

- (id)mutableCopyWithZone:(NSZone*)zone
{
    return [[NSMutableSet allocWithZone:zone] initWithSet:self];
}

- (Class)classForCoder
{
    return [NSSet class];
}

- (void)encodeWithCoder:(NSCoder*)aCoder
{
    NSEnumerator* enumerator = [self objectEnumerator];
    int count = [self count];
    id object;

    [aCoder encodeValueOfObjCType:@encode(int) at:&count];
    while((object = [enumerator nextObject]))
	[aCoder encodeObject:object];
}

- (id)initWithCoder:(NSCoder*)aDecoder
{
    int i, count;
    id* objects;

    [aDecoder decodeValueOfObjCType:@encode(int) at:&count];
    objects = Malloc(sizeof(id) * count);
    for(i = 0; i < count; i++)
	objects[i] = [aDecoder decodeObject];

    [self initWithObjects:objects count:count];
    
    Free(objects);

    return self;
}

@end /* NSSet */

/*
 * NSMutableSet 
 */

@implementation NSMutableSet

/* Methods from NSSet */

+ (id)allocWithZone:(NSZone*)zone
{
    return NSAllocateObject( (self == [NSMutableSet class]) ? 
	    [NSConcreteMutableSet class] : self, 0, zone);
}

+ (id)setWithCapacity:(unsigned)numItems
{
    return [[[[self class] alloc] initWithCapacity:numItems] autorelease];
}

- (id)initWithCapacity:(unsigned)numItems
{
    [self subclassResponsibility:_cmd];
    return self;
}

/* Adding Objects */

- (void)addObject:(id)object
{
    [self subclassResponsibility:_cmd];
}

- (void)addObjectsFromArray:(NSArray*)array
{
    int i, n = [array count];
    for (i=0; i<n; i++)
	[self addObject:[array objectAtIndex:i]];
}

- (void)unionSet:(NSSet*)other
{
    id keys = [other objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	[self addObject:key];
}

- (void)setSet:(NSSet*)other
{
    [self removeAllObjects];
    [self unionSet:other];
}

/* Removing Objects */

- (void)intersectSet:(NSSet*)other
{
    id keys = [self objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	if ([other containsObject:key]==NO)
	    [self removeObject:key];
}

- (void)minusSet:(NSSet*)other
{
    id keys = [other objectEnumerator];
    id key;

    while ((key = [keys nextObject]))
	[self removeObject:key];
}

- (void)removeAllObjects
{
    id en = [self objectEnumerator];
    id key;
    while ((key=[en nextObject]))
	[self removeObject:key];
}

- (void)removeObject:(id)object
{
    [self subclassResponsibility:_cmd];
}

- (id)copyWithZone:(NSZone*)zone
{
    return [[NSSet allocWithZone:zone] initWithSet:self copyItems:YES];
}

- (Class)classForCoder
{
    return [NSMutableSet class];
}

@end /* NSMutableSet */

