/**
 * @file url-xml.c
 * @brief XML URL handling
 *
 * This file is used when loading data about the URLs to be parsed.  This
 * data will be read from a file, either specified from the configuration
 * file or passed in as an append or insert.
 */

/* $Progeny$
 *
 * AUTHOR: John R. Daily <jdaily@progeny.com>
 *
 * Copyright 2002 Progeny Linux Systems, Inc.
 * Copyright 2002 Hewlett-Packard Company
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "config.h"

#include <assert.h>
#include <string.h>

#include <expat.h>

#include <discover/discover.h>
#include <discover/discover-conf.h>
#include <discover/discover-xml.h>

#include <discover/load-url.h>
#include <discover/url-xml.h>
#include <discover/utils.h>

/** Define possible states within the XML data. */
enum state { START, DISCOVER_DATA };
struct context {
    enum state state;

    discover_xml_url_t *urls;
    int unknown_level; /* How deep are we into unknown XML tags? */
};

static discover_xml_url_t *xml_urls = NULL;
static discover_xml_url_t *data_urls[BUS_COUNT][3];

static char *known_url_elements[] = {
    "location",
    "discover-data",
    NULL
};


static bool
unknown_url_element(const XML_Char * const tag)
{
    int i;
    for (i = 0; known_url_elements[i] != NULL; i++) {
        if (strcmp(tag, known_url_elements[i]) == 0)
            return false;
    }
    return true;
}

static void
add_location(struct context *context, const XML_Char *attrs[])
{
    discover_xml_url_t *new;
    int i;

    assert(context != NULL);
    assert(attrs != NULL);

    new = discover_xml_url_new();

    for (i = 0; attrs[i]; i += 2) {
        if (strcmp(attrs[i], "url") == 0) {
            new->url = _discover_xstrdup(attrs[i + 1]);
        } else if (strcmp(attrs[i], "bus") == 0) {
            new->bus = _discover_xstrdup(attrs[i + 1]);
        } else if (strcmp(attrs[i], "type") == 0) {
            new->type = _discover_xstrdup(attrs[i + 1]);
        }
    }

    if (context->urls) {
        if (context->urls->last) {
            context->urls->last->next = new;
        } else {
            context->urls->next = new;
        }
        context->urls->last = new;
    } else {
        context->urls = new;
        context->urls->next = context->urls->last = NULL;
    }
}

static void
start_element(void *ctx, const XML_Char *name, const XML_Char *attrs[])
{
    struct context *context = ctx;

    assert(context != NULL);
    assert(name != NULL);

    if (unknown_url_element(name)) {
        context->unknown_level++;
        return;
    }

    if (context->unknown_level > 0) {
        return;
    }

    switch (context->state) {
    case START:
        if (strcmp(name, "discover-data") == 0) {
            context->state = DISCOVER_DATA;
        }
        break;

    case DISCOVER_DATA:
        if (strcmp(name, "location") == 0) {
            add_location(context, attrs);
        }
        break;
    }
}

static void
end_element(void *ctx, const XML_Char *name)
{
    struct context *context = ctx;

    assert(context != NULL);
    assert(name != NULL);

    if (unknown_url_element(name)) {
        context->unknown_level--;
        return;
    }

    if (context->unknown_level > 0) {
        return;
    }

    /*
     * We don't have any end tags to worry about, but if we did, they
     * would be handled here.
     */
}

/**
 * @defgroup url_xml URL list XML parsing
 * @{
 */

/**
 * Get the list of URLs from which bus and device data will be
 * retrieved.
 *
 * @param status Address in which to place status report
 **/
discover_xml_url_t *
discover_xml_get_urls(discover_error_t *status)
{
    discover_xml_url_t *urls, *i;
    XML_Parser parser;
    struct context context;
    int loaded_urls = 0;

    assert(status != NULL);

    status->code = 0;

    if (!xml_urls) {
        context.state = START;
        context.urls = NULL;
        context.unknown_level = 0;

        urls = discover_conf_get_urls(status);
        if (status->code != 0) {
            return NULL;
        }

        for (i = urls;
             i;
             i = i->next) {
            parser = XML_ParserCreate(NULL);
            XML_SetElementHandler(parser, start_element, end_element);
            XML_SetUserData(parser, &context);

            if (_discover_load_url(i->url, parser)) {
                loaded_urls++;
            }

        }

        if (loaded_urls == 0) {
            discover_xml_url_free(context.urls);
            XML_ParserFree(parser);
            status->code = DISCOVER_EIO;
            return NULL;
        }

        if (!XML_Parse(parser, "", 0, 1)) {
            status->code = DISCOVER_EXML;
        }

        XML_ParserFree(parser);
        xml_urls = context.urls;

        /* This must be done here because there could be bogus status left
         * over from the XML_Parse check above.  (We assume something good
         * happened by this point.)
         */
        status->code = 0;
    }

    return xml_urls;
}

/**
 * Free the list of URLs.
 */
void
discover_xml_free_urls(void)
{
    if (xml_urls) {
        discover_xml_url_free(xml_urls);
        xml_urls = NULL;
    }
}

/**
 * Get the list of data URLs, based on the bus type and type of data
 * required.
 *
 * @param bus Bus in question
 * @param filetype Type of file (vendor, busclass, or device)
 * @param status Address in which to place status report
 **/
discover_xml_url_t *
discover_xml_get_data_urls(discover_bus_t bus, discover_filetype_t filetype,
                           discover_error_t *status)
{
    discover_xml_url_t *urls, *i, *new;
    char *busname = discover_conf_get_bus_name(bus);
    char *filetypename = discover_conf_get_filetype_name(filetype);

    assert(status != NULL);
    assert(busname != NULL);

    status->code = 0;

    if (!data_urls[bus][filetype]) {
        urls = discover_xml_get_urls(status);
        if (status->code != 0) {
            return NULL;
        }

        status->code = DISCOVER_EDATANOTFOUND;
        for (i = urls;
             i;
             i = i->next) {
            if (strcmp(i->bus, busname) == 0
                && strcmp(i->type, filetypename) == 0) {

                status->code = 0;

                new = discover_xml_url_new();
                discover_xml_url_copy(i, new);

                if (data_urls[bus][filetype]) {
                    data_urls[bus][filetype]->last->next = new;
                    data_urls[bus][filetype]->last = new;
                } else {
                    data_urls[bus][filetype] = new;
                    data_urls[bus][filetype]->last = new;
                    data_urls[bus][filetype]->next = NULL;
                }
            }
        }
    }

    return data_urls[bus][filetype];
}

/**
 * Free the list of data URLs.
 */
void
discover_xml_free_data_urls(void)
{
    int b, f;

    for(b = 0; b < BUS_COUNT; b++) {
        for(f = 0; f < 3; f++) {
            discover_xml_url_free(data_urls[b][f]);
            data_urls[b][f] = NULL;
        }
    }
}

/**
 * Create and initialize a new (empty) URL structure.
 */
discover_xml_url_t *
discover_xml_url_new(void)
{
    discover_xml_url_t *new;

    new = _discover_xmalloc(sizeof(discover_xml_url_t));

    new->url = NULL;
    new->bus = NULL;
    new->type = NULL;
    new->label = NULL;
    new->next = NULL;
    new->last = NULL;

    return new;
}

/**
 * Copy a URL structure.
 *
 * @param src Copy from (source)
 * @param dst Copy to (destination)
 */
void
discover_xml_url_copy(discover_xml_url_t *src,
                      discover_xml_url_t *dst)
{
    assert(src != NULL);
    assert(dst != NULL);

    if (src->url) {
        dst->url = _discover_xstrdup(src->url);
    }

    if (src->bus) {
        dst->bus = _discover_xstrdup(src->bus);
    }

    if (src->type) {
        dst->type = _discover_xstrdup(src->type);
    }

    if (src->label) {
        dst->label = _discover_xstrdup(src->label);
    }
}

/**
 * Get the url member of url.
 */
char *
discover_xml_url_get_url(discover_xml_url_t *url)
{
    assert(url != NULL);

    return url->url;
}

/**
 * Get the label member of url.
 */
char *
discover_xml_url_get_label(discover_xml_url_t *url)
{
    assert(url != NULL);

    return url->label;
}

/**
 * Get the bus member of URL.
 */
char *
discover_xml_url_get_bus(discover_xml_url_t *url)
{
    assert(url != NULL);

    return url->bus;
}

/**
 * Get the type member of url.
 */
char *
discover_xml_url_get_type(discover_xml_url_t *url)
{
    assert(url != NULL);

    return url->type;
}

/**
 * Get the next member of url.
 */
discover_xml_url_t *
discover_xml_url_get_next(discover_xml_url_t *url)
{
    assert(url != NULL);

    return url->next;
}

/**
 * Get the last member of url.
 */
discover_xml_url_t *
discover_xml_url_get_last(discover_xml_url_t *url)
{
    assert(url != NULL);

    return url->last;
}

/**
 * Free the URL or list of URLs.
 *
 * @param urls URL or list of URLs to free
 */
void
discover_xml_url_free(discover_xml_url_t *urls)
{
    discover_xml_url_t *url, *last;

    last = NULL;

    for (url = urls;
         url;
         url = url->next) {
        if (url->url) {
            free(url->url);
        }

        if (url->bus) {
            free(url->bus);
        }

        if (url->type) {
            free(url->type);
        }

        if (url->label) {
            free(url->label);
        }

        if (last) {
            free(last);
        }
        last = url;
    }

    if (last) {
        free(last);
    }
}

/** @} */
