/* 
   NSNotificationCenter.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 <Foundation/NSNotification.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSString.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSLock.h>
#include <Foundation/common.h>

#include <extensions/objc-runtime.h>
#include <extensions/NSException.h>
#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>

#define DEFAULT_CAPACITY 1023

/*
 * Objects/selectors pair used in sets
 */

@interface NSNotificationListItem : NSObject
{
    @public
    id  observer;	// observer that will receive selector
    SEL selector;	// is a postNotification:
}
- initWithObject:(id)anObserver selector:(SEL)aSelector;
- (BOOL)isEqual:other;
- (unsigned int)hash;
- (void)postNotification:(NSNotification*)notification;
@end

@implementation NSNotificationListItem

- initWithObject:(id)anObserver selector:(SEL)aSelector
{
    observer = anObserver;
    selector = aSelector;
    return self;
}

- (BOOL)isEqual:other
{
    if ([other isKindOfClass:[NSNotificationListItem class]]) {
	NSNotificationListItem* obj = other;
	return (observer == obj->observer) && SEL_EQ(selector, obj->selector);
    }
    
    return NO;
}

- (unsigned int)hash
{
    return ((int)observer >> 4) + 
	   __NSHashCString(NULL, sel_get_name(selector));
}

- (void)postNotification:(NSNotification*)notification
{
    [observer perform:selector withObject:notification];
}

@end

/*
 * Register for objects to observer mapping
 */

@interface NSNotificationObserverRegister : NSObject
{
    NSHashTable* observerItems;
}
- init;
- (unsigned int)count;
- (void)addObjectsInList:(NSMutableArray*)list;
- (void)addObserver:observer selector:(SEL)selector;
- (void)removeObserver:observer;
@end

@implementation NSNotificationObserverRegister

- init
{
    observerItems = NSCreateHashTable(NSObjectHashCallBacks, DEFAULT_CAPACITY);
    return self;
}

- (void)dealloc
{
    NSFreeHashTable(observerItems);
    [super dealloc];
}

- (unsigned int)count
{
    return NSCountHashTable(observerItems);
}

- (void)addObjectsInList:(NSMutableArray*)list
{
    id reg;
    NSHashEnumerator itemsEnum = NSEnumerateHashTable(observerItems);

    while((reg = (id)NSNextHashEnumeratorItem(&itemsEnum)))
	[list addObject:reg];
}

- (void)addObserver:observer selector:(SEL)selector
{
    NSNotificationListItem* reg = [[[NSNotificationListItem alloc]
	initWithObject:observer selector:selector] autorelease];
    
    NSHashInsertIfAbsent(observerItems, reg);
}

- (void)removeObserver:observer
{
    NSMutableArray* list;
    NSNotificationListItem* reg;
    NSHashEnumerator itemsEnum;
    int i;
    
    list = [[[NSMutableArray alloc]
	initWithCapacity:[self count]] autorelease];
    itemsEnum = NSEnumerateHashTable(observerItems);
    
    while((reg = (id)NSNextHashEnumeratorItem(&itemsEnum)))
	if (reg->observer == observer)
	    [list addObject:reg];
    
    for (i = [list count]-1; i >= 0; i--)
	NSHashRemove(observerItems, [list objectAtIndex:i]);
}

@end

/*
 * Register for objects to observer mapping
 */

@interface NSNotificationObjectRegister : NSObject
{
    NSMapTable* objectObservers;
    NSNotificationObserverRegister* nilObjectObservers;
}

- init;
- listToNotifyForObject:object;
- (void)addObserver:observer selector:(SEL)selector object:object;
- (void)removeObserver:observer object:object;
- (void)removeObserver:observer;

@end

@implementation NSNotificationObjectRegister

- init
{
    objectObservers = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, 
	NSObjectMapValueCallBacks, DEFAULT_CAPACITY);
    nilObjectObservers = [NSNotificationObserverRegister new];
    return self;
}

- (void)dealloc
{
    NSFreeMapTable(objectObservers);
    [nilObjectObservers release];
    [super dealloc];
}

- listToNotifyForObject:object
{
    id reg = nil;
    int count;
    id list;
    
    if (object)
	reg = (id)NSMapGet(objectObservers, object);
    count = [reg count] + [nilObjectObservers count];
    list = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
    
    [reg addObjectsInList:list];
    [nilObjectObservers addObjectsInList:list];
    
    return list;
}

- (void)addObserver:observer selector:(SEL)selector object:object
{
    NSNotificationObserverRegister* reg;
    
    if (object) {
	reg = (id)NSMapGet(objectObservers, object);
	if (!reg) {
	    reg = [[[NSNotificationObserverRegister alloc] init] autorelease];
	    NSMapInsert(objectObservers, object, reg);
	}
    }
    else
	reg = nilObjectObservers;
    
    [reg addObserver:observer selector:selector];
}

- (void)removeObserver:observer object:object
{
    NSNotificationObserverRegister* reg;
    
    if (object)
	reg = NSMapGet(objectObservers, object);
    else
	reg = nilObjectObservers;
    
    [reg removeObserver:observer];
}

- (void)removeObserver:observer
{
    id obj, reg;
    NSMapEnumerator regEnum = NSEnumerateMapTable(objectObservers);

    while (NSNextMapEnumeratorPair(&regEnum, (void*)&obj, (void*)&reg))
	[reg removeObserver:observer];

    [nilObjectObservers removeObserver:observer];
}

@end

/*
 * NSNotificationCenter	
 */

static NSNotificationCenter *defaultCenter = nil;

@implementation NSNotificationCenter 

/* Class methods */

+ (NSNotificationCenter*)defaultCenter
{
    if (!defaultCenter) {
	// THREAD
	defaultCenter = [[self alloc] init];
    }
    
    return defaultCenter;
}

/* Init/dealloc */

- (id)init
{
    nameToObjects = [NSMutableDictionary new];
    nullNameToObjects = [NSNotificationObjectRegister new];
    return self;
}

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

/* Register && post notifications */
    
- (void)postNotification:(NSNotification*)notification
{
    NSArray* fromName;
    NSArray* fromNull;

    NSNotificationObjectRegister* reg;
    
    id name = [notification notificationName];
    id object = [notification notificationObject];

    if (name == nil)
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"`nil' notification name in postNotification:"]);
    
    [lock lock];
    
    // get objects to notify with registered notification name
    reg = [nameToObjects objectForKey:name];
    fromName = [reg listToNotifyForObject:object];
    
    // get objects to notify with no notification name
    fromNull = [nullNameToObjects listToNotifyForObject:object];

    [lock unlock];

    // send notifications
    [fromName makeObjectsPerform:@selector(postNotification:)
	withObject:notification];
    [fromNull makeObjectsPerform:@selector(postNotification:)
	withObject:notification];
}

- (void)addObserver:observer selector:(SEL)selector 
  name:(NSString*)notificationName object:object
{
    NSNotificationObjectRegister* reg;
    
    [lock lock];

    if (notificationName == nil)
	reg = nullNameToObjects;
    else {
	notificationName = [[notificationName
	    copyWithZone:[notificationName zone]] autorelease];
	reg = [nameToObjects objectForKey:notificationName];
	if (!reg) {
	    reg = [[[NSNotificationObjectRegister alloc] init] autorelease];
	    [nameToObjects setObject:reg forKey:notificationName];
	}
    }
    [reg addObserver:observer selector:selector object:object];

    [lock unlock];
}

- (void)removeObserver:observer 
  name:(NSString*)notificationName object:object
{
    NSNotificationObjectRegister* reg;
    
    [lock lock];

    if (notificationName == nil)
	reg = nullNameToObjects;
    else
	reg = [nameToObjects objectForKey:notificationName];
    [reg removeObserver:observer object:object];

    [lock unlock];
}

- (void)removeObserver:observer
{
    id name, enumerator;
    NSNotificationObjectRegister* reg;

    [lock lock];

    enumerator = [nameToObjects keyEnumerator];
    
    while ((name = [enumerator nextObject])) {
	reg = [nameToObjects objectForKey:name];
	[reg removeObserver:observer];
    }

    [nullNameToObjects removeObserver:observer];

    [lock unlock];
}

- (void)postNotificationName:(NSString*)notificationName object:object
{
    [self postNotification:[NSNotification 
	    notificationWithName:notificationName 
	    object:object
	    userInfo:nil]];
}

- (void)postNotificationName:(NSString*)notificationName object:object
  userInfo:(NSDictionary*)userInfo;
{
    [self postNotification:[NSNotification 
	    notificationWithName:notificationName 
	    object:object
	    userInfo:userInfo]];
}

@end

