/* ====================================================================
 * 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_filter.h"

typedef struct FTDFilterObjectClass FTDFilterObjectClass;

FTDFilterObject *ftd_filter_get_object(void);
GType ftd_filter_object_get_type (void);

struct FTDFilterObjectClass
{
    GObjectClass parent;
};

enum
{
  BRING_TO_FRONT_SIGNAL,
  OPEN_POSTING_SIGNAL,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(FTDFilterObject, ftd_filter_object, G_TYPE_OBJECT)

static void
ftd_filter_object_init (FTDFilterObject *obj)
{
    g_static_rw_lock_init(&obj->rwlock);

    obj->filter = ftd_filter_new_filter();
}

static void
filter_finalize(GObject *obj)
{
    FTDFilterObject *filter = FTD_FILTER_OBJECT(obj);

    g_static_rw_lock_writer_lock(&filter->rwlock);

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

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

static void
ftd_filter_object_class_init (FTDFilterObjectClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = filter_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
}

FTDFilterObject *
ftd_filter_object_new(void)
{
    return g_object_new(FTD_FILTER_TYPE_OBJECT, NULL);
}

void
ftd_filter_object_destroy(FTDFilterObject *obj)
{
    g_object_unref(obj);
}

/**
 * Maakt een nieuw filter aan.
 *
 * @return  Hte nieuwe filter.
 */
FtdFilter *
ftd_filter_new_filter(void)
{
    FtdFilter *res = g_malloc0(sizeof(FtdFilter));

    res->cat = -1;
    res->sCat = -1;

    return res;
}

/**
 * Geeft een filter vrij.
 *
 * @arg     filter      Het vrij te geven filter.
 */
void
ftd_filter_free_filter(FtdFilter * filter)
{
    g_assert( filter );

    if( filter->filterNaam ) {
        ftd_string_free( filter->filterNaam );
    }

    if( filter->title ) {
        ftd_string_free( filter->title );
    }

    if( filter->poster ) {
        ftd_string_free( filter->poster );
    }

    if( filter->tags ) {
        FtdList *list = filter->tags;

        while( list ) {
            FtdString *tag = ftd_list_value( list );
            g_assert( tag );
            ftd_string_free( tag );
            list = ftd_list_next( list );
        }

        ftd_list_free( filter->tags );
    }

    if( filter->extra_tag ) {
        ftd_string_free( filter->extra_tag );
    }

    if( filter->groupFilterList ) {
        FtdList *list = filter->groupFilterList;

        while( list ) {
            FtdString *groupID = ftd_list_value( list );
            g_assert( groupID );
            ftd_string_free( groupID );
            list = ftd_list_next( list );
        }

        ftd_list_free( filter->groupFilterList );
    }

    if ( filter->statusList ) {
        ftd_list_free(filter->statusList);
    }

    g_free( filter );
}

/**
 * Geeft het in effect zijnde filter terug.
 * Geef het resultaat niet vrij!
 *
 * @return  Het in effect zijnde filter.
 */
FtdFilter *
ftd_filter_object_get_filter(FTDFilterObject *obj)
{
    FTDFilterObject *filter_obj = FTD_FILTER_OBJECT(obj);
    FtdFilter *filter;

    g_static_rw_lock_reader_lock(&filter_obj->rwlock);

    filter = ftd_filter_duplicate(filter_obj->filter);

    g_static_rw_lock_reader_unlock(&filter_obj->rwlock);

    return filter;
}

/**
 * Stel een nieuw filter in
 *
 * @arg  Het nieuwe filter. Een eventueel oud ingesteld filter zal vrijgegeven worden
*/
void
ftd_filter_object_set_filter(FTDFilterObject *obj, const FtdFilter *filter)
{
    FTDFilterObject *filter_obj = FTD_FILTER_OBJECT(obj);

    g_static_rw_lock_writer_lock(&filter_obj->rwlock);

    if (filter_obj->filter) {
        ftd_filter_free_filter(filter_obj->filter);
    }

    filter_obj->filter = ftd_filter_duplicate(filter);

    g_static_rw_lock_writer_unlock(&filter_obj->rwlock);
}

int
ftd_filter_object_get_filter_postid(FTDFilterObject *obj)
{
    FTDFilterObject *filter_obj = FTD_FILTER_OBJECT(obj);
    int postid;

    g_static_rw_lock_reader_lock(&filter_obj->rwlock);

    postid = filter_obj->filter->postId;

    g_static_rw_lock_reader_unlock(&filter_obj->rwlock);

    return postid;
}

/**
 * Kopieer het huidig geladen filter
 *
 * @return  Een kopie van het actieve filter
*/
FtdFilter *
ftd_filter_duplicate(const FtdFilter *orig_filter)
{
    FtdFilter *filter = ftd_filter_new_filter();
    memcpy(filter, orig_filter, sizeof(FtdFilter));

    if (filter->filterNaam) {
        filter->filterNaam = ftd_string_new(ftd_string_value(orig_filter->filterNaam));
    }

    if (filter->title) {
        filter->title = ftd_string_new(ftd_string_value(orig_filter->title));
    }

    if (filter->poster) {
        filter->poster = ftd_string_new(ftd_string_value(orig_filter->poster));
    }

    if (filter->tags) {
        FtdList *list = filter->tags;
        filter->tags = NULL;

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

            filter->tags = ftd_list_append(filter->tags, ftd_string_new(ftd_string_value(tag)));

            list = ftd_list_next(list);
        }
    }

    if (filter->extra_tag) {
        filter->extra_tag = ftd_string_new(ftd_string_value(orig_filter->extra_tag));
    }

    if (filter->groupFilterList) {
        filter->groupFilterList = ftd_splitted_list_duplicate(ftd_config_get()->groupFilterList);
    }

    if (filter->statusList) {
        filter->statusList = ftd_list_copy(filter->statusList);
    }

    return filter;
}

gboolean
ftd_filter_object_accepts(FTDFilterObject *obj, const FtdEvent *event, gboolean use_global_groups)
{
    FTDFilterObject *filter_obj = FTD_FILTER_OBJECT(obj);
    gboolean retval;

    g_static_rw_lock_reader_lock(&filter_obj->rwlock);

    retval = ftd_filter_accepts(filter_obj->filter, event, use_global_groups);

    g_static_rw_lock_reader_unlock(&filter_obj->rwlock);

    return retval;
}

/**
 * Geeft aan of een event door een filter geaccepteerd wordt.
 * De globale groepenlijst kan voor deze functie gebruikt worden om een duidelijke verbetering in performance te maken.
 * Hiertoe dient wel voor het aanroepen van deze functie ::ftd_group_set_group_id_list aangeroepen worden.
 *
 * @arg     filter              Het filter waartegen getest wordt.
 * @arg     event               Het event wat gefilterd moet worden.
 * @arg     use_global_groups   True indien de globale groepenlijst gebruikt moet worden.
 *
 * @return  True indien het event door het gegeven filter geaccepteerd wordt.
 */
gboolean
ftd_filter_accepts(const FtdFilter *filter, const FtdEvent *event, gboolean use_global_groups)
{
    gboolean foundSCat;
    FtdList *list;

    if (filter->prevCheck != 0 && !filter->daySelector) {
        if (event->stamp < filter->prevCheck) {
            return FALSE;
        }
    }

    if (filter->postId != 0) {
        if (event->postId == filter->postId) {
            return TRUE;
        }

        return FALSE;
    }

    if (filter->cat != -1) {
        if (event->cat != filter->cat) {
            return FALSE;
        } else {
            if (filter->sCat != -1) {
                if (event->sCat != filter->sCat) {
                    return FALSE;
                }
            }
        }
    }

    if (filter->statusList) {
        gboolean ok = FALSE;

        FtdList *list = filter->statusList;
        while (list) {
            int status = GPOINTER_TO_INT(ftd_list_value(list));

            if (status == event->status) {
                ok = TRUE;
            }

            list = ftd_list_next(list);
        }

        if (!ok) {
            return FALSE;
        }
    }

    if (filter->groupFilterList) {
        if(use_global_groups) {
            FtdList *grouplist = event->groups;
            gboolean is_in_list = FALSE;
            while (grouplist) {
                FtdString *groupId = ftd_list_value(grouplist);
                if (ftd_group_id_is_in_list(atoi(ftd_string_value(groupId)))) {
                    is_in_list = TRUE;
                    break;
                }
                grouplist = ftd_list_next(grouplist);
            }

            if (!is_in_list) {
                return FALSE;
            }
        } else {
            gboolean is_in_list;
            FtdList *grouplist = event->groups;
            int listlen = ftd_list_length(grouplist);
            int *groupids = g_malloc0(sizeof(int) * listlen );
            int counter = 0;
            while (grouplist) {
                FtdString *groupId = ftd_list_value(grouplist);
                groupids[counter++] = atoi(ftd_string_value(groupId));
                grouplist = ftd_list_next(grouplist);
            }

            grouplist = filter->groupFilterList;
            is_in_list = FALSE;

            while (grouplist) {
                int groupId = atoi(ftd_string_value(ftd_list_value(grouplist)));
                for (counter = 0; counter < listlen; counter++) {
                    if (groupids[counter] == groupId) {
                        is_in_list = TRUE;
                        break;
                    }
                }

                if(is_in_list) {
                    break;
                }

                grouplist = ftd_list_next(grouplist);
            }
            g_free(groupids);

            if (!is_in_list) {
                return FALSE;
            }
        }
    }

    if (filter->title != NULL) {
        char *upEventTitle = ftd_string_upcase(ftd_string_value(event->title));
        char *upTitle = ftd_string_upcase(ftd_string_value(filter->title)); // FIXME: Dit MOET te optimaliseren zijn

        if (strstr(upEventTitle, upTitle) == NULL) {
            g_free (upEventTitle);
            g_free (upTitle);
            return FALSE;
        }
        g_free (upEventTitle);
        g_free (upTitle);
    }

    if (filter->poster != NULL) {
        char *upEventPoster = ftd_string_upcase(ftd_string_value(event->poster));
        char *upPoster = ftd_string_upcase(ftd_string_value(filter->poster)); // FIXME: Dit MOET te optimaliseren zijn

        if (strstr(upEventPoster, upPoster) == NULL) {
            g_free (upEventPoster);
            g_free (upPoster);
            return FALSE;
        }
        g_free (upEventPoster);
        g_free (upPoster);
    }

    if (filter->daySelector) {
        if (event->stamp < filter->start_time || event->stamp > filter->end_time) {
            return FALSE;
        }
    }

    // Kijk of alle tags waarop gefilterd wordt ook voorkomen bij de post
    if(filter->tags) {
        FtdList *taglist1 = filter->tags;
        gboolean aOK = TRUE;
        gboolean bOK = TRUE;
        gboolean cOK = TRUE;
        gboolean dOK = TRUE;
        // Even kijken welke typen tags we op filteren
        while (taglist1) {
            switch (ftd_string_value(ftd_list_value(taglist1))[0]) {
                case 'a' :
                    aOK = FALSE;
                    break;
                case 'b' :
                    bOK = FALSE;
                    break;
                case 'c' :
                    cOK = FALSE;
                    break;
                case 'd' :
                    dOK = FALSE;
                    break;
            }
            taglist1 = ftd_list_next(taglist1);
        }

        taglist1 = filter->tags;
        while (taglist1) {
            FtdString *tag = ftd_list_value(taglist1);
            FtdList *taglist2 = event->tags;
            gboolean is_in_list = FALSE;
            while (taglist2) {
                FtdString *tag2 = ftd_list_value(taglist2);
                if (tag2) {
                    if (!strcmp(ftd_string_value(tag), ftd_string_value(tag2))) {
                        switch (ftd_string_value(tag)[0]) {
                            case 'a' :
                                if (!aOK) {
                                    aOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                            case 'b' :
                                if (!bOK) {
                                    bOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                            case 'c' :
                                if (!cOK) {
                                    cOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                            case 'd' :
                                if (!dOK) {
                                    dOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                        }
                        break;
                    }
                }
                taglist2 = ftd_list_next (taglist2);
            }

            if (is_in_list && aOK && bOK && cOK && dOK) {
                break;
            }

            taglist1 = ftd_list_next(taglist1);
        }
        if (!(aOK && bOK && cOK && dOK)) {
            return FALSE;
        }
    }

    if (filter->extra_tag) {
        char *tmp;
        char *upEventTag;
        char *upTag;

        if (!event->extra_tag) {
            return FALSE;
        }

        // Voeg achter beide strings een spatie, zodat er gezocht kan worden op hele woorden
        tmp = ftd_string_upcase(ftd_string_value(event->extra_tag));
        upEventTag = g_strdup_printf(" %s ", tmp);
        g_free(tmp);

        tmp = ftd_string_upcase(ftd_string_value(filter->extra_tag));
        upTag = g_strdup_printf(" %s ", tmp);
        g_free(tmp);

        if (strstr(upEventTag, upTag) == NULL) {
            g_free (upEventTag);
            g_free (upTag);
            return FALSE;
        }

        g_free (upEventTag);
        g_free (upTag);
    }

    // Controleer of er in de opgegeven categorie wel filters beschikbaar zijn
    list = core->categories;
    foundSCat = FALSE;
    while (list) {
        FtdCategory *cat = ftd_list_value(list);

        if (cat->header == FALSE && cat->cat == event->cat) {
            foundSCat = TRUE;
        }

        list = ftd_list_next(list);
    }

    if (!foundSCat) {
        return FALSE;
    }

    return TRUE;
}

/**
 * Geeft aan of een event door een in een filter ingestelde tags geaccepteerd wordt.
 *
 * @arg     filter              Het filter waartegen getest wordt.
 * @arg     event               Het event wat gefilterd moet worden.
 *
 * @return  True indien het event door het gegeven filter geaccepteerd wordt.
 */
gboolean
ftd_filter_accepts_tags(const FtdFilter *filter, const FtdEvent *event)
{
    // Kijk of alle tags waarop gefilterd wordt ook voorkomen bij de post
    if(filter->tags) {
        FtdList *taglist1 = filter->tags;
        gboolean aOK = TRUE;
        gboolean bOK = TRUE;
        gboolean cOK = TRUE;
        gboolean dOK = TRUE;
        // Even kijken welke typen tags we op filteren
        while (taglist1) {
            switch (ftd_string_value(ftd_list_value(taglist1))[0]) {
                case 'a' :
                    aOK = FALSE;
                    break;
                case 'b' :
                    bOK = FALSE;
                    break;
                case 'c' :
                    cOK = FALSE;
                    break;
                case 'd' :
                    dOK = FALSE;
                    break;
            }
            taglist1 = ftd_list_next(taglist1);
        }

        taglist1 = filter->tags;
        while (taglist1) {
            FtdString *tag = ftd_list_value(taglist1);
            FtdList *taglist2 = event->tags;
            gboolean is_in_list = FALSE;
            while (taglist2) {
                FtdString *tag2 = ftd_list_value(taglist2);
                if (tag2) {
                    if (!strcmp(ftd_string_value(tag), ftd_string_value(tag2))) {
                        switch (ftd_string_value(tag)[0]) {
                            case 'a' :
                                if (!aOK) {
                                    aOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                            case 'b' :
                                if (!bOK) {
                                    bOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                            case 'c' :
                                if (!cOK) {
                                    cOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                            case 'd' :
                                if (!dOK) {
                                    dOK = TRUE;
                                    is_in_list = TRUE;
                                }
                                break;
                        }
                        break;
                    }
                }
                taglist2 = ftd_list_next (taglist2);
            }

            if (is_in_list && aOK && bOK && cOK && dOK) {
                break;
            }

            taglist1 = ftd_list_next(taglist1);
        }
        if (!(aOK && bOK && cOK && dOK)) {
            return FALSE;
        }
    }

    return TRUE;
}
