#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;
    }
}



syntax highlighted by Code2HTML, v. 0.9.1