/* 
   EOPrimaryKeyDictionary.m

   Copyright (C) 1996 Free Software Foundation, Inc.

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

   This file is part of the GNUstep Database 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; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <Foundation/NSZone.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSException.h>
#include <Foundation/NSUtilities.h>

#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include "EOPrimaryKeyDictionary.h"

/*
 * Concrete Classes declaration
 */

@interface EOSinglePrimaryKeyDictionary : EOPrimaryKeyDictionary
{
    id	key;
    id	value;
}
- initWithObject:(id)anObject forKey:(id)aKey;
- key;
@end

@interface EOSinglePrimaryKeyDictionaryEnumerator : NSEnumerator
{
    id key;
}
- iniWithObject:(id)aKey;
@end

@interface EOMultiplePrimaryKeyDictionary : EOPrimaryKeyDictionary
{
    int		count;
    NSArray*	keys;
    id		values[0];
}
+ allocWithZone:(NSZone*)zone capacity:(int)capacity;
- initWithKeys:(NSArray*)keys fromDictionary:(NSDictionary*)dict;
@end

/*
 * Single key dictionary 
 */

@implementation EOSinglePrimaryKeyDictionary

- initWithObject:(id)anObject forKey:(id)aKey
{
    key = [aKey retain];
    value = [anObject retain];
    fastHash = [anObject hash];
    
    return self;
}

- (void)dealloc
{
    [key release];
    [value release];
    [super dealloc];
}

- (NSEnumerator *)keyEnumerator
{
    return [[[EOSinglePrimaryKeyDictionaryEnumerator alloc]
	iniWithObject:key] autorelease];
}

- (id)objectForKey:(id)aKey
{
    if ([key isEqual:aKey])
	return value;
    else
	return nil;
}

- (unsigned int)count
{
    return 1;
}

- (unsigned int)hash
{
    return 1;
}

- (id)key
{
    return key;
}

- (NSArray*)allKeys
{
    return [NSArray arrayWithObject:key];
}

- (NSArray*)allValues
{
    return [NSArray arrayWithObject:value];
}

- (BOOL)isEqualToDictionary:(NSDictionary*)other
{
    if (self == (EOSinglePrimaryKeyDictionary*)other)
	return YES;
    if (self->isa == ((EOSinglePrimaryKeyDictionary*)other)->isa) {
	if (fastHash == ((EOSinglePrimaryKeyDictionary*)other)->fastHash &&
	    [key isEqual:((EOSinglePrimaryKeyDictionary*)other)->key] &&
	    [value isEqual:((EOSinglePrimaryKeyDictionary*)other)->value])
		return YES;
	else
		return NO;
    }
    if ([other count] != 1)
	return NO;
    return [value isEqual:[other objectForKey:key]];
}

- (id)copyWithZone:(NSZone*)zone
{
    if ([self zone] == (zone ? zone : NSDefaultMallocZone()))
	return [self retain];
    else
	return [[[self class] allocWithZone:zone] 
	    initWithObject:value forKey:key];
}

- (id)copy
{
	return [self copyWithZone:NULL];
}

- (BOOL)fastIsEqual:other
{
    if (self == other)
	return YES;
    if (self->isa == ((EOSinglePrimaryKeyDictionary*)other)->isa) {
	if (fastHash == ((EOSinglePrimaryKeyDictionary*)other)->fastHash &&
	    key == ((EOSinglePrimaryKeyDictionary*)other)->key &&
	    [value isEqual:((EOSinglePrimaryKeyDictionary*)other)->value])
		return YES;
	else
	    return NO;
    }
    THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"fastIsEqual: compares only "
			   @"EOPrimaryKeyDictionary instances !"]);
    return NO;
}

@end

@implementation EOSinglePrimaryKeyDictionaryEnumerator

- iniWithObject:(id)aKey
{
    key = [aKey retain];
    return self;
}

- (void)dealloc
{
    [key release];
    [super dealloc];
}

- nextObject
{
    id tmp = key;
    key = nil;    
    return [tmp autorelease];
}

@end

/*
 * Multiple key dictionary is very time-memory efficient
 */

@implementation EOMultiplePrimaryKeyDictionary

+ allocWithZone:(NSZone*)zone capacity:(int)capacity
{
    return NSAllocateObject(self, sizeof(id)*capacity, zone);
}

- initWithKeys:(NSArray*)theKeys fromDictionary:(NSDictionary*)dict;
{
    int i;
    
    count = [theKeys count];
    keys = [theKeys retain];
    fastHash = 0;
    
    for (i=0; i<count; i++) {
	values[i] = [[dict objectForKey:[keys objectAtIndex:i]] retain];
	if (!values[i]) {
	    [self autorelease];
	    return nil;
	}
	fastHash += [values[i] hash];
    }
    
    return self;
}

- (void)dealloc
{
    int i;
    
    for (i=0; i<count; i++)
	[values[i] release];
    [keys release];
    [super dealloc];
}

- (NSEnumerator *)keyEnumerator
{
    return [keys objectEnumerator];
}

- (id)objectForKey:(id)aKey
{
    int max, min, mid;
    // Binary search for key's index
    for (min = 0, max = count-1; min <= max; ) {
	NSComparisonResult ord;
	
	mid = (min+max) >> 1;
	ord = [(NSString*)aKey compare:(NSString*)[keys objectAtIndex:mid]];
    	if (ord == NSOrderedSame)
	    return values[mid];
	if (ord == NSOrderedDescending) 
	    min = mid+1;
	else
	    max = mid-1;
    }
    return nil;
}

- (unsigned int)count
{
    return count;
}

- (unsigned int)hash
{
    return count;
}

- (NSArray*)allKeys
{
    return keys;
}

- (NSArray*)allValues
{
    return [[[NSArray alloc] initWithObjects:values count:count] autorelease];
}

- (BOOL)isEqualToDictionary:(NSDictionary*)other
{
    int i;
    if (self == (EOMultiplePrimaryKeyDictionary*)other)
	return YES;
    if (count != [other count])
	return NO;
    for (i = 0; i < count; i++)
	if (![values[i] isEqual:[other objectForKey:[keys objectAtIndex:i]]])
	    return NO;
    return YES;
}

- (id)copyWithZone:(NSZone*)zone
{
    if ([self zone] == (zone ? zone : NSDefaultMallocZone()))
	return [self retain];
    else
	return [[isa allocWithZone:zone capacity:count] 
	    initWithKeys:keys fromDictionary:self];
}

- (id)copy
{
	return [self copyWithZone:NULL];
}

- (unsigned)fastHash
{
    return fastHash;
}

- (BOOL)fastIsEqual:aDict
{
    int i;
    
    if (self->isa != ((EOMultiplePrimaryKeyDictionary*)aDict)->isa)
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"fastIsEqual: can compare only "
			   @"EOPrimaryKeyDictionary instances"]);
    if (self->count != ((EOMultiplePrimaryKeyDictionary*)aDict)->count ||
	self->fastHash != ((EOMultiplePrimaryKeyDictionary*)aDict)->fastHash ||
	self->keys != ((EOMultiplePrimaryKeyDictionary*)aDict)->keys)
	return NO;
    
    for (i = count-1; i >= 0; i--)
	if (![values[i] isEqual:
	    ((EOMultiplePrimaryKeyDictionary*)aDict)->values[i]])
	    return NO;
    return YES;
}

@end

/*
 * Cluster Abstract class
 */

@implementation EOPrimaryKeyDictionary

+ allocWithZone:(NSZone*)zone
{
  return NSAllocateObject(self, 0, zone);
}

+ dictionaryWithKeys:(NSArray*)keys fromDictionary:(NSDictionary*)dict
{
    if ([dict count] == 0)
	return nil;
    if ([keys count] == 1) {
	id key = [keys objectAtIndex:0];
	// Check if already an EOSinglePrimaryKeyDictionary from same entity
	// return it since the new one will be identical to it; we have
	// no problem regarding its correctness since it was built by this
	// method !
	if ([dict isKindOfClass:[EOSinglePrimaryKeyDictionary class]] &&
	    [(EOSinglePrimaryKeyDictionary*)dict key]==[keys objectAtIndex:0])
		return dict;
	// Alloc single key dictionary
	return [[[EOSinglePrimaryKeyDictionary alloc]
	    initWithObject:[dict objectForKey:key] 
	    forKey:key] autorelease];
    }
    else {
	// Check if already an EOMultiplePrimaryKeyDictionary from same entity
	// return it since the new one will be identical to it; we have
	// no problem regarding its correctness since it was built by this
	// method !
	if ([dict isKindOfClass:[EOMultiplePrimaryKeyDictionary class]] &&
	    [dict allKeys] == keys)
		return dict;
	// Alloc multi-key dictionary
	return [[[EOMultiplePrimaryKeyDictionary 
	    allocWithZone:NULL capacity:[keys count]]
	    initWithKeys:keys fromDictionary:dict] autorelease];
    }
}

+ dictionaryWithObject:(id)object forKey:(id)key
{
    return [[[EOSinglePrimaryKeyDictionary alloc]
	initWithObject:object forKey:key] autorelease];
}

- (unsigned)fastHash
{
    return fastHash;
}

- (BOOL)fastIsEqual:aDict
{
    // TODO - request concrete implementation
    return NO;
}

@end /* EOPrimaryKeyDictionary */
