/* 
   NSDictionary.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/NSObject.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSData.h>
#include <Foundation/NSRange.h>
#include <Foundation/NSString.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSException.h>
#include <Foundation/NSCoder.h>
#include <Foundation/exceptions/GeneralExceptions.h>

#include "NSConcreteDictionary.h"


@interface __KeyValueDescription : NSObject
{
@public
    NSString* keyDescription;
    NSString* valueDescription;
}
@end /* __KeyValueDescription */

@implementation __KeyValueDescription
- (NSComparisonResult)compare:(__KeyValueDescription*)other
{
    return [keyDescription compare:other->keyDescription];
}
@end /* __KeyValueDescription */


/*
 * NSDictionary class
 */

@implementation NSDictionary

/* Creating and Initializing an NSDictionary */

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

+ (id)dictionary
{
    return [[[self alloc] initWithDictionary:nil] autorelease];
}

+ (id)dictionaryWithContentsOfFile:(NSString*)path
{
    return [[[self alloc] initWithContentsOfFile:path] autorelease];
}

+ (id)dictionaryWithObjects:(NSArray*)objects forKeys:(NSArray*)keys
{
    return [[[self alloc] initWithObjects:objects forKeys:keys] autorelease];
}

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

+ (id)dictionaryWithObjectsAndKeys:(id)firstObject, ...
{
    id dict = [self alloc];
    va_list va;
    va_start(va, firstObject);
    [dict initWithObjectsAndKeys:firstObject arguments:va];
    va_end(va);
    return [dict autorelease];
}

+ (id)dictionaryWithDictionary:(NSDictionary*)aDict
{
    return [[[self alloc] initWithDictionary:aDict] autorelease];
}

+ (id)dictionaryWithObject:object forKey:key
{
    return [[[self alloc] initWithObjects:&object forKeys:&key count:1] 
	autorelease];
}

- (id)initWithContentsOfFile:(NSString*)fileName
{
    id plist = nil;
    TRY {
	plist = [[NSString stringWithContentsOfFile:fileName] propertyList];
	if (![plist isKindOfClass:[NSDictionary class]])
	    plist = nil;
    } END_TRY
    OTHERWISE {
	plist = nil;
    }
    END_CATCH
    
    if (plist == nil) {
	[self autorelease];
	return nil;
    }
    else
	return [self initWithDictionary:plist];
}

- (id)initWithDictionary:(NSDictionary*)dictionary copyItems:(BOOL)flag
{
    NSEnumerator* keye = [dictionary keyEnumerator];
    unsigned count = [dictionary count];
    id *keys = Malloc(sizeof(id)*count);
    id *values = Malloc(sizeof(id)*count);
    id key;
    
    count = 0;
    while ((key = [keye nextObject])) {
	keys[count] = key;
	values[count] = [dictionary objectForKey:key];
	if (flag) {
	    keys[count] = [[keys[count] copyWithZone:NULL] autorelease];
	    values[count] = [[values[count] copyWithZone:NULL] autorelease];
	}
	count++;
    }
    
    [self initWithObjects:values forKeys:keys count:count];
    
    Free(keys);
    Free(values);
    return self;
}

- (id)initWithDictionary:(NSDictionary*)dictionary
{
    return [self initWithDictionary:dictionary copyItems:NO];
}

- (id)initWithObjectsAndKeys:(id)firstObject,...
{
    va_list va;
    va_start(va, firstObject);
    [self initWithObjectsAndKeys:firstObject arguments:va];
    va_end(va);
    return self;
}

- (id)initWithObjects:(NSArray*)objects forKeys:(NSArray*)keys
{
    unsigned int i, count = [objects count];
    id *mkeys;
    id *mobjs;

    if (count != [keys count])
	THROW([[InvalidArgumentException alloc] initWithReason:
		@"NSDictionary initWithObjects:forKeys must \
		    have both arguments of the same size"]);

    mkeys = Malloc(sizeof(id)*count);
    mobjs = Malloc(sizeof(id)*count);

    for (i = 0; i < count; i++) {
	mkeys[i] = [keys objectAtIndex:i];
	mobjs[i] = [objects objectAtIndex:i];
    }

    [self initWithObjects:mobjs forKeys:mkeys count:count];
    
    Free(mkeys);
    Free(mobjs);
    return self;
}

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

/* Accessing Keys and Values */

- (NSArray*)allKeys
{
    id array;
    id *objs = Malloc(sizeof(id)*[self count]);
    id keys = [self keyEnumerator];
    id key;
    unsigned int index = 0;

    while ((key = [keys nextObject]))
	objs[index++] = key;
    
    array = [[[NSArray alloc] initWithObjects:objs count:index] autorelease];

    Free(objs);
    return array;
}

- (NSArray*)allKeysForObject:(id)object
{
    id array;
    id *objs = Malloc(sizeof(id)*[self count]);
    id keys = [self keyEnumerator];
    id key;
    unsigned int index = 0;

    while ((key = [keys nextObject]))
	if ([object isEqual:[self objectForKey:key]])
	    objs[index++] = key;

    array = [[[NSArray alloc] initWithObjects:objs count:index] autorelease];

    Free(objs);
    return array;
}

- (NSArray*)allValues
{
    id array;
    id *objs = Malloc(sizeof(id)*[self count]);
    id keys = [self objectEnumerator];
    id obj;
    unsigned int index = 0;

    while ((obj = [keys nextObject]))
	objs[index++] = obj;
    
    array = [[[NSArray alloc] initWithObjects:objs count:index] autorelease];

    Free(objs);
    return array;
}

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

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

- (id)objectForKey:(id)aKey;
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSArray*)objectsForKeys:(NSArray*)keys notFoundMarker:notFoundObj
{
    int count = [keys count];
    id* objs = Malloc(sizeof(id)*count);
    id ret;
    
    for (count--; count >= 0; count--) {
	id ret = [self objectForKey:[keys objectAtIndex:count]];
	objs[count] = ret ? ret : notFoundObj;
    }
    
    ret = [[[NSArray alloc] initWithObjects:objs count:count] autorelease];
    Free(objs);
    return ret;
}

/* Counting Entries */

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

/*
 * Comparing Dictionaries
 */

- (BOOL)isEqualToDictionary:(NSDictionary*)other
{
    id keys, key;
    if( other == self )
	return YES;
    if ([self count] != [other count] || other == nil)
	return NO;
    keys = [self keyEnumerator];
    while ((key = [keys nextObject]))
	if ([[self objectForKey:key] isEqual:[other objectForKey:key]]==NO)
	    return NO;
    return YES;
}

/* Storing Dictionaries */

- (NSString*)descriptionWithLocale:(NSDictionary*)locale
   indent:(unsigned int)indent;
{
    id key, value;
    NSEnumerator* enumerator;
    unsigned indent1 = indent + 4;
    NSMutableArray* keyDescriptions;
    id pool = [NSAutoreleasePool new];
    SEL sel;
    IMP imp;
    __KeyValueDescription* descHolder;
    NSMutableString* description = [[NSMutableString stringWithCString:"{\n"]
					retain];
    NSMutableString* indentation
	    = [NSString stringWithFormat:
			[NSString stringWithFormat:@"%%%dc", indent1], ' '];

    if(![self count]) {
	[pool release];
	return @"{}";
    }

    enumerator = [self keyEnumerator];
    keyDescriptions = [NSMutableArray arrayWithCapacity:[self count]];

    while((key = [enumerator nextObject])) {
	descHolder = [__KeyValueDescription alloc];
	value = [self objectForKey:key];

	if ([key respondsToSelector:@selector(descriptionWithLocale:indent:)])
	    descHolder->keyDescription = [key descriptionWithLocale:locale 
		indent:indent1];
	else if ([key
		respondsToSelector:@selector(descriptionWithLocale:)])
	    descHolder->keyDescription = [key descriptionWithLocale:locale];
	else
	    descHolder->keyDescription = [key stringRepresentation];

	if ([value
		respondsToSelector:@selector(descriptionWithLocale:indent:)])
	    descHolder->valueDescription = [value descriptionWithLocale:locale 
		indent:indent1];
	else if ([value
		respondsToSelector:@selector(descriptionWithLocale:)])
	    descHolder->valueDescription = [value 
		descriptionWithLocale:locale];
	else
	    descHolder->valueDescription = [value stringRepresentation];

	Assert([descHolder->keyDescription isKindOfClass:[NSString class]]);
	Assert([descHolder->valueDescription isKindOfClass:[NSString class]]);
	[keyDescriptions addObject:descHolder];
	[descHolder release];
    }
    
    [keyDescriptions sortUsingSelector:@selector(compare:)];

    sel = @selector(appendString:);
    imp = [description methodForSelector:sel];
    Assert(imp);

    enumerator = [keyDescriptions objectEnumerator];
    while((descHolder = [enumerator nextObject])) {
	(*imp)(description, sel, indentation);
	(*imp)(description, sel, descHolder->keyDescription);
	(*imp)(description, sel, @" = ");
	(*imp)(description, sel, descHolder->valueDescription);
	(*imp)(description, sel, @";\n");
    }
    (*imp)(description, sel, indent
	    ? [NSMutableString stringWithFormat:
			[NSString stringWithFormat:@"%%%dc}", indent], ' ']
	    : [NSMutableString stringWithCString:"}"]);

    [pool release];

    return [description autorelease];
}

- (NSString*)descriptionInStringsFileFormat
{
    id key, value;
    NSMutableArray* keys;
    NSEnumerator* enumerator;
    NSMutableString* description = [[NSMutableString new] autorelease];
    id pool = [NSAutoreleasePool new];
    
    keys = [[[self allKeys] mutableCopy] autorelease];
    [keys sortUsingSelector:@selector(compare:)];
    
    enumerator = [keys objectEnumerator];
    while((key = [enumerator nextObject])) {
	value = [self objectForKey:key];

	Assert([key isKindOfClass:[NSString class]]);
	Assert([value isKindOfClass:[NSString class]]);

	[description appendString:[key stringRepresentation]];
	[description appendString:@" = "];
	[description appendString:[value stringRepresentation]];
	[description appendString:@";\n"];
    }
    [pool release];

    return description;
}

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

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

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

- (BOOL)writeToFile:(NSString*)path atomically:(BOOL)useAuxiliaryFile
{
    id description = [self description];
    NSData* data = [NSData dataWithBytes:[description cString]
			    length:[description cStringLength]];

    return writeToFile(path, data, useAuxiliaryFile);
}

/* From adopted/inherited protocols */

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

- (BOOL)isEqual:(id)anObject
{
    if ([anObject isKindOfClass:[NSDictionary class]] == NO)
	return NO;
    return [self isEqualToDictionary:anObject];
}

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

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

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

- (void)encodeWithCoder:(NSCoder*)aCoder
{
    int count = [self count];
    NSEnumerator* enumerator = [self keyEnumerator];
    id key, value;

    [aCoder encodeValueOfObjCType:@encode(int) at:&count];
    while((key = [enumerator nextObject])) {
	value = [self objectForKey:key];
	[aCoder encodeObject:key];
	[aCoder encodeObject:value];
    }
}

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

    [aDecoder decodeValueOfObjCType:@encode(int) at:&count];
    keys = Malloc(sizeof(id) * count);
    values = Malloc(sizeof(id) * count);

    for(i = 0; i < count; i++) {
	keys[i] = [aDecoder decodeObject];
	values[i] = [aDecoder decodeObject];
    }

    [self initWithObjects:keys forKeys:keys count:count];

    Free(keys);
    Free(values);

    return self;
}

@end /* NSDictionary */

/*
 * Extensions to NSDictionary
 */

@implementation NSDictionary(NSDictionaryExtensions)

- (id)initWithObjectsAndKeys:(id)firstObject arguments:(va_list)argList
{
    id object;
    id *ka, *oa;
    va_list va = argList;
    int count = 0;
    
    for (object = firstObject; object; object = va_arg(va,id)) {
	if (!va_arg(va,id))
	    THROW([[InvalidArgumentException alloc] 
		    initWithReason:@"Nil key to be added in dictionary"]);
	count++;
    }
    
    ka = Malloc(sizeof(id)*count);
    oa = Malloc(sizeof(id)*count);
    
    for (count=0, object=firstObject; object; object=va_arg(argList,id)) {
	ka[count] = va_arg(argList,id);
	oa[count] = object;
	count++;
    }

    [self initWithObjects:oa forKeys:ka count:count];

    Free(ka);
    Free(oa);
    return self;
}

@end;

/*
 * NSMutableDictionary class
 */

@implementation NSMutableDictionary

/* Creating and Initializing an NSDictionary */

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

+ (id)dictionaryWithCapacity:(unsigned int)aNumItems
{
    return [[[self alloc] initWithCapacity:aNumItems] autorelease];
}

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

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

/* Adding and Removing Entries */

- (void)addEntriesFromDictionary:(NSDictionary*)otherDictionary
{
    id nodes = [otherDictionary keyEnumerator];
    id key;
    
    while ((key = [nodes nextObject]))
	[self setObject:[otherDictionary objectForKey:key] forKey:key];
}

- (void)removeAllObjects
{
    id keys = [self keyEnumerator];
    id key;

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

- (void)removeObjectForKey:(id)theKey
{
    [self subclassResponsibility:_cmd];
}

- (void)removeObjectsForKeys:(NSArray*)keyArray
{
    unsigned int index, count = [keyArray count];
    for (index = 0; index<count; index++)
	[self removeObjectForKey:[keyArray objectAtIndex:index]];
}

- (void)setObject:(id)anObject forKey:(id)aKey
{
    [self subclassResponsibility:_cmd];
}

- (void)setDictionary:(NSDictionary*)otherDictionary
{
    [self removeAllObjects];
    [self addEntriesFromDictionary:otherDictionary];
}

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

@end /* NSMutableDictionary */

/*
 * NSDictionary Enumerator classes
 */

@implementation _NSDictionaryObjectEnumerator
{
    NSDictionary*	dict;
    NSEnumerator*	keys;
}

- initWithDictionary:(NSDictionary*)_dict
{
    dict = [_dict retain];
    keys = [[_dict keyEnumerator] retain];
    return self;
}

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

- nextObject
{
    return [dict objectForKey:[keys nextObject]];
}

@end /* _NSDictionaryObjectEnumerator */

