/* ====================================================================
 * FTD4Linux Software License, Version 1.0
 *
 * Copyright (c) 2000-2004 The FTD4Linux Development Team.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        FTD4Linux Development Team (http://www.ftd4linux.2y.net/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "FTD" and "FTD4Linux" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact alguno@ftd4linux.2y.net.
 *
 * 5. Products derived from this software may not be called "FTD4Linux",
 *    nor may "FTD4Linux" appear in their name, without prior written
 *    permission of the FTD4Linux Development Team.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE FTD4LINUX DEVELOPMENT TEAM OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <glib-object.h>
#include "ftd4linux.h"
#include "ftd_app_core.h"
#include "ftd_event.h"

static FtdTree *ftd_eventlist_tree_new(void);
static void     ftd_eventlist_tree_free(FtdTree *tree);

typedef struct FTDEventlistClass FTDEventlistClass;

FTDEventlist *ftd_eventlist_get_object(void);
GType ftd_eventlist_get_type (void);

struct FTDEventlist
{
    GObject parent;

    GStaticRWLock rwlock;

    FtdTree         *eventTreeTotal;          ///< De volledige boom met ::FtdEvent pointers naar alle events.
    FtdTree         *eventTreeFiltered;       ///< De gefilterde boom met ::FtdEvent pointers. Let op! Geef alleen de boom en NIET de elementen in de boom vrij! Deze elementen zitten ook in eventTreeTotal en dienen daar vrijgegeven te worden.
    FtdFilter       *filter;                  ///< Het filter dat momenteel actief is.

};

struct FTDEventlistClass
{
    GObjectClass parent;
};

enum
{
  BRING_TO_FRONT_SIGNAL,
  OPEN_POSTING_SIGNAL,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(FTDEventlist, ftd_eventlist, G_TYPE_OBJECT)

static void
ftd_eventlist_init (FTDEventlist *obj)
{
    g_static_rw_lock_init(&obj->rwlock);

    obj->eventTreeTotal = ftd_eventlist_tree_new();
    obj->eventTreeFiltered = NULL;
    obj->filter = NULL;
}

static void
eventlist_finalize (GObject *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    if (eventlist->eventTreeTotal) {
        ftd_eventlist_tree_free(eventlist->eventTreeTotal);
        eventlist->eventTreeTotal = NULL;
    }

    if (eventlist->eventTreeFiltered) {
        ftd_tree_destroy(eventlist->eventTreeFiltered);
        eventlist->eventTreeFiltered = NULL;
    }

    if (eventlist->filter) {
        ftd_filter_free_filter(eventlist->filter);
        eventlist->filter = NULL;
    }

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);
    g_static_rw_lock_free(&eventlist->rwlock);
}

static void
ftd_eventlist_class_init (FTDEventlistClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = eventlist_finalize;

#if 0
    signals[BRING_TO_FRONT_SIGNAL] = g_signal_new  ("bring_to_front",
                                                    G_OBJECT_CLASS_TYPE (klass),
                                                    G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                                                    0,
                                                    NULL, NULL,
                                                    g_cclosure_marshal_VOID__VOID,
                                                    G_TYPE_NONE, 0);

    signals[OPEN_POSTING_SIGNAL] = g_signal_new    ("open_posting",
                                                    G_OBJECT_CLASS_TYPE (klass),
                                                    G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                                                    0,
                                                    NULL, NULL,
                                                    g_cclosure_marshal_VOID__INT,
                                                    G_TYPE_NONE, 1, G_TYPE_INT);
#endif
}

FTDEventlist *
ftd_eventlist_new(void)
{
    return g_object_new(FTD_EVENTLIST_TYPE_OBJECT, NULL);
}

void
ftd_eventlist_destroy(FTDEventlist *obj)
{
    g_object_unref(obj);
}

static gint
tree_compare_func(gconstpointer a, gconstpointer b)
{
    return (GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b));
}

static FtdTree *
ftd_eventlist_tree_new(void)
{
    return ftd_tree_new(tree_compare_func);
}

static gboolean
tree_free_func(gpointer key, gpointer value, gpointer data)
{
    FTDEventObject *event = FTD_EVENT_OBJECT(value);

    ftd_event_object_destroy(event);

    return FALSE;
}

/**
 * Geeft de boom met alle events vrij.
 */
static void
ftd_eventlist_tree_free (FtdTree *eventTree)
{
    g_return_if_fail(eventTree != NULL);

    ftd_tree_foreach(eventTree, tree_free_func, NULL);

    ftd_tree_destroy(eventTree);
}

/**
 * Laad de lijst met alle events die eerder opgeslagen werden.
 */
gboolean
ftd_eventlist_load(FTDEventlist *obj, char **errmsg)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FtdString *inhoud;
    FtdErrorCode retval;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    // Voorkom memory leaks
    if (eventlist->eventTreeTotal) {
        ftd_eventlist_tree_free(eventlist->eventTreeTotal);
        eventlist->eventTreeTotal = ftd_eventlist_tree_new();
    }

    if (eventlist->eventTreeFiltered) {
        ftd_tree_destroy(eventlist->eventTreeFiltered);
        eventlist->eventTreeFiltered = NULL;
    }

    inhoud = ftd_file_load(EVENTS_FILE, TRUE);
    if (inhoud == NULL) {
        g_static_rw_lock_writer_unlock(&eventlist->rwlock);
        return ERROR_UNKNOWN;   // FIXME: Is dit wel de juiste foutcode hiervoor ?
    }

    ftd_categories_reset_counters();
    retval = ftd_events_parse (inhoud, &eventlist->eventTreeTotal, NULL, errmsg, TRUE);

    ftd_string_free(inhoud);

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    return retval;
}

struct tree_save_func_data {
    xmlDocPtr doc;
    xmlNodePtr rootNode;
    time_t cur_time;
};

static gboolean
tree_save_func(gpointer key, gpointer value, gpointer data)
{
    struct tree_save_func_data *func_data = (struct tree_save_func_data *) data;
    FtdEvent *event = (FtdEvent *) value;

    // Postings met de status DELETED moeten automatisch na 24 uur verwijderd worden
    if (event->status == FTDSTATUS_DELETED && func_data->cur_time > event->updated + DUUR_DAG) {
        return FALSE;
    }

    xmlAddChild(func_data->rootNode, ftd_events_xml_add_basic(event, func_data->doc));

    return FALSE;
}

/**
 * Slaat de gegeven tree met events op als zijnde de volledige lijst.
 *
 * @arg     tree        Een FtdTree met ::FtdEvent pointers naar alle events die opgeslagen moeten worden.
 *
 * @return  True indien het opslaan succesvol was.
 */
gboolean
ftd_eventlist_save(FTDEventlist *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    char *volledigeFilename;
    xmlDocPtr doc;
    xmlNodePtr rootNode;
    int retval;
    struct tree_save_func_data data;

    if(!eventlist->eventTreeTotal) {
        return FALSE;
    }

    doc = xmlNewDoc(BAD_CAST "1.0");
    rootNode = xmlNewNode(NULL, BAD_CAST "Events");
    xmlDocSetRootElement(doc, rootNode);

    data.cur_time = time(NULL);
    data.doc = doc;
    data.rootNode = rootNode;

    ftd_eventlist_foreach(eventlist, FALSE, TRUE, tree_save_func, &data);

    volledigeFilename = ftd_file_parse_cache_filename(EVENTS_FILE, TRUE);
    xmlSetCompressMode(9);
    retval = xmlSaveFormatFile(volledigeFilename, doc, 1);
    g_free(volledigeFilename);

    xmlFreeDoc(doc);

    return (retval != -1);
}

struct _foreach_func_data
{
    GTraverseFunc cb;
    gpointer data;
};

static gboolean
foreach_func(gpointer key, gpointer value, gpointer data)
{
    struct _foreach_func_data *func_data = (struct _foreach_func_data *) data;
    FTDEventObject *eventObj = FTD_EVENT_OBJECT(value);
    FtdEvent *event;
    gboolean retval;

    g_static_rw_lock_reader_lock(&eventObj->rwlock);
    event = ftd_event_duplicate(eventObj->event);
    g_static_rw_lock_reader_unlock(&eventObj->rwlock);

    retval = func_data->cb(key, event, func_data->data);

    ftd_event_free(event);

    return retval;
}

void
ftd_eventlist_foreach(FTDEventlist *obj, gboolean filtered_list, gboolean read_only, GTraverseFunc cb, gpointer data)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    struct _foreach_func_data func_data;

    if (read_only) {
        g_static_rw_lock_reader_lock(&eventlist->rwlock);
    } else {
        g_static_rw_lock_writer_lock(&eventlist->rwlock);
    }

    func_data.cb = cb;
    func_data.data = data;

    if (filtered_list) {
        ftd_tree_foreach(eventlist->eventTreeFiltered, foreach_func, &func_data);
    } else {
        ftd_tree_foreach(eventlist->eventTreeTotal, foreach_func, &func_data);
    }

    if (read_only) {
        g_static_rw_lock_reader_unlock(&eventlist->rwlock);
    } else {
        g_static_rw_lock_writer_unlock(&eventlist->rwlock);
    }
}

static gboolean
tree_filter_func(gpointer key, gpointer value, gpointer data)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(data);
    FTDEventObject *event = FTD_EVENT_OBJECT(value);
    int postId = eventlist->filter->postId;

    g_static_rw_lock_reader_lock(&event->rwlock);

    // Als we moeten zoeken op FTD ID mogen we alle ingestelde filters negeren
    if (postId != 0) {
        if (event->event->postId == postId) {
            ftd_tree_insert(eventlist->eventTreeFiltered, GINT_TO_POINTER(event->event->postId), event);

            g_static_rw_lock_reader_unlock(&event->rwlock);

            return TRUE;
        }

        g_static_rw_lock_reader_unlock(&event->rwlock);

        return FALSE;
    }

    if (ftd_filter_accepts(eventlist->filter, event->event, TRUE)) {
        ftd_tree_insert(eventlist->eventTreeFiltered, GINT_TO_POINTER(event->event->postId), event);
    }

    g_static_rw_lock_reader_unlock(&event->rwlock);

    return FALSE;
}

/**
 * Filtert de lijst met alle events aan de hand van het opgegeven filter
 */
void
ftd_eventlist_apply_filter (FTDEventlist *obj, FtdFilter *filter)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    if (eventlist->eventTreeFiltered) {
        ftd_tree_destroy (eventlist->eventTreeFiltered);
    }
    eventlist->eventTreeFiltered = ftd_eventlist_tree_new();

    eventlist->filter = ftd_filter_duplicate(filter);

    // Pas een eventueel nieuwsgroepen-filter toe
    if (eventlist->filter->groupFilterList) {
        ftd_group_set_group_id_list(eventlist->filter->groupFilterList);
    }

    ftd_tree_foreach(eventlist->eventTreeTotal, tree_filter_func, eventlist);

    // Herstel het toegepaste nieuwsgroepen-filter
    if (eventlist->filter->groupFilterList) {
        ftd_group_unset_group_id_list();
    }

    /* Als we op FTD id moeten zoeken en het FTD event is nog niet aanwezig
       in de lokale cache, dan dient deze online opgezocht te worden */
    if (eventlist->filter->postId != 0 && ftd_tree_num_nodes(eventlist->eventTreeFiltered) == 0) {
        char *errmsg = NULL;

        g_static_rw_lock_writer_unlock(&eventlist->rwlock);
        FtdErrorCode retval = ftd_eventinfo_fetch(eventlist->filter->postId, &errmsg);
        g_static_rw_lock_writer_lock(&eventlist->rwlock);
        if (retval == ERROR_NONE) {
            FTDEventObject *event = ftd_tree_lookup(eventlist->eventTreeTotal, GINT_TO_POINTER(eventlist->filter->postId));

            if (event) {
                ftd_tree_insert(eventlist->eventTreeFiltered, GINT_TO_POINTER(event->event->postId), event);
            }
        }
    }

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);
}

/**
 * Voegt twee lijsten met events samen. Een eventueel ingesteld filter wordt hierbij
 * nog niet toegepast!
 *
 * @arg     tree            De originele boom met ::FtdEvent pointers.
 * @arg     listNieuw       De lijst met ::FtdEvent pointers die bij de originele lijst gevoegd moeten worden.
 * @arg     callback        Een callback functie die elke 10 events aangeroepen wordt, om terugkoppeling te geven naar de gebruiker. Mag ook NULL zijn.
 */
void
ftd_eventlist_merge (FTDEventlist *obj, FtdList *listNieuw, FtdEventParseCallback callback, gpointer callback_data)
{
    FtdList *list = NULL;
    int len, cur;
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FtdList *listSeenEvents = NULL;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    list = listNieuw;
    len = ftd_list_length(listNieuw);
    cur = 0;

    while (list) {
        FTDEventObject *eventOrig;
        FTDEventObject *event = ftd_list_value(list);

        cur++;
        if (callback && cur % 10 == 0) {        // Stuur alleen elke 10e keer een seintje om de
            callback(len, cur, callback_data);  // rest niet te veel te vertragen
        }

        /* Zijn we dit event reeds tegengekomen ? */
        if (ftd_list_find(listSeenEvents, GINT_TO_POINTER(event->event->postId))) {
             /* Ja, negeer dit event */
            list = ftd_list_next(list);
            continue;
        }

        /* Update de categorie-tellertjes */
        if (event->event->stamp >= core->config->prevCheck) {      // Tel alleen de nieuwe postings
            ftd_category_update(event->event);
        }

        /* Kijk of het event in de watcher lijst voorkomt en activeer een vlaggetje hiervoor */
        if (ftd_watcher_test_event(event->event)) {
            core->watcher_state = 0;
            event->event->gevonden_voor_watcher = TRUE;
        }

        /* Zoek in de listRes of er soms wijzigingen zijn aangebracht in dit record */
        eventOrig = ftd_tree_lookup(eventlist->eventTreeTotal, GINT_TO_POINTER(event->event->postId));

        if (eventOrig == NULL) {
            /* Geen wijzigingen gevonden, voeg event aan de boom toe */
            ftd_tree_insert(eventlist->eventTreeTotal, GINT_TO_POINTER(event->event->postId), event);
        } else {
            /* Vervang originele event */
            ftd_tree_replace(eventlist->eventTreeTotal, GINT_TO_POINTER(event->event->postId), event);
            ftd_event_object_destroy(eventOrig);

            /* Kijk of het event in de watchlist voorkomt en activeer een vlaggetje hiervoor */
            if (ftd_watchlist_is_in_list(event->event->postId)) {
                core->watchlist_state = 0;
                event->event->geupdated_in_watchlist = TRUE;
            }
        }

        listSeenEvents = ftd_list_append(listSeenEvents, GINT_TO_POINTER(event->event->postId));

        list = ftd_list_next(list);
    }

    if (callback) {
        callback(len, len, callback_data);
    }

    ftd_list_free(listSeenEvents);

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);
}

/**
 * Voegt een event toe aan de eventlist
 */
void
ftd_eventlist_add_or_replace_event (FTDEventlist *obj, const FtdEvent *event)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FTDEventObject *eventOrig;
    FTDEventObject *eventNieuw;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    /* Zoek in de lijst of dit element reeds voorkomt */
    eventOrig = ftd_tree_lookup(eventlist->eventTreeTotal, GINT_TO_POINTER(event->postId));
    if (eventOrig == NULL) {
        /* Niets gevonden, voeg event aan de boom toe */
        eventNieuw = ftd_event_object_new();
        ftd_event_object_set_event(eventNieuw, event);
        ftd_tree_insert(eventlist->eventTreeTotal, GINT_TO_POINTER(event->postId), eventNieuw);
    } else {
        /* Vervang originele event */
        ftd_event_object_set_event(eventOrig, event);

        /* Kijk of het even in de watchlist voorkomt en activeer een vlaggetje hiervoor */
        if (ftd_watchlist_is_in_list(event->postId)) {
            core->watchlist_state = 0;
        }
    }

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);
}

FtdEvent *
ftd_eventlist_lookup_id(FTDEventlist *obj, int eventId)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FTDEventObject *event_obj;
    FtdEvent *event = NULL;

    g_static_rw_lock_reader_lock(&eventlist->rwlock);

    event_obj = ftd_tree_lookup(eventlist->eventTreeTotal, GINT_TO_POINTER(eventId));
    if (event_obj) {
        event = ftd_event_object_get_event(event_obj);
    }

    g_static_rw_lock_reader_unlock(&eventlist->rwlock);

    return event;
}

static gboolean
eventlist_purge_func(gpointer key, gpointer value, gpointer data)
{
    FtdList **list = (FtdList **) data;
    FTDEventObject *event = FTD_EVENT_OBJECT(value);

    g_static_rw_lock_reader_lock(&event->rwlock);

    // We kunnen vanuit hier niet verwijderen. Los dit op door alle records
    // die verwijderd moeten worden te markeren in een speciale lijst
    if ((event->event->status == FTDSTATUS_DELETED && time(NULL) > event->event->updated + DUUR_DAG) || event->event->status == FTDSTATUS_DELETED2) {
        *list = ftd_list_prepend(*list, GINT_TO_POINTER(event->event->postId));
    }

    g_static_rw_lock_reader_unlock(&event->rwlock);

    return FALSE;
}

void
ftd_eventlist_purge_deleted(FTDEventlist *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FtdList *listPurge;
    FtdList *list;

    /* Verwijder postings met status 'Deleted' die ouder zijn dan 24 uur */
    listPurge = NULL;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    ftd_tree_foreach(eventlist->eventTreeTotal, eventlist_purge_func, &listPurge);

    list = listPurge;
    while(list) {
        int postId = GPOINTER_TO_INT(ftd_list_value(list));
        FTDEventObject *eventObj = ftd_tree_lookup(eventlist->eventTreeTotal, GINT_TO_POINTER(postId));

        /* Als het event in de watchlist voorkomt, verwijderen */
        if (ftd_watchlist_is_in_list(postId)) {
            ftd_watchlist_remove(postId);
        }

#if GLIB_CHECK_VERSION (2,8,0)
        if (!ftd_tree_remove(eventlist->eventTreeTotal, GINT_TO_POINTER(postId))) {
            g_print(__FILE__ ":%i dit mag niet voorkomen!!\n", __LINE__);
        }
#else
        ftd_tree_remove(eventlist->eventTreeTotal, GINT_TO_POINTER(postId));
#endif

        ftd_event_object_destroy(eventObj);

        list = ftd_list_next(list);
    }

    ftd_list_free(listPurge);

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);
}

struct tree_cleanup_purge_data {
    time_t minimumDatum;
    FtdList *listPurge;
};

static gboolean
tree_cleanup_func(gpointer key, gpointer value, gpointer data)
{
    struct tree_cleanup_purge_data *func_data = (struct tree_cleanup_purge_data *) data;
    FTDEventObject *event = FTD_EVENT_OBJECT(value);

    g_static_rw_lock_reader_lock(&event->rwlock);

    if (func_data->minimumDatum > event->event->stamp) {
        // Event is verlopen en moet verwijderd worden
        func_data->listPurge = ftd_list_prepend(func_data->listPurge, GINT_TO_POINTER(event->event->postId));
    }

    g_static_rw_lock_reader_unlock(&event->rwlock);

    return FALSE;
}

/**
 * Ruimt de lijst met alle events op.
 */
void
ftd_eventlist_cleanup(FTDEventlist *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    struct tree_cleanup_purge_data func_data;
    int verwijderPostsNaXDagen;
    FtdList *list;

    if (!eventlist->eventTreeTotal) {
        return;
    }

    verwijderPostsNaXDagen = 0;
    switch(core->config->verwijderPostsNaXDagen) {
        case 0:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE1) * DUUR_DAG;
            break;
        case 1:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE2) * DUUR_DAG;
            break;
        case 2:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE3) * DUUR_DAG;
            break;
        case 3:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE4) * DUUR_DAG;
            break;
        case 4:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE5) * DUUR_DAG;
            break;
        case 5:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE6) * DUUR_DAG;
            break;
        case 6:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE7) * DUUR_DAG;
            break;
        case 7:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE8) * DUUR_DAG;
            break;
        case 8:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE9) * DUUR_DAG;
            break;
        case 9:
            verwijderPostsNaXDagen = atoi(CACHE_OPTIE10) * DUUR_DAG;
            break;
        default:
            g_warning("Ongeldige waarde : core->config->verwijderPostsNaXDagen = %i\n", core->config->verwijderPostsNaXDagen);
            return;
    }

    func_data.minimumDatum = time(NULL) - verwijderPostsNaXDagen;
    func_data.listPurge = NULL;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    ftd_tree_foreach(eventlist->eventTreeTotal, tree_cleanup_func, &func_data);

    list = func_data.listPurge;

    while (list) {
        int postId = GPOINTER_TO_INT(ftd_list_value(list));
        FTDEventObject *eventObj = ftd_tree_lookup(eventlist->eventTreeTotal, GINT_TO_POINTER(postId));

        /* Event is verlopen..kijk of er nog een vermelding
         * in de watchlist staat en zoja, bewaar deze ! */
        if (ftd_watchlist_is_in_list(postId)) {
            list = ftd_list_next(list);
            continue;
        }

        /* Verwijder het element uit de boom */
#if GLIB_CHECK_VERSION (2,8,0)
        if (!ftd_tree_remove(eventlist->eventTreeTotal, GINT_TO_POINTER(postId))) {
            g_print(__FILE__ ":%i dit mag niet voorkomen!!\n", __LINE__);
        }
#else
        ftd_tree_remove(eventlist->eventTreeTotal, GINT_TO_POINTER(postId));
#endif

        ftd_event_object_destroy(eventObj);

        list = ftd_list_next(list);
    }

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    /* Schrijf de lijst alleen terug naar de harde schijf als er
     * ook daadwerkelijk iets veranderd iets */
    if (func_data.listPurge) {
        /* we moeten de eventlist eerst opslaan en daarna
         * opnieuw inlezen omdat anders de tellertjes in de category
         * list niet kloppen */
        ftd_eventlist_save(eventlist);
        ftd_eventlist_load(eventlist, NULL);

        ftd_list_free(func_data.listPurge);
    }
}

/**
 * Geeft het aantal events dat door het filter werd geaccepteerd weer.
 *
 * @return  Het aantal events in de gefilterde lijst.
 */
int
ftd_eventlist_get_count(FTDEventlist *obj, gboolean filtered)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    int count;

    g_static_rw_lock_reader_lock(&eventlist->rwlock);

    if (filtered) {
        count = ftd_tree_num_nodes(eventlist->eventTreeFiltered);
    } else {
        count = ftd_tree_num_nodes(eventlist->eventTreeTotal);
    }

    g_static_rw_lock_reader_unlock(&eventlist->rwlock);

    return count;
}

#if 0
static gint
tree_get_first_event_func(gconstpointer a, gconstpointer b)
{
    // We moeten alleen het eerste element hebben, retourneer dus gelijk 0
    return 0;
}
#else
static void
hashtable_get_first_event_func(gpointer key, gpointer value, gpointer user_data)
{
    FtdEvent **ret = user_data;
    FTDEventObject *event = FTD_EVENT_OBJECT(value);

    *ret = ftd_event_duplicate(event->event);
}
#endif

/**
 * Geeft het eerste event uit de gefilterde boom terug.
 *
 * @return  Het eerste event uit de gefilterde boom. Dit event moet vrijgegeven worden met ftd_event_free()
 */
FtdEvent *
ftd_eventlist_get_first_filtered_event(FTDEventlist *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FTDEventObject *event;
    FtdEvent *ret = NULL;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);
#if 0
    event = ftd_tree_search(eventlist->eventTreeFiltered, tree_get_first_event_func, NULL);
    ret = ftd_event_duplicate(event->event);
#else
    g_hash_table_foreach(eventlist->eventTreeFiltered, hashtable_get_first_event_func, &ret);
#endif
    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    return ret;
}

FtdFilter *
ftd_eventlist_get_filter(FTDEventlist *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FtdFilter *filter;

    g_static_rw_lock_reader_lock(&eventlist->rwlock);

    filter = ftd_filter_duplicate(eventlist->filter);

    g_static_rw_lock_reader_unlock(&eventlist->rwlock);

    return filter;
}

void
ftd_eventlist_set_posting_is_active(FTDEventlist *obj, int eventId, gboolean is_active)
{
    // Vereist geen implementatie aangezien we alles in het geheugen bijhouden
}
