/* createrepo_c - Library of routines for manipulation with repodata
 * Copyright (C) 2013  Tomas Mlcoch
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 */

#include <glib.h>
#include <glib/gprintf.h>
#include <assert.h>
#include <errno.h>
#include "error.h"
#include "xml_parser.h"
#include "xml_parser_internal.h"
#include "misc.h"

#define ERR_DOMAIN      CREATEREPO_C_ERROR


cr_ParserData *
cr_xml_parser_data(unsigned int numstates)
{
    cr_ParserData *pd = g_new0(cr_ParserData, 1);
    pd->content = g_malloc(CONTENT_REALLOC_STEP);
    pd->acontent = CONTENT_REALLOC_STEP;
    pd->swtab = g_malloc0(sizeof(cr_StatesSwitch *) * numstates);
    pd->sbtab = g_malloc(sizeof(unsigned int) * numstates);

    return pd;
}

void
cr_xml_parser_data_free(cr_ParserData *pd)
{
    if (!pd) {
        return;
    }
    if (pd->parser) {
        xmlFreeParserCtxt(pd->parser);
    }
    g_free(pd->content);
    g_free(pd->swtab);
    g_free(pd->sbtab);
    g_free(pd);
}

void
cr_char_handler(void *pdata, const xmlChar *s, int len)
{
    int l;
    char *c;
    cr_ParserData *pd = pdata;

    if (pd->err)
        return; /* There was an error -> do nothing */

    if (!pd->docontent)
        return; /* Do not store the content */

    l = pd->lcontent + len + 1;
    if (l > pd->acontent) {
        pd->acontent = l + CONTENT_REALLOC_STEP;
        pd->content = realloc(pd->content, pd->acontent);
    }

    c = pd->content + pd->lcontent;
    pd->lcontent += len;
    while (len-- > 0)
        *c++ = *s++;
    *c = '\0';
}

int
cr_xml_parser_warning(cr_ParserData *pd,
                      cr_XmlParserWarningType type,
                      const char *msg,
                      ...)
{
    int ret;
    va_list args;
    gchar *warn;
    GError *tmp_err;

    assert(pd);
    assert(msg);

    if (!pd->warningcb)
        return CR_CB_RET_OK;

    va_start(args, msg);
    g_vasprintf(&warn, msg, args);
    va_end(args);

    tmp_err = NULL;
    ret = pd->warningcb(type, warn, pd->warningcb_data, &tmp_err);
    g_free(warn);
    if (ret != CR_CB_RET_OK) {
        if (tmp_err)
            g_propagate_prefixed_error(&pd->err, tmp_err,
                                       "Parsing interrupted: ");
        else
            g_set_error(&pd->err, ERR_DOMAIN, CRE_CBINTERRUPTED,
                        "Parsing interrupted");
    }


    assert(pd->err || ret == CR_CB_RET_OK);

    return ret;
}

gint64
cr_xml_parser_strtoll(cr_ParserData *pd,
                      const char *nptr,
                      unsigned int base)
{
    gint64 val;
    char *endptr = NULL;

    assert(pd);
    assert(base <= 36 && base != 1);

    if (!nptr)
        return 0;

    val = g_ascii_strtoll(nptr, &endptr, base);

    if ((val == G_MAXINT64 || val == G_MININT64) && errno == ERANGE)
        cr_xml_parser_warning(pd, CR_XML_WARNING_BADATTRVAL,
                "Correct integer value \"%s\" caused overflow", nptr);
    else if (val == 0 && *endptr != '\0')
        cr_xml_parser_warning(pd, CR_XML_WARNING_BADATTRVAL,
                "Conversion of \"%s\" to integer failed", nptr);

    return val;
}

int
cr_newpkgcb(cr_Package **pkg,
            G_GNUC_UNUSED const char *pkgId,
            G_GNUC_UNUSED const char *name,
            G_GNUC_UNUSED const char *arch,
            G_GNUC_UNUSED void *cbdata,
            GError **err)
{
    assert(pkg && *pkg == NULL);
    assert(!err || *err == NULL);

    *pkg = cr_package_new();

    return CR_CB_RET_OK;
}

int
cr_xml_parser_generic(xmlParserCtxtPtr parser,
                      cr_ParserData *pd,
                      const char *path,
                      GError **err)
{
    /* Note: This function uses .err members of cr_ParserData! */

    int ret = CRE_OK;
    CR_FILE *f;
    GError *tmp_err = NULL;
    char buf[XML_BUFFER_SIZE];

    assert(parser);
    assert(pd);
    assert(path);
    assert(!err || *err == NULL);

    f = cr_open(path, CR_CW_MODE_READ, CR_CW_AUTO_DETECT_COMPRESSION, &tmp_err);
    if (tmp_err) {
        int code = tmp_err->code;
        g_propagate_prefixed_error(err, tmp_err, "Cannot open %s: ", path);
        return code;
    }

    while (1) {
        int len;
        len = cr_read(f, buf, XML_BUFFER_SIZE, &tmp_err);
        if (tmp_err) {
            ret = tmp_err->code;
            g_critical("%s: Error while reading xml '%s': %s",
                       __func__, path, tmp_err->message);
            g_propagate_prefixed_error(err, tmp_err, "Read error: ");
            break;
        }

        if (xmlParseChunk(parser, buf, len, len == 0)) {
            ret = CRE_XMLPARSER;
            const xmlError *xml_err = xmlCtxtGetLastError(parser);
            g_critical("%s: parsing error '%s': %s",
                       __func__,
                       path,
                       xml_err->message);
            g_set_error(err, ERR_DOMAIN, CRE_XMLPARSER,
                        "Parse error '%s' at line: %d (%s)",
                        path,
                        (int) xml_err->line,
                        (char *) xml_err->message);
            break;
        }

        if (pd->err) {
            ret = pd->err->code;
            g_propagate_error(err, pd->err);
            break;
        }

        if (len == 0)
            break;
    }

    if (ret != CRE_OK) {
        // An error already encoutentered
        // just close the file without error checking
        cr_close(f, NULL);
    } else {
        // No error encountered yet
        cr_close(f, &tmp_err);
        if (tmp_err) {
            ret = tmp_err->code;
            g_propagate_prefixed_error(err, tmp_err, "Error while closing: ");
        }
    }

    return ret;
}

int
cr_xml_parser_generic_from_string(xmlParserCtxtPtr parser,
                                  cr_ParserData *pd,
                                  const char *xml_string,
                                  GError **err)
{
    /* Note: This function uses .err members of cr_ParserData! */

    int ret = CRE_OK;
    int block_size = XML_BUFFER_SIZE;
    const char *next_data = xml_string;
    const char *end_of_string = xml_string + strlen(xml_string);
    int finished = 0;

    assert(parser);
    assert(pd);
    assert(xml_string);
    assert(!err || *err == NULL);

    const char *data;
    while (!finished) {
        data = next_data;

        // Check if we are in the last loop
        next_data = data + block_size;
        if (next_data > end_of_string) {
            block_size = strlen(data);
            finished = 1;
        }

        if (xmlParseChunk(parser, data, block_size, finished)) {
            ret = CRE_XMLPARSER;
            const xmlError *xml_err = xmlCtxtGetLastError(parser);
            g_critical("%s: parsing error '%s': %s",
                       __func__,
                       data,
                       xml_err->message);
            g_set_error(err, ERR_DOMAIN, CRE_XMLPARSER,
                        "Parse error '%s' at line: %d (%s)",
                        data,
                        (int) xml_err->line,
                        (char *) xml_err->message);
        }

        if (pd->err) {
            ret = pd->err->code;
            g_propagate_error(err, pd->err);
        }
    }

    return ret;
}

const xmlChar **
unescape_ampersand_from_values(const xmlChar **attr, gboolean *allocation_needed) {
    *allocation_needed = FALSE;

    if (!attr) {
        return attr;
    }

    // In the vast majority of cases there is no '&' in the
    // data so check if there is any first and if not return
    // quickly.
    //
    // attr consists of key1, value1, key2, value2 pairs,
    // we know which keys we want (they don't contain &) so
    // we don't have to check those.
    size_t nattr;
    for (nattr = 0; attr[nattr]; nattr+=2) {
        if (strchr((char *)attr[nattr+1], '&')) {
            *allocation_needed = TRUE;
        }
    }

    if (!*allocation_needed) {
        return attr;
    }

    char **attr_copy = g_malloc0(sizeof(char *) * (nattr + 1));
    if (attr_copy) {
        for (nattr = 0; attr[nattr]; nattr++) {
            if (strchr((char *)attr[nattr], '&')) {
                char **cut_out_amp = g_strsplit((char *)attr[nattr], "#38;", -1);
                attr_copy[nattr] = g_strjoinv(NULL, cut_out_amp);
                g_strfreev(cut_out_amp);
            } else {
                attr_copy[nattr] = g_strdup((char *)attr[nattr]);
            }
        }

        attr_copy[nattr] = 0;
    }

    return (const xmlChar **)attr_copy;
}
