/* ====================================================================
 * 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 <sqlite3.h>
#include "ftd4linux.h"
#include "ftd_app_core.h"
#include "ftd_event.h"
#include "ftd_sqlite.h"
#include "ftd_eventinfo_cache.h"

static void add_or_replace_event(FTDSQLite *sqlite, const FtdEvent *event);

typedef struct FTDEventlistClass FTDEventlistClass;

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

struct FTDEventlist
{
    GObject parent;

    GStaticRWLock rwlock;

    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->filter = NULL;
}

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

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

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

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 gboolean
tree_free_func(gpointer key, gpointer value, gpointer data)
{
    FTDEventObject *event = FTD_EVENT_OBJECT(value);

    ftd_event_object_destroy(event);

    return FALSE;
}

/**
 * Laad de lijst met alle events die eerder opgeslagen werden.
 */
gboolean
ftd_eventlist_load(FTDEventlist *obj, char **errmsg)
{
    // Vereist met SQLite geen implementatie aangezien direct op de database gewerkt wordt
    return TRUE;
}

/**
 * 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)
{
    // Vereist met SQLite geen implementatie aangezien direct op de database gewerkt wordt
    return TRUE;
}

struct _foreach_func_data
{
    FTDEventlist *eventlist;
    gboolean has_filter;
    GTraverseFunc cb;
    gpointer data;
};

static gboolean
foreach_func(int num_columns, const char **field_data, gpointer data)
{
    struct _foreach_func_data *func_data = (struct _foreach_func_data *) data;
    FtdEvent *event;
    gboolean retval;
    FtdEvent *event_cache;
    FtdList *list;

    // Query: SELECT ID, Status, FName, Title, Poster, Link, Stamp, Groups, Cat, SCat, Updated, Views, Tags, Tag FROM FTDPosts

    event = ftd_event_new();
    event->postId = atoi(field_data[0]);
    event->status = atoi(field_data[1]);
    event->fileName = ftd_string_new(field_data[2]);
    event->title = ftd_string_new(field_data[3]);
    event->poster = ftd_string_new(field_data[4]);
    event->link = ftd_string_new(field_data[5]);
    event->stamp = atoi(field_data[6]);
    event->groups = ftd_splitted_list_split(field_data[7]);
    event->cat = atoi(field_data[8]);
    event->sCat = atoi(field_data[9]);
    event->updated = atoi(field_data[10]);
    event->numViews = atoi(field_data[11]);
    event->tags = ftd_splitted_list_split(field_data[12]);
    event->extra_tag = ftd_string_new(field_data[13]);

    // Controleer of deze posting wel aan de opgegeven tags voldoet
    // Dit moet apart gebeuren aangezien dit met SQL niet zo makkelijk
    // te achterhalen valt
    if (func_data->has_filter && !ftd_filter_accepts_tags(func_data->eventlist->filter, event)) {
        ftd_event_free(event);
        return FALSE;
    }

    // Hebben we nog aanvullende gegevens in onze cache staan ?
    event_cache = ftd_event_info_cache_lookup(core->eventinfo_cache, event->postId);
    if (event_cache) {
        event->rating = event_cache->rating;
        if (event_cache->announcement) {
            event->announcement = ftd_string_new(ftd_string_value(event_cache->announcement));
        }

        if (event_cache->description) {
            event->description = ftd_string_new(ftd_string_value(event_cache->description));
        }

        if (event_cache->sig) {
            event->sig = ftd_string_new(ftd_string_value(event_cache->sig));
        }

        event->fileType = event_cache->fileType;
        event->aantalFiles = event_cache->aantalFiles;
        event->fileStart = event_cache->fileStart;
        event->parType = event_cache->parType;
        event->aantalParFiles = event_cache->aantalParFiles;
        event->parBlockSize = event_cache->parBlockSize;
        event->numViews = event_cache->numViews;
        event->kudos = event_cache->kudos;
        event->locked = event->locked;

        if (event_cache->image) {
            event->image = ftd_string_new(ftd_string_value(event_cache->image));
        }

        if (event_cache->reason) {
            event->reason = ftd_string_new(ftd_string_value(event_cache->reason));
        }

        // Dupliceer de MP3 tracks
        event->mp3Tracks = NULL;
        list = event_cache->mp3Tracks;
        while (list) {
            FtdString *track = ftd_list_value(list);

            event->mp3Tracks = ftd_list_append(event->mp3Tracks, ftd_string_new(ftd_string_value(track)));

            list = ftd_list_next(list);
        }

        // Dupliceer de forum informatie
        if (event_cache->forum) {
            event->forum = ftd_events_forum_new();
            event->forum->lastUpdated = event_cache->forum->lastUpdated;

            list = event_cache->forum->forum_reacties;
            while (list) {
                FtdEventForumReactie *reactie_nieuw;
                FtdEventForumReactie *reactie_oud = ftd_list_value(list);

                reactie_nieuw = ftd_events_forum_reactie_new();
                memcpy(reactie_nieuw, reactie_oud, sizeof(FtdEventForumReactie));
                reactie_nieuw->author = ftd_string_new(ftd_string_value(reactie_oud->author));
                reactie_nieuw->body = ftd_string_new(ftd_string_value(reactie_oud->body));
                reactie_nieuw->avatar = ftd_string_new(ftd_string_value(reactie_oud->avatar));

                event->forum->forum_reacties = ftd_list_append(event->forum->forum_reacties, reactie_nieuw);
                list = ftd_list_next(list);
            }
        }

        ftd_event_free(event_cache);
    }

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

    ftd_event_free(event);

    return retval;
}

static char *
add_clause(const char *query, gboolean has_where_keyword, const char *clause)
{
    char *ret;

    if (has_where_keyword) {
        ret = g_strdup_printf("%s AND %s", query, clause);
    } else {
        ret = g_strdup_printf("%s WHERE %s", query, clause);
    }

    return ret;
}

static char *
generate_select_query(gboolean get_count, gboolean use_filter, FtdFilter *filter)
{
    char *query;

    if (use_filter) {
        char *query_tmp;
        gboolean has_where_keyword = FALSE;

        g_assert(filter != NULL);

        if (get_count) {
            query = g_strdup_printf("SELECT COUNT(ID) FROM FTDPosts ");
        } else {
            query = g_strdup_printf("SELECT ID, Status, FName, Title, Poster, Link, Stamp, Groups, Cat, SCat, Updated, Views, Tags, Tag FROM FTDPosts ");
        }

        if (filter->prevCheck) {
            char *tmp = g_strdup_printf("Stamp > %i", filter->prevCheck);
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            g_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->postId) {
            char *tmp = g_strdup_printf("ID=%i", filter->postId);
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            g_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->title && ftd_string_length(filter->title) > 0) {
            char *tmp = sqlite3_mprintf("Title LIKE '%%%q%%'", ftd_string_value(filter->title));
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            sqlite3_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->poster && ftd_string_length(filter->poster) > 0) {
            char *tmp = sqlite3_mprintf("Poster LIKE '%%%q%%'", ftd_string_value(filter->poster));
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            sqlite3_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->cat >= 0) {
            char *tmp = g_strdup_printf("Cat = %i", filter->cat);
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            g_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->sCat >= 0) {
            char *tmp = g_strdup_printf("SCat = %i", filter->sCat);
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            g_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->groupFilterList) {
            char *tmp, *tmp2;
            FtdList *list = filter->groupFilterList;
            tmp = g_strdup("(");
            gboolean beenhere = FALSE;

            while (list) {
                FtdString *groupId = ftd_list_value(list);

                if (!beenhere) {
                    beenhere = TRUE;
                    tmp2 = g_strdup_printf("%s Groups LIKE '%%%s|%%'", tmp, ftd_string_value(groupId));
                    g_free(tmp);
                    tmp = tmp2;
                } else {
                    tmp2 = g_strdup_printf("%s OR Groups LIKE '%%%s|%%'", tmp, ftd_string_value(groupId));
                    g_free(tmp);
                    tmp = tmp2;
                }

                list = ftd_list_next(list);
            }

            tmp2 = g_strdup_printf("%s)", tmp);
            g_free(tmp);
            tmp = tmp2;

            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            g_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->extra_tag && ftd_string_length(filter->extra_tag) > 0) {
            char *tmp = sqlite3_mprintf("Tag LIKE '%%%q%%'", ftd_string_value(filter->extra_tag));
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            sqlite3_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        if (filter->daySelector) {
            char *tmp = g_strdup_printf("Stamp > %i AND Stamp < %i", filter->start_time, filter->end_time);
            query_tmp = add_clause(query, has_where_keyword, tmp);

            g_free(query);
            g_free(tmp);

            query = query_tmp;
            has_where_keyword = TRUE;
        }

        // TODO: filter velden 'veld', 'asc' en 'categoriseerd'
    } else {
        if (get_count) {
            query = g_strdup_printf("SELECT COUNT(ID) FROM FTDPosts");
        } else {
            query = g_strdup_printf("SELECT ID, Status, FName, Title, Poster, Link, Stamp, Groups, Cat, SCat, Updated, Views, Tags, Tag FROM FTDPosts");
        }
    }

    return query;
}

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;
    char *errmsg = NULL;
    FTDSQLite *sqlite;
    char *query;

    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;
    func_data.eventlist = eventlist;
    func_data.has_filter = filtered_list;

    sqlite = ftd_sqlite_new();

    query = generate_select_query(FALSE, filtered_list, eventlist->filter);

    if (!ftd_sqlite_execute_multi_value_query(sqlite, query, &errmsg, foreach_func, &func_data)) {
        if (errmsg) {
            g_print("%s\n", errmsg);
            g_free(errmsg);
        }
    }

    g_free(query);

    ftd_sqlite_destroy(sqlite);

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

/**
 * 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);
    eventlist->filter = ftd_filter_duplicate(filter);
    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    /* 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) {
        char *errmsg = NULL;
        FtdEvent *event = ftd_eventlist_lookup_id(eventlist, eventlist->filter->postId);
        FtdErrorCode retval;

        if (event) {
            ftd_event_free(event);
            return;
        }

        // FTD id is nog niet bekend bij ons, haal deze op van de FTD server
        retval = ftd_eventinfo_fetch(eventlist->filter->postId, &errmsg);
        if (retval == ERROR_NONE) {
#if 0
            ftd_eventlist_insert_or_replace_event(eventlist, event);
            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);
            }
#endif
        }
    }
}

/**
 * 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. Let op dat in deze lijst de meest recente events als eerste vermeld moeten staan!
 * @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;
    FtdList *listSeenEvents = NULL;
    int len, cur;
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    FTDSQLite *sqlite;

    if (!listNieuw) {
        // Niks toe doen
        if (callback) {
            callback(0, 0, callback_data);
        }

        return;
    }

    // Draai de listNieuw volledig om zodat de allernieuwste
    // events als eerste behandeld worden en we zo makkelijk
    // bij duplicates het oudste element automatisch kunnen negeren
    list = listNieuw;
    len = ftd_list_length(listNieuw);
    cur = 0;

    sqlite = ftd_sqlite_new();
    ftd_sqlite_begin_transaction(sqlite, NULL);

    while (list) {
        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;
        }

        add_or_replace_event(sqlite, event->event);

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

    ftd_sqlite_finish_transaction(sqlite, NULL);
    ftd_sqlite_destroy(sqlite);

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

    ftd_list_free(listSeenEvents);
}

static void
add_or_replace_event(FTDSQLite *sqlite, const FtdEvent *event)
{
    char *query;
    char *errmsg;
    FtdString *groups;
    FtdString *tags;

    groups = ftd_splitted_list_concat(event->groups);
    tags = ftd_splitted_list_concat(event->tags);

    query = sqlite3_mprintf("REPLACE INTO FTDPosts (ID, Status, FName, Title, Poster, Link,"
                            "Stamp, Groups, Cat, SCat, Updated, Views, Tags, Tag) VALUES "
                            "(%i, %i, '%q', '%q', '%q', '%q', "
                            "%i, '%s', %i, %i, %i, %i, '%q', '%q')",
                            event->postId,
                            event->status,
                            ftd_string_value(event->fileName),
                            ftd_string_value(event->title),
                            ftd_string_value(event->poster),
                            ftd_string_value(event->link),
                            event->stamp,
                            ftd_string_value(groups),
                            event->cat,
                            event->sCat,
                            event->updated,
                            event->numViews,
                            ftd_string_value(tags),
                            ftd_string_value(event->extra_tag));

    if (!ftd_sqlite_execute_query(sqlite, query, &errmsg, NULL, NULL)) {
        g_print("%s\n", errmsg);
        g_free(errmsg);
    }

    sqlite3_free(query);

    /* 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;
    }
}

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

    sqlite = ftd_sqlite_new();

    add_or_replace_event(sqlite, event);

    ftd_sqlite_destroy(sqlite);

    // Voeg het event ook gelijk toe aan de eventinfo cache indien er
    // eventinfo specifieke gegevens in staan
    if (event->fileType != FILETYPE_NONE) {
        ftd_event_info_cache_append_eventinfo(core->eventinfo_cache, event);
    }
}

static gboolean
lookup_id_func(gpointer key, gpointer value, gpointer data)
{
    FtdEvent **event_nieuw = data;
    FtdEvent *event = value;

    g_assert(GPOINTER_TO_INT(key) == event->postId);

    *event_nieuw = ftd_event_duplicate(event);

    return TRUE;
}

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

    g_static_rw_lock_writer_lock(&eventlist->rwlock);
    orig_filter = eventlist->filter;
    eventlist->filter = ftd_filter_new_filter();
    eventlist->filter->postId = eventId;
    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    ftd_eventlist_foreach(eventlist, TRUE, TRUE, lookup_id_func, &event);

    g_static_rw_lock_writer_lock(&eventlist->rwlock);
    ftd_filter_free_filter(eventlist->filter);
    eventlist->filter = orig_filter;
    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    return event;
}

void
ftd_eventlist_purge_deleted(FTDEventlist *obj)
{
    FTDSQLite *sqlite;
    char *query;
    char *errmsg;

    /* Verwijder postings met status 'Deleted' die ouder zijn dan 24 uur */
    sqlite = ftd_sqlite_new();
    query = g_strdup_printf("DELETE FROM FTDPosts WHERE ((status = %i AND updated < %i) OR status = %i)", FTDSTATUS_DELETED, time(NULL) - DUUR_DAG * (core->config->verwijderPostsNaXDagen * 7), FTDSTATUS_DELETED2);

    if (!ftd_sqlite_execute_query(sqlite, query, &errmsg, NULL, NULL)) {
        g_print("ftd_eventlist_purge_deleted: %s\n", errmsg);
        g_free(errmsg);
    }

    ftd_sqlite_destroy(sqlite);
}

/**
 * Ruimt de lijst met alle events op.
 */
void
ftd_eventlist_cleanup(FTDEventlist *obj)
{
    FTDEventlist *eventlist = FTD_EVENTLIST(obj);
    int verwijderPostsNaXDagen;
    FTDSQLite *sqlite;
    char *query;
    char *errmsg;

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

    // FIXME: Er wordt hier nog geen rekening gehouden met het feit dat postings
    // nog in de watchlist kunnen staan
    sqlite = ftd_sqlite_new();
    query = g_strdup_printf("DELETE FROM FTDPosts WHERE stamp < %i", time(NULL) - verwijderPostsNaXDagen);

    if (!ftd_sqlite_execute_query(sqlite, query, &errmsg, NULL, NULL)) {
        g_print("ftd_eventlist_cleanup: %s\n", errmsg);
        g_free(errmsg);
    }

    ftd_sqlite_destroy(sqlite);
}

/**
 * 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;
    FTDSQLite *sqlite;
    char *query;
    char *errmsg = NULL;

    g_static_rw_lock_reader_lock(&eventlist->rwlock);

    sqlite = ftd_sqlite_new();

    query = generate_select_query(TRUE, filtered, eventlist->filter);

    if (!ftd_sqlite_execute_single_int_value_query(sqlite, query, &errmsg, &count)) {
        g_print("%s\n", errmsg);
        g_free(errmsg);
        count = 0;
    }

    g_free(query);

    ftd_sqlite_destroy(sqlite);

    g_static_rw_lock_reader_unlock(&eventlist->rwlock);

    return count;
}

static gboolean
get_first_event_func(gpointer key, gpointer value, gpointer user_data)
{
    FtdEvent **ret = user_data;
    FtdEvent *event = value;

    *ret = ftd_event_duplicate(event);

    return TRUE;
}


/**
 * 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);
    FtdEvent *event = NULL;
    FTDSQLite *sqlite;
    char *query, *query_tmp;
    char *errmsg;
    struct _foreach_func_data func_data;

    g_static_rw_lock_writer_lock(&eventlist->rwlock);

    sqlite = ftd_sqlite_new();

    query_tmp = generate_select_query(FALSE, TRUE, eventlist->filter);
    query = g_strdup_printf("%s LIMIT 0,1", query_tmp);
    g_free(query_tmp);

    func_data.eventlist = eventlist;
    func_data.cb = get_first_event_func;
    func_data.data = &event;
    func_data.has_filter = TRUE;

    if (!ftd_sqlite_execute_multi_value_query(sqlite, query, &errmsg, foreach_func, &func_data)) {
        g_print("%s\n", errmsg);
        g_free(errmsg);
    }

    g_free(query);

    ftd_sqlite_destroy(sqlite);

    g_static_rw_lock_writer_unlock(&eventlist->rwlock);

    return event;
}

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)
{
    ftd_event_info_cache_set_active(core->eventinfo_cache, eventId, is_active);
}

