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

/*
 * NSNotificationQueue queue
 */

typedef struct _NSNotificationQueueRegistration {
    struct _NSNotificationQueueRegistration* next;
    struct _NSNotificationQueueRegistration* prev;
    NSNotification* notification;
    id name;
    id object;
    NSArray* modes;
} NSNotificationQueueRegistration;

struct _NSNotificationQueueList;

typedef struct _NSNotificationQueueList {
    struct _NSNotificationQueueRegistration* head;
    struct _NSNotificationQueueRegistration* tail;
} NSNotificationQueueList;

/*
 * Queue functions
 *
 *  Queue             Elem              Elem              Elem
 *    head ---------> prev -----------> prev -----------> prev --> nil
 *            nil <-- next <----------- next <----------- next
 *    tail --------------------------------------------->
 */

static void
remove_from_queue(
    NSNotificationQueueList* queue,
    NSNotificationQueueRegistration* item,
    NSZone* zone)
{
    if (item->prev)
	item->prev->next = item->next;
    else {
	queue->tail = item->next;
	if (item->next)
	    item->next->prev = NULL;
    }
    
    if (item->next)
	item->next->prev = item->prev;
    else {
	queue->head = item->prev;
	if (item->prev)
	    item->prev->next = NULL;
    }
    [item->notification release];
    [item->modes release];
    NSZoneFree(zone, item);
}

static void
add_to_queue(
    NSNotificationQueueList* queue,
    NSNotification* notification,
    NSArray* modes,
    NSZone* zone)
{
    NSNotificationQueueRegistration* item = 
	    NSZoneCalloc(zone, 1, sizeof(NSNotificationQueueRegistration));
	    
    item->notification = [notification retain];
    item->name = [notification notificationName];
    item->object = [notification notificationObject];
    item->modes = [modes copyWithZone:[modes zone]];
    
    item->prev = NULL;
    item->next = queue->tail;
    queue->tail = item;
    if (item->next)
	item->next->prev = item;
    if (!queue->head)
	queue->head = item;
}

/*
 * Notification Queue calss variables
 */

typedef struct _InstanceList {
    struct _InstanceList* next;
    struct _InstanceList* prev;
    id	queue;
} InstanceList;

static InstanceList*  notificationQueues;
static NSNotificationQueue* defaultQueue;

/*
 * NSNotificationQueue class implementation
 */

@implementation NSNotificationQueue

+ (void)initialize
{
    // THREAD
    defaultQueue = [[self alloc] init];
}

+ (NSNotificationQueue*)defaultQueue
{
    return defaultQueue;
}

- (id)init
{
    return [self initWithNotificationCenter:
	    [NSNotificationCenter defaultCenter]];
}

- (id)initWithNotificationCenter:(NSNotificationCenter*)notificationCenter
{
    InstanceList *regItem;

    zone = [self zone];

    // init queue
    center = [notificationCenter retain];
    asapQueue = NSZoneCalloc(zone, 1, sizeof(NSNotificationQueueList));
    idleQueue = NSZoneCalloc(zone, 1, sizeof(NSNotificationQueueList));

    // insert in global queue list
    // THREAD
    regItem = Calloc(1, sizeof(InstanceList));
    regItem->next = notificationQueues;
    notificationQueues = regItem;

    return self;
}

- (void)dealloc
{
    InstanceList *regItem, *theItem;
    NSNotificationQueueRegistration* item;

    // remove from classs instances list
    // THREAD
    if (notificationQueues->queue == self)
	    notificationQueues = notificationQueues->next;
    else {
	for (regItem=notificationQueues; regItem->next; regItem=regItem->next)
	{
	    if (regItem->next->queue == self) {
		theItem = regItem->next;
		regItem->next = theItem->next;
		Free(theItem);
		break;
	    }
	}
    }
    
    // release self
    for (item = asapQueue->head; item; item=item->prev)
	remove_from_queue(asapQueue, item, zone);
    NSZoneFree(zone, asapQueue);

    for (item = idleQueue->head; item; item=item->prev)
	remove_from_queue(idleQueue, item, zone);
    NSZoneFree(zone, idleQueue);

    [center release];
    [super dealloc];
}

/* Inserting and Removing Notifications From a Queue */

- (void)dequeueNotificationsMatching:(NSNotification*)notification
  coalesceMask:(NSNotificationCoalescing)coalesceMask
{
    NSNotificationQueueRegistration* item;
    NSNotificationQueueRegistration* next;
    id name   = [notification notificationName];
    id object = [notification notificationObject];
    
    // find in ASAP notification in queue
    for (item = asapQueue->tail; item; item=next) {
	next = item->next;
	if ((coalesceMask & NSNotificationCoalescingOnName)
	    && [name isEqual:item->name]) 
	    {
		remove_from_queue(asapQueue, item, zone);
		continue;
	    }
	if ((coalesceMask & NSNotificationCoalescingOnSender)
	    && (object == item->object))
	    {
		remove_from_queue(asapQueue, item, zone);
		continue;
	    }
    }
    
    // find in idle notification in queue
    for (item = idleQueue->tail; item; item=next) {
	next = item->next;
	if ((coalesceMask & NSNotificationCoalescingOnName)
	    && [name isEqual:item->name]) 
	    {
		remove_from_queue(asapQueue, item, zone);
		continue;
	    }
	if ((coalesceMask & NSNotificationCoalescingOnSender)
	    && (object == item->object))
	    {
		remove_from_queue(asapQueue, item, zone);
		continue;
	    }
    }
}

- (void)postNotification:(NSNotification*)notification forModes:(NSArray*)modes
{
    BOOL ok = NO;
    NSString* mode = [[NSRunLoop currentRunLoop] currentMode];
    
    // check to see if run loop is in a valid mode
    if (!mode || !modes)
	ok = YES;
    else {
	int i;
	
	for (i = [modes count]-1; i >= 0; i--)
	    if ([mode isEqual:[modes objectAtIndex:i]]) {
		ok = YES;
		break;
	    }
    }
    
    // if mode is valid then post
    if (ok)
	[center postNotification:notification];
}

- (void)enqueueNotification:(NSNotification*)notification
  postingStyle:(NSPostingStyle)postingStyle	
{
    [self enqueueNotification:notification
	    postingStyle:postingStyle
	    coalesceMask:NSNotificationCoalescingOnName + 
			 NSNotificationCoalescingOnSender 
	    forModes:nil];
}

- (void)enqueueNotification:(NSNotification*)notification
  postingStyle:(NSPostingStyle)postingStyle
  coalesceMask:(NSNotificationCoalescing)coalesceMask
  forModes:(NSArray*)modes
{
    if (coalesceMask != NSNotificationNoCoalescing)
	[self dequeueNotificationsMatching:notification
		coalesceMask:coalesceMask];

    switch (postingStyle) {
	case NSPostNow:
		[self postNotification:notification forModes:modes];
		break;
	case NSPostASAP:
		add_to_queue(asapQueue, notification, modes, zone);
		break;
	case NSPostWhenIdle:
		add_to_queue(idleQueue, notification, modes, zone);
		break;
    }
}

/*
 * NotificationQueue internals
 */

+ (void)runLoopIdle
{
    InstanceList* item;

    for (item=notificationQueues; item; item=item->next)
	[item->queue notifyIdle];
}

+ (void)runLoopASAP
{
    InstanceList* item;
    
    for (item=notificationQueues; item; item=item->next)
	[item->queue notifyASAP];
}

- (void)notifyIdle
{
    // post next IDLE notification in queue
    if (idleQueue->head) {
	[self postNotification:idleQueue->head->notification 
		      forModes:idleQueue->head->modes];
	remove_from_queue(idleQueue, idleQueue->head, zone);
    }
}

- (void)notifyASAP
{
    // post all ASAP notifications in queue
    while (asapQueue->head) {
	[self postNotification:asapQueue->head->notification
		      forModes:idleQueue->head->modes];
	remove_from_queue(asapQueue, asapQueue->head, zone);
    }
}

@end

