/**
 * @file busclass-xml.c
 * @brief Busclass XML data file parsing
 *
 * This file contains the routines needed to properly process the busclass
 * XML data.  This file is responsible for handling URLs and actually
 * storing the XML data during the parsing process.
 */

/* $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 <sys/types.h>

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

#include <expat.h>

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

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

/** A list member describing a busclass */
struct discover_xml_busclass {
    char *id;
    char *name;
    discover_xml_busclass_t *next;
};

static discover_xml_busclass_t *busclasses[BUS_COUNT];

struct context {
    discover_xml_busclass_t **blist;

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

static char *known_bus_elements[] = {
    "busclass",
    "busclass_list",
    NULL
};

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

static void
start_element(void *ctx, const XML_Char *name, const XML_Char *attrs[])
{
    struct context *context = ctx;
    int i;
    char *vid, *vname;
    discover_xml_busclass_t *next, **blist;

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

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

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

    if (strcmp(name, "busclass") == 0) {
        vid = vname = NULL;
        for (i = 0; attrs[i]; i += 2) {
            if (strcmp(attrs[i], "id") == 0) {
                vid = (char *) attrs[i + 1];
            } else if (strcmp(attrs[i], "name") == 0) {
                vname = (char *) attrs[i + 1];
            }
        }

        assert(vid != NULL);
        assert(vname != NULL);

        blist = context->blist;
        next = *blist;
        *blist = discover_xml_busclass_new();
        (*blist)->id = _discover_xstrdup(vid);
        (*blist)->name = _discover_xstrdup(vname);
        (*blist)->next = next;
    }
}

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

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

    if (unknown_bus_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 busclass_xml Busclass list XML parsing
 * @{
 */

/**
 * Merge new busclasses into a list.
 *
 * @param blist Address of the list to merge busclasses into
 * @param url URL of the document defining the busclasses
 * @param status Address in which to place status report
 */
/* Sshh!  Don't tell, but this doesn't actually do any merging at all.
 * Instead, it simply inserts newer entries at the front of the list,
 * meaning that busclass info found later supersedes info found earlier.
 * This gives the illusion of merging, but potentially wastes memory
 * with duplicates.
 */
void
discover_xml_merge_busclass_url(discover_xml_busclass_t **blist,
                                char *url, discover_error_t *status)
{
    XML_Parser parser;
    struct context context;

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

    status->code = 0;

    context.blist = blist;
    context.unknown_level = 0;

    parser = XML_ParserCreate(NULL);
    XML_SetElementHandler(parser, start_element, end_element);
    XML_SetUserData(parser, &context);

    if (!_discover_load_url(url, parser)) {
        XML_ParserFree(parser);
        status->code = DISCOVER_EIO;
        return;
    }

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

    XML_ParserFree(parser);

    return;
}

/**
 * Get the list of busclasses by bus type.
 *
 * @param bus Type of bus for which busclasses are required
 * @param status Address in which to place status report
 */
discover_xml_busclass_t *
discover_xml_get_busclasses(discover_bus_t bus, discover_error_t *status)
{
    discover_xml_url_t *urls, *i;
    char *url;

    assert(status != NULL);

    status->code = 0;

    if (!busclasses[bus]) {
        urls = discover_xml_get_data_urls(bus, BUSCLASS_TYPE, status);
        if (status->code != 0) {
            return NULL;
        }

        for (i = urls;
             i;
             i = discover_xml_url_get_next(i)) {
            url = discover_xml_url_get_url(i);
            discover_xml_merge_busclass_url(&(busclasses[bus]), url, status);
            if (status->code != 0) {
                return NULL;
            }
        }
    }

    return busclasses[bus];
}


/**
 * Free the list of busclasses.
 */
void
discover_xml_free_busclasses(void)
{
    int i;
    for (i = 0; i < BUS_COUNT; i++) {
        discover_xml_busclass_free(busclasses[i]);
        busclasses[i] = NULL;
    }
}

/**
 * Return the device class corresponding to a busclass.
 *
 * @param busclass Busclass to look up
 * @param busclasses List of busclasses to search
 */
char *
discover_xml_busclass_to_class(char *busclass,
                               discover_xml_busclass_t *busclasses)
{
    char *result;

    assert(busclass != NULL);
    assert(busclasses != NULL);

    result = NULL;

    for (;
         busclasses;
         busclasses = busclasses->next) {
        if (strcmp(busclass, busclasses->id) == 0) {
            result = busclasses->name;
            break;
        }
    }

    return result;
}

/**
 * Compare a busclass to a device class, returning the same kind of
 * value returned by strcmp(3).  Note that this function has a special
 * case for the class 'all'; it is considered equal to all busclasses.
 *
 * @param busclass Busclass to compare
 * @param discover_class Device class to compare
 * @param busclasses List of busclasses to search
 */
int
discover_xml_busclass_cmp(char *busclass, char *discover_class,
                          discover_xml_busclass_t *busclasses)
{
    char *found_class_name = NULL;

    assert(busclass != NULL);
    assert(discover_class != NULL);
    assert(busclasses != NULL);

    if (strcmp(discover_class, "all") == 0) {
        return 0;
    }

    found_class_name = discover_xml_busclass_to_class(busclass, busclasses);

    /*
     * If the busclass database doesn't know about this device class, we
     * can't make any valid comparison.
     */
    if (found_class_name == NULL) {
        return -1;
    }

    return strcmp(found_class_name, discover_class);
}

/**
 * Get the id member of busclass.
 *
 * @param busclass
 */
char *
discover_xml_busclass_get_id(discover_xml_busclass_t *busclass)
{
    assert(busclass != NULL);

    return busclass->id;
}

/**
 * Get the name member of busclass.
 *
 * @param busclass
 */
char *
discover_xml_busclass_get_name(discover_xml_busclass_t *busclass)
{
    assert(busclass != NULL);

    return busclass->name;
}

/**
 * Get the next member of busclass (used for traversing lists of
 * busclasses).
 */
discover_xml_busclass_t *
discover_xml_busclass_get_next(discover_xml_busclass_t *busclass)
{
    assert(busclass != NULL);

    return busclass->next;
}

/**
 * Create and initialize a new discover_xml_busclass_t object.
 */
discover_xml_busclass_t *
discover_xml_busclass_new(void)
{
    discover_xml_busclass_t *new;

    new = _discover_xmalloc(sizeof(discover_xml_busclass_t));

    new->id = NULL;
    new->name = NULL;
    new->next = NULL;

    return new;
}

/**
 * Free the busclass or list of busclasses.
 *
 * @param busclasses Busclass or list of busclasses to free
 */
void
discover_xml_busclass_free(discover_xml_busclass_t *busclasses)
{
    discover_xml_busclass_t *busclass, *last;

    last = NULL;

    for (busclass = busclasses;
         busclass;
         busclass = busclass->next) {
        if (busclass->id) {
            free(busclass->id);
        }

        if (busclass->name) {
            free(busclass->name);
        }

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

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

/** @} */

/*
 * Local variables:
 * c-file-style: "progeny"
 * indent-tabs-mode: nil
 * End:
 */
/* vim: set cin fo=tcroq sw=4 et sts=4 tw=75: */
