/**
 * @file sysdep.c
 * @brief Interact with system dependencies
 *
 * This file holds the routines that interact with the system-dependent
 * interface.  This file essentially bridges the gap between the XML data
 * and the hardware contained within the machine.
 */

/* $Progeny$
 *
 * AUTHOR: John R. Daily <jdaily@progeny.com>
 *
 * Copyright 2002 Progeny Linux Systems, Inc.
 *
 * 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 <assert.h>

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

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

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


/** Function pointers */
typedef discover_sysdep_data_t *(raw_sysdep_function_t)(void);

static raw_sysdep_function_t *raw_map[] = {
    _discover_get_ata_raw,
    _discover_get_pci_raw,
    _discover_get_pcmcia_raw,
    _discover_get_scsi_raw,
    _discover_get_usb_raw
};


/*
 * Utility functions
 */

/** Create a new instance of sysdep data. */
discover_sysdep_data_t *
_discover_sysdep_data_new(void)
{
    discover_sysdep_data_t *node =
        _discover_xmalloc(sizeof(discover_sysdep_data_t));
    node->busclass = NULL;
    node->vendor = NULL;
    node->model = NULL;
    node->data = NULL;
    node->next = NULL;
    return node;
}

/** Create a new instance of sysdep device data. */
discover_sysdep_device_data_t *
_discover_sysdep_device_data_new(void)
{
    discover_sysdep_device_data_t *node =
        _discover_xmalloc(sizeof(discover_sysdep_device_data_t));
    node->path = NULL;
    node->value = NULL;
    node->next = NULL;
    return node;
}

/** Release the memory that the sysdep data was holding. */
void
_discover_free_sysdep_data(discover_sysdep_data_t *head)
{
    discover_sysdep_data_t *node;

    while (head) {
        node = head->next;
        if (head->vendor) {
            free(head->vendor);
        }
        if (head->model) {
            free(head->model);
        }
        if (head->busclass) {
            free(head->busclass);
        }
        if (head->data) {
            _discover_free_sysdep_device_data(head->data);
        }
        free(head);
        head = node;
    }
}

/** Release memory being held by device-specific data. */
void
_discover_free_sysdep_device_data(discover_sysdep_device_data_t *head)
{
    discover_sysdep_device_data_t *node;
    while (head) {
        node = head->next;
        if (head->path) {
            free(head->path);
        }
        if (head->value) {
            free(head->value);
        }
        free(head);
        head = node;
    }
}

/** Convert sysdep device data into discover data under the "device" node. */
discover_data_t *
_discover_convert_device_data(discover_sysdep_device_data_t *head)
{
    discover_data_t *data_head, *data_current, *data_new;
    discover_sysdep_device_data_t *node;
    char *buffer, *path, *token;

    /** Set up the toplevel data node. */
    data_head = discover_data_new();
    data_head->discover_class = _discover_xstrdup("device");

    /*
     * Step through the device data nodes returned by the sysdeps.
     * Each node will have a path relative to the "device" toplevel,
     * separated by slashes, as well as a value.  We need to set
     * each one up to look like it came from the XML data.
     */
    for (node = head; node; node = head->next) {
        /** Set up the first parent below "device" for this node. */
        data_current = NULL;

        /** Set up the token parser. */
        path = _discover_xstrdup(node->path);
        buffer = _discover_xstrdup(node->path);

        /** Split the node path by the path separator. */
        for (token = strtok_r(path, "/", &buffer); token;
             token = strtok_r(NULL, "/", &buffer)) {
            /** Set up the new data node. */
            data_new = discover_data_new();
            data_new->discover_class = _discover_xstrdup(token);

            /** Does this node have a predecessor in the path? */
            if (data_current == NULL) {
                /** No; make it a child of the "device" node. */
                if (data_head->child == NULL) {
                    data_head->child = data_new;
                } else {
                    data_current = data_head->child;
                    while (data_current->next)
                        data_current = data_current->next;
                    data_current->next = data_new;
                }
            } else {
                /** Yes; make it a child of the previous. */
                data_current->child = data_new;
                data_new->parent = data_current;
            }

            data_current = data_new;
        }

        /*
         * We've gotten to the very last path element; set the 
         * node's value here.
         */
        data_current->text = _discover_xstrdup(node->value);

        /** Clean up after the token parser. */
        free(path);
        free(buffer);
    }

    return data_head;
}

/*
 * Functions that access the sysdeps
 */
static discover_device_t *devices[BUS_COUNT];

discover_device_t *
discover_get_devices(discover_bus_t bus, discover_error_t *status)
{
    discover_device_t *device, *last, *tmp, *i;
    discover_device_t *xml_devices;
    discover_bus_map_t *busmap;
    discover_sysdep_data_t *head, *node;
    discover_data_t *data_head, *data_node;

    assert(status);

    status->code = DISCOVER_SUCCESS;
    device = last = NULL;

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

    if (busmap->scan_never) {
        status->code = DISCOVER_EBUSDISABLED;
        return NULL;
    }

    if (devices[bus]) {
        return devices[bus];
    }

    xml_devices = discover_xml_get_devices(bus, status);
    if (!xml_devices) {
        return NULL;
    }

    /*
     * Allow overrides of this function.
     */
    if (busmap->get_raw) {
        head = node = busmap->get_raw();
    } else {
        head = node = raw_map[bus]();
    }

    while (node) {
        /*
         * The variable names are ambiguous:
         *   node   -  Hardware data retrieved from the OS
         *   device -  Matching hardware data from our database
         */
        device =
            discover_xml_get_matching_devices(xml_devices, node->vendor,
                                              node->model, status);

        if (!device) {
            device = discover_device_new();
            device->model_id = strdup(node->model);
            device->vendor_id = strdup(node->vendor);
        }

        /*
         * If we get a busclass from the hardware, treat it as
         * canonical.
         */
        if (node->busclass != NULL) {
            if (device->busclass != NULL) {
                free(device->busclass);
            }
            device->busclass = strdup(node->busclass);
        }

        /* Add sysdep data to the data nodes. */
        if (node->data != NULL) {
            data_head = _discover_convert_device_data(node->data);
            if (data_head != NULL) {
                for (data_node = data_head; data_node->next != NULL; 
                     data_node = data_node->next) ;
                data_node->next = device->data;
                device->data = data_head;
            }
        }

        if (last) {
            /* walk to the end of the tree */
            for (; last->next; last = last->next)
                ;
            last->next = device;
            last = device;
        } else {
            devices[bus] = last = device;
        }

        node = node->next;
    }

    _discover_free_sysdep_data(head);

    return devices[bus];
}

void
discover_free_devices(void)
{
    int i;

    for (i = 0; i < BUS_COUNT; i++) {
        discover_device_free(devices[i], 0);
        devices[i] = NULL;
    }
}

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