/**
 * @file vendor-xml.c
 * @brief Vendor XML data file parsing
 *
 * This file contains the routines needed to properly process the vendor
 * XML data.  This file is responsible for handling URLs and
 * 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>

/** List member describing a hardware vendor */
struct discover_xml_vendor {
    char *id;
    char *name;
    discover_xml_vendor_t *next;
};

static discover_xml_vendor_t *vendors[BUS_COUNT];

/** Structure that holds information about the XML being parsed */
struct context {
    discover_xml_vendor_t **vlist;

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

static char *known_vendor_elements[] = {
    "vendor",
    "vendor_list",
    NULL
};


static bool
unknown_vendor_element(const XML_Char * const tag)
{
    int i;
    for (i = 0; known_vendor_elements[i] != NULL; i++) {
        if (strcmp(tag, known_vendor_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_vendor_t *next, **vlist;

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

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

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

    if (strcmp(name, "vendor") == 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);

        vlist = context->vlist;
        next = *vlist;
        *vlist = discover_xml_vendor_new();
        (*vlist)->id = _discover_xstrdup(vid);
        (*vlist)->name = _discover_xstrdup(vname);
        (*vlist)->next = next;
    }
}

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

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

    if (unknown_vendor_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 vendor_xml Vendor list XML parsing
 * @{
 */

/**
 * Merge new vendors into a list.
 *
 * @param vlist Address of the list to merge vendors into
 * @param url URL of the document defining the vendors
 * @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 vendor info found later supersedes info found earlier.
 * This gives the illusion of merging, but potentially wastes memory
 * with duplicates.
 */
void
discover_xml_merge_vendor_url(discover_xml_vendor_t **vlist,
                              char *url, discover_error_t *status)
{
    XML_Parser parser;
    struct context context;

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

    status->code = 0;

    context.vlist = vlist;
    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 vendors by bus type.
 *
 * @param bus Type of bus for which vendors are required
 * @param status Address in which to place status report
 */
discover_xml_vendor_t *
discover_xml_get_vendors(discover_bus_t bus, discover_error_t *status)
{
    discover_xml_url_t *urls, *i;
    char *url;

    assert(status != NULL);

    status->code = 0;

    if (!vendors[bus]) {
        urls = discover_xml_get_data_urls(bus, VENDOR_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_vendor_url(&(vendors[bus]), url, status);
            if (status->code != 0) {
                return NULL;
            }
        }
    }

    return vendors[bus];
}

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

/**
 * Convert a vendor ID to a human-readable string naming the vendor.
 *
 * @param vendors List of vendors to search
 * @param id Vendor ID to search for
 */
char *
discover_xml_vendor_id2name(discover_xml_vendor_t *vendors, char *id)
{
    discover_xml_vendor_t *vendor;
    char *name;

    assert(vendors != NULL);
    assert(id != NULL);

    name = NULL;

    for (vendor = vendors;
         vendor;
         vendor = vendor->next) {
        if (strcmp(id, vendor->id) == 0) {
            name = vendor->name;
            break;
        }
    }

    return name;
}

/**
 * Get the id member of vendor.
 */
char *
discover_xml_vendor_get_id(discover_xml_vendor_t *vendor)
{
    assert(vendor != NULL);

    return vendor->id;
}

/**
 * Get the name member of vendor.
 */
char *
discover_xml_vendor_get_name(discover_xml_vendor_t *vendor)
{
    assert(vendor != NULL);

    return vendor->name;
}

/**
 * Get the next member of vendor.
 */
discover_xml_vendor_t *
discover_xml_vendor_get_next(discover_xml_vendor_t *vendor)
{
    assert(vendor != NULL);

    return vendor->next;
}

/**
 * Create and initialize a new discover_xml_vendor_t structure.
 */
discover_xml_vendor_t *
discover_xml_vendor_new(void)
{
    discover_xml_vendor_t *new;

    new = _discover_xmalloc(sizeof(discover_xml_vendor_t));

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

    return new;
}

/**
 * Free the vendor or list of vendors.
 *
 * @param vendors Vendor or list of vendors to free
 */
void
discover_xml_vendor_free(discover_xml_vendor_t *vendors)
{
    discover_xml_vendor_t *vendor, *last;

    last = NULL;

    for (vendor = vendors;
         vendor;
         vendor = vendor->next) {
        if (vendor->id) {
            free(vendor->id);
        }

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

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

    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: */
