/**
 * @file device.c
 * @brief Device enumeration and information management
 *
 * This file contains many device-related routines.  Most of these routines
 * are used to alter or access data pertaining to a device.  These routines
 * will likely be most interesting to people writing code against the
 * Discover API.
 */

/* $Progeny$
 *
 * Copyright 2001, 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 <stdbool.h>
#include <string.h>

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

#include <discover/device.h>
#include <discover/utils.h>

/**
 * @defgroup device Devices
 * @{
 */

/**
 * Get a list of devices of a specified class on all buses scanned by
 * default.
 *
 * @param discover_class Class of devices to find
 * @param status Address in which to place status report
 */
discover_device_t *
discover_device_find(char *discover_class, discover_error_t *status)
{
    discover_device_t *result, *device, *last, *new_device;
    discover_xml_busclass_t *busclasses;
    discover_bus_map_t *busmap;
    int i;

    assert(discover_class);

    result = last = NULL;

    busmap = discover_conf_get_full_bus_map(status);
    if (status->code != 0) {
        return NULL;
    }

    for (i = 0; busmap[i].name; i++) {
        if (!busmap[i].scan_default) {
            continue;
        }

        busclasses = discover_xml_get_busclasses(i, status);
        if (status->code != 0) {
            return result;
        }
        for (device = discover_get_devices(i, status);
             device;
             device = discover_device_get_next(device)) {
            if (!device->busclass) {
                /* This is a device about which we know nothing. */
                continue;
            }
            if (discover_xml_busclass_cmp(device->busclass, discover_class,
                                          busclasses) == 0) {
                new_device = discover_device_new();
                discover_device_copy(device, new_device);
                new_device->next = NULL;

                if (last) {
                    for (; last->next; last = last->next)
                        ;
                    last->next = new_device;
                    last = new_device;
                } else {
                    result = last = new_device;
                }
            }
            else {
            }
        }

        if (status->code != 0) {
            return result;
        }
    }

    if (result) {
        status->code = DISCOVER_SUCCESS;
    } else {
        char *message = _discover_xmalloc(100);
        snprintf(message, 100, "Device \"%s\" not found",
                    discover_class);
        status->code = DISCOVER_EDEVICENOTFOUND;
        status->create_message(&status, message);
    }

    return result;
}

static discover_data_t *
scan_data_list(discover_data_t *node, char *discover_class, char **version,
               discover_error_t *status)
{
    discover_data_t *result = NULL;
    int cmp;

    for ( ; node != NULL; node = node->next) {
        if (strcmp(node->discover_class, discover_class) == 0) {
            if (*version && node->version) {
                cmp = discover_xml_version_cmp(node->version,
                                               *version, status);

                if (status->code != 0) {
                    return result;
                }
                if (cmp) {
                    result = node;
                    /* The version is no longer relevant for nested data
                     * elements.
                     */
                    *version = NULL;
                    break;
                }
            } else {
                result = node;
                break;
            }
        }
    }
    return result;
}


static discover_data_t *
get_data(discover_device_t *device, char *discover_class, char **version,
         discover_error_t *status)
{
    assert(device != NULL);
    assert(discover_class != NULL);

    return scan_data_list(device->data, discover_class, version, status);
}

static discover_data_t *
get_child_data(discover_data_t *data, char *discover_class, char **version,
               discover_error_t *status)
{
    assert(data != NULL);
    assert(discover_class != NULL);

    return scan_data_list(data->child, discover_class, version, status);
}

/**
 * Get the data matching the class path and version number (optional)
 * from the device structure.
 *
 * The class parameter requires further explanation.  The XML data
 * sources have hierarchical data elements for each device.  You
 * access the data in an element by specifying the path along the data
 * elements to it.
 *
 * For example, device elements for video cards usually have a data
 * element of class "xfree86" containing data related to the XFree86
 * package; that element itself usually contains a data element of
 * class "server" that specifies which server to use for this video
 * card.  You access this data by passing the string "xfree86/server"
 * to this function.
 *
 * @param device Device from which to get the data
 * @param discover_class Class to search for
 * @param version Version number to require (NULL for unversioned search)
 * @param status Address in which to place status report
 */
char *
discover_device_get_data(discover_device_t *device, char *discover_class,
                         char *version, discover_error_t *status)
{
    discover_data_t *data;
    char *result, *tmp, *tmp_orig, **argv, *block;
    size_t argv_len;
    int i;

    assert(device != NULL);
    assert(discover_class != NULL);
    assert(status != NULL);

    status->code = 0;

    data = NULL;
    result = NULL;
    tmp_orig = tmp = _discover_xstrdup(discover_class);
    argv = NULL;
    argv_len = 0;

    while ((block = strsep(&tmp, "/"))) {
        argv_len++;
        argv = _discover_xrealloc(argv, sizeof(char *) * argv_len);
        argv[argv_len - 1] = block;
    }


    while(!data && device) {
        data = get_data(device, argv[0], &version, status);

        if (data) {
            for (i = 1; i < argv_len; i++) {
                data = get_child_data(data, argv[i], &version, status);
                if (status->code != 0) {
                    goto out;
                }
                if (!data) {
                    break;
                }
            }
        }

        device = device->extra;
    }

    if (data) {
        result = data->text;
    }

out:

    free(tmp_orig);
    free(argv);

    return result;
}

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

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

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

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

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

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

    dst->busclasses = src->busclasses;
    dst->vendors = src->vendors;
    dst->data = src->data;
    dst->extra = src->extra;
    dst->next = src->next;
}

/******************************************************************************
 * Data accessors
 */

/**
 * Get the class member of data.
 */
char *
discover_data_get_class(discover_data_t *data)
{
    assert(data != NULL);

    return data->discover_class;
}

/**
 * Get the text member of data.
 */
char *
discover_data_get_text(discover_data_t *data)
{
    assert(data != NULL);

    return data->text;
}

/**
 * Get the parent member of data.
 */
discover_data_t *
discover_data_get_parent(discover_data_t *data)
{
    assert(data != NULL);

    return data->parent;
}

/**
 * Get the child member of data.
 */
discover_data_t *
discover_data_get_child(discover_data_t *data)
{
    assert(data != NULL);

    return data->child;
}

/**
 * Get the next member of data.
 */
discover_data_t *
discover_data_get_next(discover_data_t *data)
{
    assert(data != NULL);

    return data->next;
}

/**
 * Get the prev member of data.
 */
discover_data_t *
discover_data_get_prev(discover_data_t *data)
{
    assert(data != NULL);

    return data->prev;
}

/**
 * Get the first member of data.
 */
discover_data_t *
discover_data_get_first(discover_data_t *data)
{
    int i = 1;

    if (data == NULL) {
        return NULL;
    }

    while(i) {
        if(data->prev) {
            data = data->prev;
        } else if(data->parent) {
            data = data->parent;
        } else {
            i = 0;
        }
    }
/*

    for (;
         data->prev;
         data = data->prev)
    ;
    for (;
         data->parent;
         data = data->parent)
    ;
*/
    return data;
}

/**
 * Create and initialize a new discover_data_t structure.
 */
discover_data_t *
discover_data_new(void)
{
    discover_data_t *new;

    new = _discover_xmalloc(sizeof(discover_data_t));

    new->discover_class = NULL;
    new->version = NULL;
    new->text = NULL;
    new->parent = NULL;
    new->child = NULL;
    new->next = NULL;
    new->prev = NULL;

    return new;
}

/**
 * Free the data tree.  Note that unlike the other free functions in
 * Discover, this one does not free data_tree itself.  This is a
 * side-effect of how it is implemented.  It frees all children and
 * siblings of data_tree as well as the data.  After calling
 * discover_data_free, you have to call free(3) on data_tree itself.
 *
 * @param data_tree Data tree to free
 */
void
discover_data_free(discover_data_t *data_tree)
{
    discover_data_t *data, *last;

    last = NULL;

    if (data_tree) {
        for (data = data_tree->next;
             data;
             data = data->next) {
            discover_data_free(data);

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

        if (last) {
            free(last);
        }

        if (data_tree->child) {
            discover_data_free(data_tree->child);
            free(data_tree->child);
        }

        if (data_tree->discover_class) {
            free(data_tree->discover_class);
        }

        if (data_tree->version) {
            free(data_tree->version);
        }

        if (data_tree->text) {
            free(data_tree->text);
        }
    }
}

/******************************************************************************
 * Device accessors
 */

/**
 * Get the busclass member of device.
 */
char *
discover_device_get_busclass(discover_device_t *device)
{
    assert(device != NULL);

    return device->busclass;
}

/**
 * Get the model_id member of device.
 */
char *
discover_device_get_model_id(discover_device_t *device)
{
    assert(device != NULL);

    return device->model_id;
}

/**
 * Get the model_name member of device.
 */
char *
discover_device_get_model_name(discover_device_t *device)
{
    assert(device != NULL);

    return device->model_name;
}

/**
 * Get the vendor_id member of device.
 */
char *
discover_device_get_vendor_id(discover_device_t *device)
{
    assert(device != NULL);

    return device->vendor_id;
}

/**
 * Get the vendor_name member of device.
 */
char *
discover_device_get_vendor_name(discover_device_t *device)
{
    assert(device != NULL);

    return device->vendor_name;
}

/**
 * Get the data member of device.
 */
discover_data_t *
discover_device_get_data_struct(discover_device_t *device)
{
    assert(device != NULL);

    return device->data;
}

/**
 * Get the next member of device.
 */
discover_device_t *
discover_device_get_next(discover_device_t *device)
{
    assert(device != NULL);

    return device->next;
}

/**
 * Create and initialize a new discover_device_t structure.
 */
discover_device_t *
discover_device_new(void)
{
    discover_device_t *new;

    new = _discover_xmalloc(sizeof(discover_device_t));

    new->busclass = NULL;
    new->model_id = NULL;
    new->model_name = NULL;
    new->vendor_id = NULL;
    new->vendor_name = NULL;
    new->busclasses = NULL;
    new->vendors = NULL;
    new->data = NULL;
    new->next = NULL;
    new->extra = NULL;

    return new;
}

/**
 * Free the device or list of devices.  Pass 1 as the second argument
 * to free the data structure for this device, 0 to leave it.  It is
 * necessary to specify 0 when one structure was copied from another
 * with discover_device_copy.  Otherwise, you must pass 1.
 *
 * @param devices Device or list of devices to free
 * @param free_data Whether to free the data structure
 */
void
discover_device_free(discover_device_t *devices, int free_data)
{
    discover_device_t *device, *last;

    last = NULL;

    for (device = devices;
         device;
         device = device->next) {
        if (device->busclass) {
            free(device->busclass);
        }

        if (device->model_id) {
            free(device->model_id);
        }

        if (device->model_name) {
            free(device->model_name);
        }

        if (device->vendor_id) {
            free(device->vendor_id);
        }

        if (device->vendor_name) {
            free(device->vendor_name);
        }

        if (free_data && device->data) {
            discover_data_free(device->data);
            free(device->data);
        }

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

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