
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>

#include "WUtil.h"

typedef struct W_Notification {
	const char *name;
	void *object;
	void *clientData;
	int refCount;
} Notification;

extern void W_FlushASAPNotificationQueue();

const char *WMGetNotificationName(WMNotification * notification)
{
	return notification->name;
}

void *WMGetNotificationObject(WMNotification * notification)
{
	return notification->object;
}

void *WMGetNotificationClientData(WMNotification * notification)
{
	return notification->clientData;
}

WMNotification *WMCreateNotification(const char *name, void *object, void *clientData)
{
	Notification *nPtr;

	nPtr = wmalloc(sizeof(Notification));

	nPtr->name = name;
	nPtr->object = object;
	nPtr->clientData = clientData;

	nPtr->refCount = 1;

	return nPtr;
}

void WMReleaseNotification(WMNotification * notification)
{
	notification->refCount--;

	if (notification->refCount < 1) {
		wfree(notification);
	}
}

WMNotification *WMRetainNotification(WMNotification * notification)
{
	notification->refCount++;

	return notification;
}

/***************** Notification Center *****************/

typedef struct NotificationObserver {
	WMNotificationObserverAction *observerAction;
	void *observer;

	const char *name;
	void *object;

	struct NotificationObserver *prev;	/* for tables */
	struct NotificationObserver *next;
	struct NotificationObserver *nextAction;	/* for observerTable */
} NotificationObserver;

typedef struct W_NotificationCenter {
	WMHashTable *nameTable;	/* names -> observer lists */
	WMHashTable *objectTable;	/* object -> observer lists */
	NotificationObserver *nilList;	/* obervers that catch everything */

	WMHashTable *observerTable;	/* observer -> NotificationObserver */
} NotificationCenter;

/* default (and only) center */
static NotificationCenter *notificationCenter = NULL;

void W_InitNotificationCenter(void)
{
	notificationCenter = wmalloc(sizeof(NotificationCenter));

	notificationCenter->nameTable = WMCreateHashTable(WMStringPointerHashCallbacks);
	notificationCenter->objectTable = WMCreateHashTable(WMIntHashCallbacks);
	notificationCenter->nilList = NULL;

	notificationCenter->observerTable = WMCreateHashTable(WMIntHashCallbacks);
}

void
WMAddNotificationObserver(WMNotificationObserverAction * observerAction,
			  void *observer, const char *name, void *object)
{
	NotificationObserver *oRec, *rec;

	oRec = wmalloc(sizeof(NotificationObserver));
	oRec->observerAction = observerAction;
	oRec->observer = observer;
	oRec->name = name;
	oRec->object = object;
	oRec->next = NULL;
	oRec->prev = NULL;

	/* put this action in the list of actions for this observer */
	rec = (NotificationObserver *) WMHashInsert(notificationCenter->observerTable, observer, oRec);

	if (rec) {
		/* if this is not the first action for the observer */
		oRec->nextAction = rec;
	} else {
		oRec->nextAction = NULL;
	}

	if (!name && !object) {
		/* catch-all */
		oRec->next = notificationCenter->nilList;
		if (notificationCenter->nilList) {
			notificationCenter->nilList->prev = oRec;
		}
		notificationCenter->nilList = oRec;
	} else if (!name) {
		/* any message coming from object */
		rec = (NotificationObserver *) WMHashInsert(notificationCenter->objectTable, object, oRec);
		oRec->next = rec;
		if (rec) {
			rec->prev = oRec;
		}
	} else {
		/* name && (object || !object) */
		rec = (NotificationObserver *) WMHashInsert(notificationCenter->nameTable, name, oRec);
		oRec->next = rec;
		if (rec) {
			rec->prev = oRec;
		}
	}
}

void WMPostNotification(WMNotification * notification)
{
	NotificationObserver *orec, *tmp;

	WMRetainNotification(notification);

	/* tell the observers that want to know about a particular message */
	orec = (NotificationObserver *) WMHashGet(notificationCenter->nameTable, notification->name);

	while (orec) {
		tmp = orec->next;

		if (!orec->object || !notification->object || orec->object == notification->object) {
			/* tell the observer */
			if (orec->observerAction) {
				(*orec->observerAction) (orec->observer, notification);
			}
		}

		orec = tmp;
	}

	/* tell the observers that want to know about an object */
	orec = (NotificationObserver *) WMHashGet(notificationCenter->objectTable, notification->object);

	while (orec) {
		tmp = orec->next;

		/* tell the observer */
		if (orec->observerAction) {
			(*orec->observerAction) (orec->observer, notification);
		}
		orec = tmp;
	}

	/* tell the catch all observers */
	orec = notificationCenter->nilList;
	while (orec) {
		tmp = orec->next;

		/* tell the observer */
		if (orec->observerAction) {
			(*orec->observerAction) (orec->observer, notification);
		}
		orec = tmp;
	}

	WMReleaseNotification(notification);
}

void WMRemoveNotificationObserver(void *observer)
{
	NotificationObserver *orec, *tmp, *rec;

	/* get the list of actions the observer is doing */
	orec = (NotificationObserver *) WMHashGet(notificationCenter->observerTable, observer);

	/*
	 * FOREACH orec IN actionlist for observer
	 * DO
	 *   remove from respective lists/tables
	 *   free
	 * END
	 */
	while (orec) {
		tmp = orec->nextAction;

		if (!orec->name && !orec->object) {
			/* catch-all */
			if (notificationCenter->nilList == orec)
				notificationCenter->nilList = orec->next;
		} else if (!orec->name) {
			/* any message coming from object */
			rec = (NotificationObserver *) WMHashGet(notificationCenter->objectTable, orec->object);
			if (rec == orec) {
				/* replace table entry */
				if (orec->next) {
					WMHashInsert(notificationCenter->objectTable, orec->object, orec->next);
				} else {
					WMHashRemove(notificationCenter->objectTable, orec->object);
				}
			}
		} else {
			/* name && (object || !object) */
			rec = (NotificationObserver *) WMHashGet(notificationCenter->nameTable, orec->name);
			if (rec == orec) {
				/* replace table entry */
				if (orec->next) {
					WMHashInsert(notificationCenter->nameTable, orec->name, orec->next);
				} else {
					WMHashRemove(notificationCenter->nameTable, orec->name);
				}
			}
		}
		if (orec->prev)
			orec->prev->next = orec->next;
		if (orec->next)
			orec->next->prev = orec->prev;

		wfree(orec);

		orec = tmp;
	}

	WMHashRemove(notificationCenter->observerTable, observer);
}

void WMRemoveNotificationObserverWithName(void *observer, const char *name, void *object)
{
	NotificationObserver *orec, *tmp, *rec;
	NotificationObserver *newList = NULL;

	/* get the list of actions the observer is doing */
	orec = (NotificationObserver *) WMHashGet(notificationCenter->observerTable, observer);

	WMHashRemove(notificationCenter->observerTable, observer);

	/* rebuild the list of actions for the observer */

	while (orec) {
		tmp = orec->nextAction;
		if (orec->name == name && orec->object == object) {
			if (!name && !object) {
				if (notificationCenter->nilList == orec)
					notificationCenter->nilList = orec->next;
			} else if (!name) {
				rec =
				    (NotificationObserver *) WMHashGet(notificationCenter->objectTable,
								       orec->object);
				if (rec == orec) {
					assert(rec->prev == NULL);
					/* replace table entry */
					if (orec->next) {
						WMHashInsert(notificationCenter->objectTable,
							     orec->object, orec->next);
					} else {
						WMHashRemove(notificationCenter->objectTable, orec->object);
					}
				}
			} else {
				rec = (NotificationObserver *) WMHashGet(notificationCenter->nameTable,
									 orec->name);
				if (rec == orec) {
					assert(rec->prev == NULL);
					/* replace table entry */
					if (orec->next) {
						WMHashInsert(notificationCenter->nameTable,
							     orec->name, orec->next);
					} else {
						WMHashRemove(notificationCenter->nameTable, orec->name);
					}
				}
			}

			if (orec->prev)
				orec->prev->next = orec->next;
			if (orec->next)
				orec->next->prev = orec->prev;
			wfree(orec);
		} else {
			/* append this action in the new action list */
			orec->nextAction = NULL;
			if (!newList) {
				newList = orec;
			} else {
				NotificationObserver *p;

				p = newList;
				while (p->nextAction) {
					p = p->nextAction;
				}
				p->nextAction = orec;
			}
		}
		orec = tmp;
	}

	/* reinsert the list to the table */
	if (newList) {
		WMHashInsert(notificationCenter->observerTable, observer, newList);
	}
}

void WMPostNotificationName(const char *name, void *object, void *clientData)
{
	WMNotification *notification;

	notification = WMCreateNotification(name, object, clientData);

	WMPostNotification(notification);

	WMReleaseNotification(notification);
}

/**************** Notification Queues ****************/

typedef struct W_NotificationQueue {
	WMArray *asapQueue;
	WMArray *idleQueue;

	struct W_NotificationQueue *next;
} NotificationQueue;

static WMNotificationQueue *notificationQueueList = NULL;

/* default queue */
static WMNotificationQueue *notificationQueue = NULL;

WMNotificationQueue *WMGetDefaultNotificationQueue(void)
{
	if (!notificationQueue)
		notificationQueue = WMCreateNotificationQueue();

	return notificationQueue;
}

WMNotificationQueue *WMCreateNotificationQueue(void)
{
	NotificationQueue *queue;

	queue = wmalloc(sizeof(NotificationQueue));

	queue->asapQueue = WMCreateArrayWithDestructor(8, (WMFreeDataProc *) WMReleaseNotification);
	queue->idleQueue = WMCreateArrayWithDestructor(8, (WMFreeDataProc *) WMReleaseNotification);
	queue->next = notificationQueueList;

	notificationQueueList = queue;

	return queue;
}

void WMEnqueueNotification(WMNotificationQueue * queue, WMNotification * notification, WMPostingStyle postingStyle)
{
	WMEnqueueCoalesceNotification(queue, notification, postingStyle, WNCOnName | WNCOnSender);
}

#define NOTIF ((WMNotification*)cdata)
#define ITEM  ((WMNotification*)item)

static int matchSenderAndName(void *item, void *cdata)
{
	return (NOTIF->object == ITEM->object && strcmp(NOTIF->name, ITEM->name) == 0);
}

static int matchSender(void *item, void *cdata)
{
	return (NOTIF->object == ITEM->object);
}

static int matchName(void *item, void *cdata)
{
	return (strcmp(NOTIF->name, ITEM->name) == 0);
}

#undef NOTIF
#undef ITEM

void WMDequeueNotificationMatching(WMNotificationQueue * queue, WMNotification * notification, unsigned mask)
{
	WMMatchDataProc *matchFunc;

	if ((mask & WNCOnName) && (mask & WNCOnSender))
		matchFunc = matchSenderAndName;
	else if (mask & WNCOnName)
		matchFunc = matchName;
	else if (mask & WNCOnSender)
		matchFunc = matchSender;
	else
		return;

	WMRemoveFromArrayMatching(queue->asapQueue, matchFunc, notification);
	WMRemoveFromArrayMatching(queue->idleQueue, matchFunc, notification);
}

void
WMEnqueueCoalesceNotification(WMNotificationQueue * queue,
			      WMNotification * notification, WMPostingStyle postingStyle, unsigned coalesceMask)
{
	if (coalesceMask != WNCNone)
		WMDequeueNotificationMatching(queue, notification, coalesceMask);

	switch (postingStyle) {
	case WMPostNow:
		WMPostNotification(notification);
		WMReleaseNotification(notification);
		break;

	case WMPostASAP:
		WMAddToArray(queue->asapQueue, notification);
		break;

	case WMPostWhenIdle:
		WMAddToArray(queue->idleQueue, notification);
		break;
	}
}

void W_FlushASAPNotificationQueue()
{
	WMNotificationQueue *queue = notificationQueueList;

	while (queue) {
		while (WMGetArrayItemCount(queue->asapQueue)) {
			WMPostNotification(WMGetFromArray(queue->asapQueue, 0));
			WMDeleteFromArray(queue->asapQueue, 0);
		}

		queue = queue->next;
	}
}

void W_FlushIdleNotificationQueue()
{
	WMNotificationQueue *queue = notificationQueueList;

	while (queue) {
		while (WMGetArrayItemCount(queue->idleQueue)) {
			WMPostNotification(WMGetFromArray(queue->idleQueue, 0));
			WMDeleteFromArray(queue->idleQueue, 0);
		}

		queue = queue->next;
	}
}
