/* 
   Copyright (C) 1996 
	Ovidiu Predescu <ovidiu@bx.logicnet.ro>
	Mircea Oancea <mircea@jupiter.elcom.pub.ro>

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>

   This file is part of the FoundationExtensions 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 <config.h>
#include <Foundation/NSString.h>

#include <extensions/objc-runtime.h>
#include <extensions/GCDictionary.h>
#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>
#include <extensions/GCObject.h>
#include <extensions/GarbageCollector.h>
#include "common.h"

/*
 * TODO: copyWithZone:
 */

@interface _GCDictionaryKeyEnumerator : NSObject
{
    GCDictionary*	dict;
    NSMapEnumerator	enumerator;
}

- initWithDictionary:_dict;
- nextObject;

@end /* _GCDictionaryKeyEnumerator */

@implementation _GCDictionaryKeyEnumerator

- initWithDictionary:_dict
{
    dict = [_dict retain];
    enumerator = [dict __keyEnumerator];
    return self;
}

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

- nextObject
{
    GCObjectCollectable *keyStruct, *valueStruct;
    return NSNextMapEnumeratorPair(&enumerator, 
	(void**)&keyStruct, (void**)&valueStruct) ? keyStruct->object : nil;
}

@end /* _GCDictionaryKeyEnumerator */


@implementation GCDictionary

static unsigned __GCHashObject(NSMapTable *table,
    const GCObjectCollectable* objectStruct)
{
    return [objectStruct->object hash];
}

static BOOL __GCCompareObjects(
    NSMapTable *table, 
    const GCObjectCollectable* objectStruct1,
    const GCObjectCollectable* objectStruct2)
{
    return [objectStruct1->object isEqual:objectStruct2->object];
}

static void __GCRetainObjects(NSMapTable *table,
    const GCObjectCollectable* objectStruct)
{
    [objectStruct->object retain];
}

static void __GCReleaseObjects(NSMapTable *table,
    GCObjectCollectable* objectStruct)
{
    if([GarbageCollector isGarbageCollecting]) {
	if(!objectStruct->isGarbageCollectable)
	    [objectStruct->object release];
    }
    else
	[objectStruct->object release];
    Free(objectStruct);
}

static NSString* __GCDescribeObjects(NSMapTable *table,
    const GCObjectCollectable* objectStruct)
{
    return [objectStruct->object description];
}

static const NSMapTableKeyCallBacks GCOwnedStructMapKeyCallBacks = {
    (unsigned(*)(NSMapTable *, const void *))__GCHashObject,
    (BOOL(*)(NSMapTable *, const void *, const void *))__GCCompareObjects,
    (void (*)(NSMapTable *, const void *anObject))__GCRetainObjects,
    (void (*)(NSMapTable *, void *anObject))__GCReleaseObjects,
    (NSString *(*)(NSMapTable *, const void *))__GCDescribeObjects,
    (const void *)NULL
}; 

static const NSMapTableValueCallBacks GCOwnedStructValueCallBacks = {
    (void (*)(NSMapTable *, const void *))__GCRetainObjects,
    (void (*)(NSMapTable *, void *))__GCReleaseObjects,
    (NSString *(*)(NSMapTable *, const void *))__GCDescribeObjects
}; 

+ (void)initialize
{
    static BOOL initialized = NO;

    if(!initialized) {
	initialized = YES;
	class_add_behavior(self, [GCObject class]);
    }
}

- (void)_createTableWithSize:(unsigned)size
{
    table = NSCreateMapTableWithZone(GCOwnedStructMapKeyCallBacks,
		    GCOwnedStructValueCallBacks, size, [self zone]);
}

- (id)initWithDictionary:(NSDictionary*)dictionary
{
    id keys = [dictionary keyEnumerator];
    id key;

    [self _createTableWithSize:([dictionary count] * 4) / 3];

    while ((key = [keys nextObject])) {
	GCObjectCollectable* keyStruct = Malloc(sizeof(GCObjectCollectable));
	GCObjectCollectable* valueStruct = Malloc(sizeof(GCObjectCollectable));
	id value = [dictionary objectForKey:key];
	keyStruct->object = key;
	keyStruct->isGarbageCollectable = [key isGarbageCollectable];
	valueStruct->object = value;
	valueStruct->isGarbageCollectable = [value isGarbageCollectable];
	NSMapInsert(table, keyStruct, valueStruct);
    }
    
    return self;
}

- (id)initWithObjects:(id*)objects
    forKeys:(id*)keys 
    count:(unsigned int)count
{
    [self _createTableWithSize:(count * 4) / 3];

    if (!count)
	return self;
    while(count--) {
	GCObjectCollectable* keyStruct;
	GCObjectCollectable* valueStruct;

	if (!keys[count] || !objects[count])
	    THROW([[InvalidArgumentException alloc] 
		    initWithReason:@"Nil object to be added in dictionary"]);
	keyStruct = Malloc(sizeof(GCObjectCollectable));
	valueStruct = Malloc(sizeof(GCObjectCollectable));
	keyStruct->object = keys[count];
	keyStruct->isGarbageCollectable = [keys[count] isGarbageCollectable];
	valueStruct->object = objects[count];
	valueStruct->isGarbageCollectable
	    = [objects[count] isGarbageCollectable];
	NSMapInsert(table, keyStruct, valueStruct);
    }
    return self;
}

- (void)dealloc
{
    NSFreeMapTable(table);
    [super dealloc];
}

- (NSEnumerator *)keyEnumerator
{
    return [[[_GCDictionaryKeyEnumerator alloc]
		initWithDictionary:self]
		autorelease];
}

- (id)objectForKey:(id)key
{
    GCObjectCollectable keyStruct = { key, 0 };
    GCObjectCollectable* valueStruct = NSMapGet(table,
						(void**)&keyStruct);
    return valueStruct ? valueStruct->object : nil;
}

- (unsigned int)count
{
    return NSCountMapTable(table);
}

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

- (id)copyWithZone:(NSZone*)zone
{
    if (zone == [self zone])
	return [self retain];
    else return	[[isa allocWithZone:zone] initWithDictionary:self];
}

- (id)mutableCopy
{
    return [self mutableCopyWithZone:NSDefaultMallocZone()];
}

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

/* Private */
- (NSMapEnumerator)__keyEnumerator
{
    return NSEnumerateMapTable(table);
}

- (void)gcDecrementRefCountOfContainedObjects
{
    NSMapEnumerator enumerator = NSEnumerateMapTable(table);
    GCObjectCollectable *keyStruct, *valueStruct;

    while(NSNextMapEnumeratorPair(&enumerator,
	    (void**)&keyStruct, (void**)&valueStruct)) {
	if(keyStruct->isGarbageCollectable)
	    [keyStruct->object gcDecrementRefCount];
	if(valueStruct->isGarbageCollectable)
	    [valueStruct->object gcDecrementRefCount];
    }
}

- (BOOL)gcIncrementRefCountOfContainedObjects
{
    NSMapEnumerator enumerator;
    GCObjectCollectable *keyStruct, *valueStruct;

    if([(id)self gcAlreadyVisited])
	return NO;
    [(id)self gcSetVisited:YES];

    enumerator = NSEnumerateMapTable(table);
    while(NSNextMapEnumeratorPair(&enumerator,
	    (void**)&keyStruct, (void**)&valueStruct)) {
	if(keyStruct->isGarbageCollectable) {
	    [keyStruct->object gcIncrementRefCount];
	    [keyStruct->object gcIncrementRefCountOfContainedObjects];
	}
	if(valueStruct->isGarbageCollectable) {
	    [valueStruct->object gcIncrementRefCount];
	    [valueStruct->object gcIncrementRefCountOfContainedObjects];
	}
    }
    return YES;
}

@end /* GCDictionary */


@implementation GCMutableDictionary

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized) {
	initialized = YES;
	class_add_behavior(self, [GCDictionary class]);
    }
}

- (id)init
{
    return [self initWithCapacity:0];
}

- (id)initWithCapacity:(unsigned int)aNumItems
{
    [(id)self _createTableWithSize:(aNumItems * 4) / 3];
    return self;
}

- (id)copyWithZone:(NSZone*)zone
{
    return [[GCDictionary allocWithZone:zone] initWithDictionary:self];
}

- (void)setObject:(id)anObject forKey:(id)aKey
{
    GCObjectCollectable *keyStruct = Malloc(sizeof(GCObjectCollectable));
    GCObjectCollectable *valueStruct = Malloc(sizeof(GCObjectCollectable));

    keyStruct->object = aKey;
    keyStruct->isGarbageCollectable = [aKey isGarbageCollectable];
    valueStruct->object = anObject;
    valueStruct->isGarbageCollectable = [anObject isGarbageCollectable];
    NSMapInsert(table, keyStruct, valueStruct);
}

- (void)removeObjectForKey:(id)key
{
    GCObjectCollectable keyStruct = { key, 0 };
    NSMapRemove(table, (void**)&keyStruct);
}

- (void)removeAllObjects
{
    NSResetMapTable(table);
}

@end /* GCMutableDictionary */
