/* ====================================================================
 * 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 "ftd4linux.h"
#include "ftd_app_core.h"
#include "ftd_config.h"
#include "ftd_category.h"
#include "ftd_event.h"

/**
 * Een eventuele foutmelding van de XML-parser.
 */
static char *xml_parser_errmsg = NULL;

/**
 * De callback-functie voor als er iets fout gaat met het parsen van de XML.
 */
void
xml_parser_error_func (void *ctx, const char *msg, ...)
{
    va_list args;
    char *orig = xml_parser_errmsg;
    char *tmp;

    va_start(args, msg);

#ifdef __CEGCC__
    // Onder CeGCC kan er op dit punt met de originele code die verder hieronder staat
    // een stack overflow optreden.. Moet nog verder onderzocht worden.. Werk er voorlopig
    // omheen door de foutmelding direct naar de stdio te schrijven.
    vprintf(msg, args);
#else
    tmp = g_strdup_vprintf(msg, args);
    if (orig) {
        xml_parser_errmsg = g_strdup_printf("%s %s", orig, tmp);
        g_free(orig);
        g_free(tmp);
    } else {
        xml_parser_errmsg = tmp;
    }
#endif

    va_end(args);
}

/**
 * Leest alle events uit de buffer uit.
 *
 * @arg     buffer      De XML-gecodeerde events die uitgelezen moeten worden.
 * @arg     ret         Hierin wordt de resulterende lijst met ::FtdEvent pointers naar de uitgelezen events gestops.
 * @arg     errmsg      Hierin wordt een eventueel foutbericht gestopt.
 *
 * @return  Een fout-code die aangeeft of het uitlezen van de events succesvol was.
 */
FtdErrorCode
ftd_events_parse(FtdString *buffer, FtdTree **ret_tree, FtdList **ret_list, char **errmsg, gboolean ignore_errors)
{
    xmlDocPtr    doc;
    xmlNodePtr   cur;
    FtdEvent    *event = NULL;
    static gboolean error_handler_initialised = FALSE;

    g_assert(core);
    g_assert(buffer);

    if (errmsg) {
        *errmsg = NULL;
    }

    if (!ret_tree && !ret_list) {
        g_error("ret_tree == NULL && ret_list == NULL");
    }

    if (ret_tree && ret_list) {
        g_error("ret_tree != NULL && ret_list != NULL");
    }

    if (ret_tree) {
        g_assert(*ret_tree);
    }

    /* Initialiseer de xml parser error handler indien dit nog niet gebeurd is */
    if (!error_handler_initialised) {
        xmlSetGenericErrorFunc(NULL, xml_parser_error_func);
        error_handler_initialised = TRUE;
    }

    if (xml_parser_errmsg) {
        g_free(xml_parser_errmsg);
        xml_parser_errmsg = NULL;
    }

    doc = xmlParseMemory(ftd_string_value(buffer), ftd_string_length(buffer));
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        if (errmsg) {
            *errmsg = g_strdup(xml_parser_errmsg);
        }

        g_free( xml_parser_errmsg );
        xml_parser_errmsg = NULL;
        if (!ignore_errors) {
            g_print("ftd_events_parse(): buffer = \n%s\n", ftd_string_value(buffer));
        }

        xmlFreeDoc(doc);
        return ERROR_PARSE;
    }

    if (strcasecmp(cur->name, "Events")) {
        if (errmsg) {
            *errmsg = g_strdup("ftd_events_parse(): document of the wrong type, root node != Events");
        }

        if (!ignore_errors) {
            g_print("ftd_events_parse(): buffer = \n%s\n", ftd_string_value(buffer));
        }

        xmlFreeDoc(doc);
        return ERROR_PARSE;
    }

    cur = cur->xmlChildrenNode;
    if (!cur) {
        if (errmsg) {
            *errmsg = g_strdup("ftd_events_parse(): no children of root-node!");
        }

        if (!ignore_errors) {
            g_print("ftd_events_parse(): buffer = \n%s\n", ftd_string_value(buffer));
        }

        xmlFreeDoc(doc);
        return ERROR_PARSE;
    }

    do {
        if (!strcasecmp(cur->name, "Event")) {
            char *titleUpcase = NULL;
            char *badwordUpcase = NULL;
            gboolean accepted = TRUE;

            event = ftd_events_parse_event(doc, cur->xmlChildrenNode);

            if (event->status != FTDSTATUS_DELETED && event->status != FTDSTATUS_DELETED2) {   // in dit geval hebben events geen title
                /* Pas een eventueel filter toe */
                FtdList *badwordsList;

                badwordsList = core->config->badwords;
                if (badwordsList) {
                    titleUpcase = ftd_string_upcase(ftd_string_value(event->title));
                    do {
                        FtdString *badword = ftd_list_value(badwordsList);
                        g_assert(badword);
                        badwordUpcase = ftd_string_upcase(ftd_string_value(badword));
                        if (strstr(titleUpcase, badwordUpcase)) {
                            /* badword gevonden! skippen die zooi */
                            ftd_event_free(event);
                            accepted = FALSE;
                            g_free(badwordUpcase);
                            /* Event bestaat niet meer, dus niet verder checken op badwords */
                            break;
                        }
                        g_free(badwordUpcase);
                    } while ((badwordsList = ftd_list_next(badwordsList)));
                    g_free(titleUpcase);
                }
            }

            if (accepted) {
                FTDEventObject *eventObj = ftd_event_object_new();
                ftd_event_object_set_event(eventObj, event);

                if (ret_tree) {
                    ftd_tree_insert(*ret_tree, GINT_TO_POINTER(event->postId), eventObj);
                } else {
                    *ret_list = ftd_list_prepend(*ret_list, eventObj);
                }
            }

            ftd_event_free(event);
        }
    } while ((cur=cur->next));

    xmlFreeDoc(doc);

    return ERROR_NONE;
}

/**
 * Leest speciale events uit de buffer.
 *
 * Deze methode leest speciale events, zoals die van de FTD server worden ontvangen.
 * Voorbeelden hiervan zijn een event type 4: een nieuwsgroep of een event type 7, waarin een update-bestandsnaam staat.
 * Deze speciale events worden in de core verwerkt en de gewone events worden als ::FtdEvent in ret gestopt.
 *
 * @arg     buffer      De XML-gecodeerde events die uitgelezen moeten worden.
 * @arg     lastCheck   Hierin wordt de timestamp van de huidige lijst gestopt, zodat deze gebruikt kan worden als 'nieuwe' laatste check.
 * @arg     ret         Hierin wordt de resulterende lijst met ::FtdEvent pointers naar de uitgelezen events gestops.
 * @arg     errmsg      Hierin wordt een eventueel foutbericht gestopt.
 *
 * @return  Een fout-code die aangeeft of het uitlezen van de events succesvol was.
 */
FtdErrorCode
ftd_events_list_parse(FtdString *buffer, time_t *lastCheck, FtdList **ret, char **errmsg)
{
    xmlDocPtr    doc;
    xmlNodePtr   cur;
    int          numcode;
    static gboolean error_handler_initialised = FALSE;

    g_assert(core);
    g_assert(buffer);

    *errmsg = NULL;
    // ret moet hier NIET op NULL geinitialiseerd worden omdat bij
    // de recursieve update dit gedrag juist niet gewenst is

    /* Initialiseer de xml parser error handler indien dit nog niet gebeurd is */
    if (!error_handler_initialised) {
        xmlSetGenericErrorFunc(NULL, xml_parser_error_func);
        error_handler_initialised = TRUE;
    }

    if (xml_parser_errmsg) {
        g_free(xml_parser_errmsg);
        xml_parser_errmsg = NULL;
    }

    doc = xmlParseMemory(ftd_string_value(buffer), ftd_string_length(buffer));
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        *errmsg = g_strdup(xml_parser_errmsg);
        g_free( xml_parser_errmsg );
        xml_parser_errmsg = NULL;
        xmlFreeDoc(doc);
        return ERROR_PARSE;
    }

    if (strcmp(cur->name, "Events")) {
        *errmsg = g_strdup("ftd_events_list_parse(): document of the wrong type, root node != Events");
        xmlFreeDoc(doc);
        return ERROR_PARSE;
    }

    cur = cur->xmlChildrenNode;
    if (!cur) {
        *errmsg = g_strdup("ftd_events_list_parse(): no children of root-node!");
        xmlFreeDoc(doc);
        return ERROR_PARSE;
    }

    do {
        if (!strcmp(cur->name, "Event")) {
            xmlChar *code;

            code = xmlGetProp(cur, "Code");
            if (code == NULL) {
                continue;
            }

            numcode = atoi(code);
            xmlFree(code);

            if (numcode == 0) {
                xmlNodePtr curNode = cur->xmlChildrenNode;

                while (curNode != NULL) {
                    if (!strcmp(curNode->name, "Stamp") && lastCheck) {
                        *lastCheck = ftd_xml_parse_int(doc, curNode);
                    }
                    curNode = curNode->next;
                }
            } else if (numcode == 4) {
                int groupID = 0;
                FtdString *name = NULL;
                xmlNodePtr curNode = cur->xmlChildrenNode;

                while (curNode != NULL) {
                    if (!strcmp(curNode->name, "GroupID")) {
                        groupID = ftd_xml_parse_int(doc, curNode);
                    } else if (!strcmp(curNode->name, "Name")) {
                        name = ftd_xml_parse_string(doc, curNode);
                    }
                    curNode = curNode->next;
                }

                if ((groupID == 0) || (name == NULL)) {
                    *errmsg = g_strdup("ftd_events_parse(): groupID == 0 || name == NULL\n");
                    xmlFreeDoc(doc);
                    return ERROR_PARSE;
                } else {
                    if (!ftd_group_by_id(groupID)) {
                        FtdGroup *group = ftd_group_new();
                        group->groupId = groupID;
                        group->groupName = name;
                        core->groups = ftd_list_append(core->groups, group);
                    }
                }
            } else if (numcode == 5) {
                xmlNodePtr curNode = cur->xmlChildrenNode;

                while (curNode != NULL) {
                    if (!strcmp(curNode->name, "T")) {
                        core->config->ledenCount = ftd_xml_parse_int(doc, curNode);
                    } else if (!strcmp(curNode->name, "N")) {
                        core->config->numUsersOnline = ftd_xml_parse_string(doc, curNode);
                    }
                    curNode = curNode->next;
                }
            } else if (numcode == 7) {
                xmlNodePtr curNode = cur->xmlChildrenNode;

                while (curNode != NULL) {
                    if (!strcmp(curNode->name, "Chunks")) {
                        FtdString *tmp = ftd_xml_parse_string(doc, curNode);
                        char **values = g_strsplit(ftd_string_value(tmp), "|", 0);
                        int i = 0;
                        while (values[i]) {
                            *ret = ftd_list_append(*ret, g_strdup(values[i]));
                            i++;
                        }
                        g_strfreev(values);
                        ftd_string_free(tmp);
                    }
                    curNode = curNode->next;
                }
            } else {
                *errmsg = g_strdup_printf("ftd_events_parse: Unknown numcode %d", numcode);
                xmlFreeDoc(doc);
                return ERROR_PARSE;
            }
        }
    } while ((cur=cur->next));

    xmlFreeDoc(doc);

    return ERROR_NONE;
}

/**
 * Leest een enkele event uit een XML-boom.
 *
 * @arg     doc         Het XML-document met daarin het event.
 * @arg     cur         Een pointer naar de huidige node uit doc waar het event uitgelezen moet worden.
 *
 * @return  Een nieuw FTD-event met de gegevens uit doc ingevuld.
 */
FtdEvent *
ftd_events_parse_event(xmlDocPtr doc, xmlNodePtr cur)
{
    FtdEvent *event = ftd_event_new();

    while(cur != NULL) {
        if (!strcmp(cur->name, "Stamp")) {
            event->stamp = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "PostID")) {
            event->postId = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "Status")) {
            event->status = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "FName")) {
            event->fileName = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "Title")) {
            event->title = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "Groups")) {
            FtdString *tmp = ftd_xml_parse_string(doc, cur);
            if( event->groups ) {
                g_warning( "WARNING! event->groups was already filled!" );
                ftd_splitted_list_free( &event->groups );
            }
            event->groups = ftd_splitted_list_split(ftd_string_value(tmp));
            ftd_string_free(tmp);
        } else if (!strcmp(cur->name, "Poster")) {
            event->poster = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "Link")) {
            event->link   = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "Cat")) {
            event->cat    = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "SCat")) {
            event->sCat   = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "Updated")) {
            event->updated = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "Rating")) {
            event->rating = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "Tags")) {
            FtdString *tmp = ftd_xml_parse_string(doc, cur);
            if( event->tags ) {
                g_warning( "WARNING! event->tags was already filled!" );
                ftd_splitted_list_free( &event->tags );
            }
            event->tags = ftd_splitted_list_split(ftd_string_value(tmp));
            ftd_string_free(tmp);
        } else if (!strcmp(cur->name, "Tag")) {
            event->extra_tag = ftd_xml_parse_string(doc, cur);
        }
        /* Hier onder volgen enkele velden die bij de event info horen */
        else if (!strcmp(cur->name, "Ann")) {
            event->announcement = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "Des")) {
            event->description = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "Sig")) {
            event->sig = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "FType")) {
            event->fileType = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "Files")) {
            event->aantalFiles = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "FStart")) {
            event->fileStart = ftd_xml_parse_int(doc, cur);
        } else if (!strcmp(cur->name, "Img")) {
            event->image = ftd_xml_parse_string(doc, cur);
        } else if (!strcmp(cur->name, "MP3")) {
            event->mp3Tracks = ftd_events_parse_mp3_tracks(doc, cur->xmlChildrenNode);
        }

        cur = cur->next;
    }

    // Fix detection of the sCat in case its not given in the data, or -1 in the data
    if( event->sCat == -1 && event->tags ) {
        FtdList * tags = event->tags;
        while( tags ) {
            char * tag = ftd_string_value( ftd_list_value( tags ) );
            if( *tag == 'a' && strlen( tag ) ) {
                event->sCat = atoi( tag+1 );
                break;
            }
            tags = ftd_list_next( tags );
        }
    }

    return event;
}

/**
 * Leest de namen van de mp3-bestanden uit een XML-boom.
 *
 * @arg     doc         Het XML-document met daarin de bestandsnamen.
 * @arg     cur         Een pointer naar de huidige node uit doc waar de bestandsnamen uitgelezen moeten worden.
 *
 * @return  Een lijst met ::FtdString pointers met daarin de namen van de mp3 bestanden.
 */
FtdList *
ftd_events_parse_mp3_tracks(xmlDocPtr doc, xmlNodePtr cur)
{
    FtdList *list = NULL;

    while(cur != NULL) {
        if (!strcmp(cur->name, "Track")) {
            list = ftd_list_append(list, ftd_xml_parse_string(doc, cur));
        }
        cur = cur->next;
    }

    return list;
}
