/*      -*- linux-c -*-
 *
 * (C) Copyright IBM Corp. 2003, 2005
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  This
 * file and program are licensed under a BSD style license.  See
 * the Copying file included with the OpenHPI distribution for
 * full licensing terms.
 *
 * Author(s):
 *      Renier Morales <renier@openhpi.org>
 */

#include <string.h>

#include <oh_utils.h>
#include <oh_error.h>

typedef struct {
        SaHpiRptEntryT rpt_entry;
        int owndata;
        void *data; /* private data for the owner of the RPTable */
        SaHpiUint32T update_count; /* RDR Update counter */
        GSList *rdrlist; /* Contains RDRecords for sequence lookups */
        GHashTable *rdrtable; /* Contains RDRecords for fast RecordId lookups */
} RPTEntry;

typedef struct {
       SaHpiRdrT rdr;
       int owndata;
       void *data; /* private data for the owner of the rpt entry. */
} RDRecord;


static RPTEntry *get_rptentry_by_rid(RPTable *table, SaHpiResourceIdT rid)
{
        GSList *rptnode = NULL;
        RPTEntry *rptentry = NULL;

        if (!table) {
                return NULL;
        }

        if (!(table->rptlist)) {
                /*DBG("Info: RPT is empty.");*/
                return NULL;
        }

        if (rid == SAHPI_FIRST_ENTRY) {
                rptentry = (RPTEntry *) (table->rptlist->data);
        } else {
                rptnode = (GSList *)g_hash_table_lookup(table->rptable, &rid);
                rptentry = rptnode ? (RPTEntry *)rptnode->data : NULL;
        }

        return rptentry;
}

static GSList *get_rptnode_by_rid(RPTable *table, SaHpiResourceIdT rid)
{
        GSList *rptnode = NULL;

        if (!table) {
                return NULL;
        }

        if (!(table->rptlist)) {
                /*DBG("Info: RPT is empty.");*/
                return NULL;
        }

        if (rid == SAHPI_FIRST_ENTRY) {
                rptnode = (GSList *) (table->rptlist);
        } else {
                rptnode = (GSList *)g_hash_table_lookup(table->rptable, &rid);
        }

        return rptnode;
}

static RDRecord *get_rdrecord_by_id(RPTEntry *rptentry, SaHpiEntryIdT id)
{
        GSList *rdrnode = NULL;
        RDRecord *rdrecord = NULL;

        if (!rptentry) {
                return NULL;
        }

        if (!rptentry->rdrlist) {
                /*DBG("Info: RDR repository is empty.");*/
                return NULL;
        }

        if (id == SAHPI_FIRST_ENTRY) {
                rdrecord = (RDRecord *) (rptentry->rdrlist->data);
        } else {
                rdrnode = (GSList *)g_hash_table_lookup(rptentry->rdrtable, &id);
                rdrecord = rdrnode ? (RDRecord *)rdrnode->data : NULL;
        }

        return rdrecord;
}

static GSList *get_rdrnode_by_id(RPTEntry *rptentry, SaHpiEntryIdT id)
{
        GSList *rdrnode = NULL;

        if (!rptentry) {
                return NULL;
        }

        if (!rptentry->rdrlist) {
                /*DBG("Info: RPT is empty.");*/
                return NULL;
        }

        if (id == SAHPI_FIRST_ENTRY) {
                rdrnode = (GSList *) (rptentry->rdrlist);
        } else {
                rdrnode = (GSList *)g_hash_table_lookup(rptentry->rdrtable, &id);
        }

        return rdrnode;
}

static int check_instrument_id(SaHpiRptEntryT *rptentry, SaHpiRdrT *rdr)
{
        int result = 0;
        static const SaHpiInstrumentIdT SENSOR_AGGREGATE_MAX = 0x0000010F;

        switch (rdr->RdrType) {
                case SAHPI_SENSOR_RDR:
                        if (rdr->RdrTypeUnion.SensorRec.Num >= SAHPI_STANDARD_SENSOR_MIN &&
                            rdr->RdrTypeUnion.SensorRec.Num <= SAHPI_STANDARD_SENSOR_MAX) {
                                if (rdr->RdrTypeUnion.SensorRec.Num > SENSOR_AGGREGATE_MAX) {
                                        result = -1;
                                } else if (rptentry->ResourceCapabilities &
                                           SAHPI_CAPABILITY_AGGREGATE_STATUS) {
                                        result = 0;
                                } else {
                                        result = -1;
                                }
                        } else {
                                result = 0;
                        }
                        break;
                default:
                        result = 0;
        }

        return result;
}

static void update_rptable(RPTable *table) {
        if (!table) {
                return;
        }
        oh_gettimeofday(&table->update_timestamp);
        table->update_count++;
}

/**
 * oh_get_rdr_uid
 * @type: type of rdr
 * @num: id number of the RDR unique withing the RDR type for that resource
 *
 * Helper function to derive the Record id of an rdr from its @type and @num
 *
 * Returns: a derived Record Id used to identify RDRs within Resources
 */
SaHpiEntryIdT oh_get_rdr_uid(SaHpiRdrTypeT type, SaHpiInstrumentIdT num)
{
        SaHpiEntryIdT uid;

        uid = ((SaHpiEntryIdT)type) << 16;
        uid = uid + (SaHpiEntryIdT)num;

        return uid;
}

SaHpiInstrumentIdT oh_get_rdr_num(SaHpiEntryIdT rdrid) {
        return rdrid & 0x0000ffff;
}

/**
 * oh_get_instrument_id
 * @rdr: RDR
 *
 * Helper function to derive the Instrument Id of the rdr
 *
 * Returns: an instrument id or zero
 */
SaHpiInstrumentIdT oh_get_instrument_id(const SaHpiRdrT *rdr)
{
        SaHpiInstrumentIdT num = 0;

        switch (rdr->RdrType) {
                case SAHPI_CTRL_RDR:
                        num = rdr->RdrTypeUnion.CtrlRec.Num;
                        break;
                case SAHPI_SENSOR_RDR:
                        num = rdr->RdrTypeUnion.SensorRec.Num;
                        break;
                case SAHPI_INVENTORY_RDR:
                        num = rdr->RdrTypeUnion.InventoryRec.IdrId;
                        break;
                case SAHPI_WATCHDOG_RDR:
                        num = rdr->RdrTypeUnion.WatchdogRec.WatchdogNum;
                        break;
                case SAHPI_ANNUNCIATOR_RDR:
                        num = rdr->RdrTypeUnion.AnnunciatorRec.AnnunciatorNum;
                        break;
               case SAHPI_DIMI_RDR:
                        num = rdr->RdrTypeUnion.DimiRec.DimiNum;
                        break;
                case SAHPI_FUMI_RDR:
                        num = rdr->RdrTypeUnion.FumiRec.Num;
                        break;
                default:
                        num = 0;
        }

        return num;
}


/**
 * General RPT calls
 **/

/**
 * oh_init_rpt
 * @table: Pointer to RPTable structure to be initialized.
 *
 *
 * Returns: SA_OK on success Or minus SA_OK on error.
 **/
SaErrorT oh_init_rpt(RPTable *table)
{
        if (!table) {
                return SA_ERR_HPI_INVALID_PARAMS;
        }

        table->update_timestamp = SAHPI_TIME_UNSPECIFIED;
        table->update_count = 0;
        table->rptlist = NULL;
        table->rptable = NULL;

        return SA_OK;
}

/**
 * oh_flush_rpt
 * @table: Pointer to the RPT to flush.
 *
 * Cleans RPT from all entries and RDRs and frees the memory
 * associated with them.
 *
 * Returns: SA_OK on success Or minus SA_OK on error.
 **/
SaErrorT oh_flush_rpt(RPTable *table)
{
        SaHpiRptEntryT *tmp_entry;

        while ((tmp_entry = oh_get_resource_by_id(table, SAHPI_FIRST_ENTRY)) != NULL) {
                oh_remove_resource(table, SAHPI_FIRST_ENTRY);
        }

        return SA_OK;
}

/**
 * rpt_diff
 * @cur_rpt: IN. Pointer to RPTable that represents the current state of resources
 * and rdrs.
 * @new_rpt: IN. Pointer to RPTable that contains rpt entries and rdrs just recently
 * discovered.
 * @res_new: OUT. List of new or changed rpt entries
 * @rdr_new: OUT. List of new or changed rdrs
 * @res_gone: OUT. List of old and not present resources.
 * @rdr_gone: OUT. List of old and not present rdrs.
 *
 * Extracts from current the resources and rdrs that are not found
 * in new and puts them in res_gone and rdr_gone. Also, puts in res_new and rdr_new
 * the resources and rdrs that are not already in current Or that are not identical
 * to the ones in current.
 *
 * Returns: SA_ERR_HPI_INVALID_PARAMS if any argument is NULL, otherwise SA_OK.
 **/
SaErrorT rpt_diff(RPTable *cur_rpt, RPTable *new_rpt,
                  GSList **res_new, GSList **rdr_new,
                  GSList **res_gone, GSList **rdr_gone) {

        SaHpiRptEntryT *res = NULL;

        if (!cur_rpt || !new_rpt ||
            !res_new || !rdr_new || !res_gone || !rdr_gone)
                return SA_ERR_HPI_INVALID_PARAMS;

        /* Look for absent resources and rdrs */
        for (res = oh_get_resource_by_id(cur_rpt, SAHPI_FIRST_ENTRY);
             res != NULL;
             res = oh_get_resource_next(cur_rpt, res->ResourceId)) {

                SaHpiRptEntryT *tmp_res = oh_get_resource_by_id(new_rpt, res->ResourceId);

                if (tmp_res == NULL) *res_gone = g_slist_append(*res_gone, (gpointer)res);
                else {
                        SaHpiRdrT *rdr = NULL;

                        for (rdr = oh_get_rdr_by_id(cur_rpt, res->ResourceId, SAHPI_FIRST_ENTRY);
                             rdr != NULL;
                             rdr = oh_get_rdr_next(cur_rpt, res->ResourceId, rdr->RecordId)) {

                                SaHpiRdrT *tmp_rdr =
                                        oh_get_rdr_by_id(new_rpt, res->ResourceId, rdr->RecordId);

                                if (tmp_rdr == NULL)
                                        *rdr_gone = g_slist_append(*rdr_gone, (gpointer)rdr);
                        }
                }
        }

        /* Look for new resources and rdrs*/
        for (res = oh_get_resource_by_id(new_rpt, SAHPI_FIRST_ENTRY);
             res != NULL;
             res = oh_get_resource_next(new_rpt, res->ResourceId)) {

                SaHpiRptEntryT *tmp_res = oh_get_resource_by_id(cur_rpt, res->ResourceId);
                SaHpiRdrT *rdr = NULL;
                if (tmp_res == NULL || memcmp(res, tmp_res, sizeof(SaHpiRptEntryT))) {
                        *res_new = g_slist_append(*res_new, (gpointer)res);
                }

                for (rdr = oh_get_rdr_by_id(new_rpt, res->ResourceId, SAHPI_FIRST_ENTRY);
                     rdr != NULL;
                     rdr = oh_get_rdr_next(new_rpt, res->ResourceId, rdr->RecordId)) {

                        SaHpiRdrT *tmp_rdr = NULL;

                        if (tmp_res != NULL)
                                tmp_rdr = oh_get_rdr_by_id(cur_rpt, res->ResourceId, rdr->RecordId);

                        if (tmp_rdr == NULL || memcmp(rdr, tmp_rdr, sizeof(SaHpiRdrT)))
                                *rdr_new = g_slist_append(*rdr_new, (gpointer)rdr);

                }
        }
        
        return SA_OK;
}

/**
 * oh_get_rpt_info
 * @table: pointer to RPT
 * @update_count: pointer of where to place the rpt's update count
 * @update_timestamp: pointer of where to place the rpt's update timestamp
 *
 * Returns: SA_OK on success Or minus SA_OK on error.
 **/
SaErrorT oh_get_rpt_info(RPTable *table,
                         SaHpiUint32T *update_count,
                         SaHpiTimeT *update_timestamp)
{
        if (!table || !update_count || !update_timestamp) {
                return SA_ERR_HPI_INVALID_PARAMS;
        }

        *update_count = table->update_count;
        *update_timestamp = table->update_timestamp;

        return SA_OK;
}

/**
 * Resource interface functions
 */

/**
 * oh_add_resource
 * @table: Pointer to the RPT to which the RPT entry will be added.
 * @entry: The RPT entry (resource) to be added to the RPT.
 * @data: Pointer to private data for storing along with the RPT entry.
 * @owndata: boolean flag. true (%KEEP_RPT_DATA) to tell the interface *not*
 * to free the data when the resource is removed. false (%FREE_RPT_DATA) to tell
 * the interface to free the data when the resource is removed.
 *
 * Add a RPT entry to the RPT along with some private data.
 * If an RPT entry with the same resource id exists int the RPT, it will be
 * overlayed with the new RPT entry. Also, this function will assign the
 * resource id as its entry id since it is expected to be unique for the table.
 * The update count and timestamp will not be updated if the entry being added
 * already existed in the table and was the same.
 *
 * Returns: SA_OK on success Or minus SA_OK on error. SA_ERR_HPI_INVALID_PARAMS will
 * be returned if @table is NULL, @entry is NULL, ResourceId in @entry equals
 * SAHPI_FIRST_ENTRY, ResourceId in @entry equals SAHPI_UNSPECIFIED_RESOURCE_ID,
 * or ResourceEntity in @entry has a malformed entity path (look at the
 * entity path utils documentation).
 **/
SaErrorT oh_add_resource(RPTable *table, SaHpiRptEntryT *entry, void *data, int owndata)
{
        RPTEntry *rptentry;
        int update_info = 0;

        if (!table) {
                return SA_ERR_HPI_INVALID_PARAMS;
        } else if (!entry) {
                return SA_ERR_HPI_INVALID_PARAMS;
        } else if (entry->ResourceId == SAHPI_FIRST_ENTRY) {
                return SA_ERR_HPI_INVALID_PARAMS;
        } else if (entry->ResourceId == SAHPI_UNSPECIFIED_RESOURCE_ID) {
                return SA_ERR_HPI_INVALID_PARAMS;
        } else if (!oh_valid_ep(&(entry->ResourceEntity))) {
                return SA_ERR_HPI_INVALID_PARAMS;
        }

        entry->EntryId = entry->ResourceId;
        /* Check to see if the entry is in the RPTable already */
        rptentry = get_rptentry_by_rid(table, entry->ResourceId);
        /* If not, create new RPTEntry */
        if (!rptentry) {
                rptentry = g_new0(RPTEntry, 1);
                if (!rptentry) {
                        return SA_ERR_HPI_OUT_OF_MEMORY;
                }
                update_info = 1; /* Have a new changed entry */
                /* Put new RPTEntry in RPTable */
                table->rptlist = g_slist_append(table->rptlist, (gpointer)rptentry);

                /* Add to rpt hash table */
                if (!table->rptable) /* Create hash table if it doesn't exist */
                        table->rptable = g_hash_table_new(g_int_hash, g_int_equal);

                rptentry->rpt_entry.EntryId = entry->ResourceId;
                g_hash_table_insert(table->rptable,
                                    &(rptentry->rpt_entry.EntryId),
                                    g_slist_last(table->rptlist));
        }
        /* Else, modify existing RPTEntry */
        if (rptentry->data && rptentry->data != data && !rptentry->owndata)
                g_free(rptentry->data);
        rptentry->data = data;
        rptentry->owndata = owndata;
        /* Check if we really have a new/changed entry */
        if (update_info || memcmp(entry, &(rptentry->rpt_entry), sizeof(SaHpiRptEntryT))) {
                update_info = 1;
                rptentry->rpt_entry = *entry;
        }

        if (update_info) update_rptable(table);

        return SA_OK;
}

/**
 * oh_remove_resource
 * @table: Pointer to the RPT from which an RPT entry will be removed.
 * @rid: Resource id of the RPT entry to be removed.
 *
 * Remove a resource from the RPT. If the @rid is
 * %SAHPI_FIRST_ENTRY, the first RPT entry in the table will be removed.
 * The void data will be freed if @owndata was false (%FREE_RPT_DATA) when adding
 * the resource, otherwise if @owndata was true (%KEEP_RPT_DATA) it will not be freed.
 *
 * Returns: SA_OK on success Or minus SA_OK on error.
 **/
SaErrorT oh_remove_resource(RPTable *table, SaHpiResourceIdT rid)
{
        RPTEntry *rptentry;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return SA_ERR_HPI_NOT_PRESENT;
        } else {
                SaHpiRdrT *tmp_rdr;
                /* Remove all RDRs for the resource first */
                while ((tmp_rdr = oh_get_rdr_by_id(table, rid, SAHPI_FIRST_ENTRY)) != NULL) {
                        oh_remove_rdr(table, rid, SAHPI_FIRST_ENTRY);
                }
                /* then remove the resource itself. */
                table->rptlist = g_slist_remove(table->rptlist, (gpointer)rptentry);
                if (!rptentry->owndata) g_free(rptentry->data);
                g_hash_table_remove(table->rptable, &(rptentry->rpt_entry.EntryId));
                g_free((gpointer)rptentry);
                if (!table->rptlist) {
                        g_hash_table_destroy(table->rptable);
                        table->rptable = NULL;
                }
        }

        update_rptable(table);

        return SA_OK;
}

/**
 * oh_get_resource_data
 * @table: Pointer to the RPT for looking up the RPT entry's private data.
 * @rid: Resource id of the RPT entry that holds the private data.
 *
 * Get the private data for a RPT entry.  If the @rid is
 * %SAHPI_FIRST_ENTRY, the first RPT entry's data in the table will be returned.
 *
 * Returns: A void pointer to the private data for the RPT entry requested, or NULL
 * if the RPT entry was not found or the table was a NULL pointer.
 **/
void *oh_get_resource_data(RPTable *table, SaHpiResourceIdT rid)
{

        RPTEntry *rptentry;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                /*DBG("Warning: RPT entry not found. Returning NULL.");*/
                return NULL;
        }

        return rptentry->data;
}

/**
 * oh_get_resource_by_id
 * @table: Pointer to the RPT for looking up the RPT entry.
 * @rid: Resource id of the RPT entry to be looked up.
 *
 * Get a RPT entry from the RPT by using the resource id.
 * If @rid is %SAHPI_FIRST_ENTRY, the first RPT entry in the table will be returned.
 *
 * Returns:
 * Pointer to the RPT entry found or NULL if an RPT entry by that
 * id was not found or the table was a NULL pointer.
 **/
SaHpiRptEntryT *oh_get_resource_by_id(RPTable *table, SaHpiResourceIdT rid)
{
        RPTEntry *rptentry;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                /*DBG("Warning: RPT entry not found. Returning NULL.");*/
                return NULL;
        }

        return &(rptentry->rpt_entry);
}

/**
 * oh_get_resource_by_ep
 * @table: Pointer to the RPT for looking up the RPT entry.
 * @ep: Entity path of the RPT entry to be looked up.
 *
 * Get a RPT entry from the RPT by using the entity path.
 *
 * Returns:
 * Pointer to the RPT entry found or NULL if an RPT entry by that
 * entity path was not found or the table was a NULL pointer.
 **/
SaHpiRptEntryT *oh_get_resource_by_ep(RPTable *table, SaHpiEntityPathT *ep)
{
        RPTEntry *rptentry = NULL;
        GSList *node = NULL;
        SaHpiResourceIdT rid = 0;

        if (!table) {
                return NULL;
        }
        /* Check the uid database first */
        rid = oh_uid_is_initialized() ? oh_uid_lookup(ep) : 0;
        if (rid > 0) {
                /* Found it in uid database */
                return oh_get_resource_by_id(table, rid);
        } else {
                DBG("Didn't find the EP in the Uid table so "
                    "looking manually in the RPTable");
        }

        for (node = table->rptlist; node != NULL; node = node->next) {
                rptentry = (RPTEntry *) node->data;
                if (oh_cmp_ep(&(rptentry->rpt_entry.ResourceEntity), ep))
                        break;
                else rptentry = NULL;
        }

        if (!rptentry) {
                /*DBG("Warning: RPT entry not found. Returning NULL.");*/
                return NULL;
        }

        return &(rptentry->rpt_entry);
}

/**
 * oh_get_resource_next
 * @table: Pointer to the RPT for looking up the RPT entry.
 * @rid_prev: Resource id of the RPT entry previous to the one being looked up.
 *
 * Get the RPT entry next to the specified RPT entry
 * from the RPT. If @rid_prev is %SAHPI_FIRST_ENTRY, the first RPT entry
 * in the table will be returned.
 *
 * Returns:
 * Pointer to the RPT entry found or NULL if the previous RPT entry by that
 * id was not found or the table was a NULL pointer.
 **/
SaHpiRptEntryT *oh_get_resource_next(RPTable *table, SaHpiResourceIdT rid_prev)
{
        RPTEntry *rptentry = NULL;
        GSList *rptnode = NULL;

        if (rid_prev == SAHPI_FIRST_ENTRY) {
                rptentry = get_rptentry_by_rid(table, rid_prev);
        } else {
                rptnode = get_rptnode_by_rid(table, rid_prev);
                if (rptnode && rptnode->next) {
                        rptentry = (RPTEntry *)rptnode->next->data;
                }
        }

        return rptentry ? &(rptentry->rpt_entry) : NULL;
}

/**
 * oh_get_rdr_update_count
 * @table: Pointer to the RPT for looking up the RPT entry.
 * @rid: Resource id of the RPT entry to be looked up.
 * @update_count: pointer of where to place the rdr update count
 *
 * Get a RDR Repository Update Counter for the resource using the resource id.
 *
 * Returns:
 * Returns: SA_OK on success.
 * Will return SA_ERR_HPI_INVALID_PARAMS if update_count is NULL.
 * Will return SA_ERR_HPI_NOT_PRESENT if there is no resource
 * with specified rid in RPT.
 **/
SaErrorT oh_get_rdr_update_count(RPTable *table,
                                 SaHpiResourceIdT rid,
                                 SaHpiUint32T *update_count)
{
    RPTEntry *rptentry = get_rptentry_by_rid(table, rid);
    if ( !rptentry ) {
        return SA_ERR_HPI_NOT_PRESENT;
    }
    if ( !update_count ) {
        return SA_ERR_HPI_INVALID_PARAMS;
    }
    *update_count = rptentry->update_count;
    return SA_OK;
}

/**
 * RDR interface functions
 */

/**
 * oh_add_rdr
 * @table: Pointer to RPT table containig the RPT entry to which the RDR will belong.
 * @rid: Id of the RPT entry that will own the RDR to be added.
 * @rdr: RDR to be added to an RPT entry's RDR repository.
 * @data: Pointer to private data belonging to the RDR that is being added.
 * @owndata: boolean flag. true (%KEEP_RPT_DATA) to tell the interface *not*
 * to free the data when the rdr is removed. false (%FREE_RPT_DATA) to tell
 * the interface to free the data when the rdr is removed.
 *
 * Add an RDR to a RPT entry's RDR repository.
 * If an RDR is found with the same record id as the one being added, the RDR being
 * added will overlay the existing one. Also, a unique record id will be assigned
 * to it based on the RDR type and its type's numeric id.
 * All rdr interface funtions, except oh_add_rdr() will act in the context of
 * the first RPT entry in the table, if @rid is %SAHPI_FIRST_ENTRY.
 *
 * Returns: SA_OK on success Or minus SA_OK on error. Will return
 * SA_ERR_HPI_INVALID_PARAMS if instrument id is invalid. An invalid
 * intrument id for a sensor is in the range of 0x100-0x1FF. An aggregate type
 * of sensor can have its instrument id in the range of 0x100-0x10F, but
 * the resource must have the aggregate sensor capabilitiy bit set.
 **/
SaErrorT oh_add_rdr(RPTable *table, SaHpiResourceIdT rid, SaHpiRdrT *rdr, void *data, int owndata)
{
        RPTEntry *rptentry;
        RDRecord *rdrecord;
        SaHpiInstrumentIdT instr_id;

        if (!rdr) {
                return SA_ERR_HPI_INVALID_PARAMS;
        }

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry){
                return SA_ERR_HPI_NOT_PRESENT;
        }

        if (check_instrument_id(&(rptentry->rpt_entry), rdr)) {
                CRIT("Invalid instrument id found in RDR.");
                return SA_ERR_HPI_INVALID_PARAMS;
        }

        instr_id = oh_get_instrument_id(rdr);

        /* Form correct RecordId. */
        rdr->RecordId = oh_get_rdr_uid(rdr->RdrType, instr_id);
        /* Check if record exists */
        rdrecord = get_rdrecord_by_id(rptentry, rdr->RecordId);
        /* If not, create new rdr */
        if (!rdrecord) {
                rdrecord = g_new0(RDRecord, 1);
                if (!rdrecord) {
                        return SA_ERR_HPI_OUT_OF_MEMORY;
                }
                /* Put new rdrecord in rdr repository */
                rptentry->rdrlist = g_slist_append(rptentry->rdrlist, (gpointer)rdrecord);
                /* Create rdr hash table if first rdr here */
                if (!rptentry->rdrtable)
                        rptentry->rdrtable = g_hash_table_new(g_int_hash, g_int_equal);

                rdrecord->rdr.RecordId = rdr->RecordId;
                g_hash_table_insert(rptentry->rdrtable,
                                    &(rdrecord->rdr.RecordId),
                                    g_slist_last(rptentry->rdrlist));
        }
        /* Else, modify existing rdrecord */
        if (rdrecord->data && rdrecord->data != data && !rdrecord->owndata)
                g_free(rdrecord->data);
        rdrecord->data = data;
        rdrecord->owndata = owndata;
        rdrecord->rdr = *rdr;

        ++rptentry->update_count;

        return SA_OK;
}

/**
 * oh_remove_rdr
 * @table: Pointer to RPT table containig the RPT entry from which the RDR will
 * be removed.
 * @rid: Id of the RPT entry from which the RDR will be removed.
 * @rdrid: Record id of the RDR to remove.
 *
 *
 * Remove an RDR from a RPT entry's RDR repository.
 * If @rdrid is %SAHPI_FIRST_ENTRY, the first RDR in the repository will be removed.
 * If @owndata was set to false (%FREE_RPT_DATA) on the rdr when it was added,
 * the data will be freed, otherwise if it was set to true (%KEEP_RPT_DATA),
 * it will not be freed.
 * All rdr interface funtions, except oh_add_rdr() will act in the context of
 * the first RPT entry in the table, if @rid is %SAHPI_FIRST_ENTRY.
 *
 * Returns: SA_OK on success Or minus SA_OK on error.
 **/
SaErrorT oh_remove_rdr(RPTable *table, SaHpiResourceIdT rid, SaHpiEntryIdT rdrid)
{
        RPTEntry *rptentry;
        RDRecord *rdrecord;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return SA_ERR_HPI_NOT_PRESENT;
        }

        rdrecord = get_rdrecord_by_id(rptentry, rdrid);
        if (!rdrecord) {
                return SA_ERR_HPI_NOT_PRESENT;
        } else {
                rptentry->rdrlist = g_slist_remove(rptentry->rdrlist, (gpointer)rdrecord);
                if (!rdrecord->owndata) g_free(rdrecord->data);
                g_hash_table_remove(rptentry->rdrtable, &(rdrecord->rdr.RecordId));
                g_free((gpointer)rdrecord);
                if (!rptentry->rdrlist) {
                        g_hash_table_destroy(rptentry->rdrtable);
                        rptentry->rdrtable = NULL;
                }
                ++rptentry->update_count;
        }

        return SA_OK;
}

/**
 * oh_get_rdr_data
 * @table: Pointer to RPT table containig the RPT entry from which the RDR's data
 * will be read.
 * @rid: Id of the RPT entry from which the RDR's data will be read.
 * @rdrid: Record id of the RDR to read data from.
 *
 *
 * Get the private data associated to an RDR.
 * If @rdrid is %SAHPI_FIRST_ENTRY, the first RDR's data in the repository will be returned.
 * All rdr interface funtions, except oh_add_rdr() will act in the context of
 * the first RPT entry in the table, if @rid is %SAHPI_FIRST_ENTRY.
 *
 * Returns: A void pointer to the RDR data, or NULL if no data for that RDR was found or
 * the table pointer is NULL.
 **/
void *oh_get_rdr_data(RPTable *table, SaHpiResourceIdT rid, SaHpiEntryIdT rdrid)
{
        RPTEntry *rptentry;
        RDRecord *rdrecord;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return NULL; /* No resource found by that id */
        }

        rdrecord = get_rdrecord_by_id(rptentry, rdrid);
        if (!rdrecord) {
                /*DBG("Warning: RDR not found. Returning NULL.");*/
                return NULL;
        }

        return rdrecord->data;
}

/**
 * oh_get_rdr_by_id
 * @table: Pointer to RPT table containig the RPT entry tha has the RDR
 * being looked up.
 * @rid: Id of the RPT entry containing the RDR being looked up.
 * @rdrid: Record id of the RDR being looked up.
 *
 * Get a reference to an RDR by its record id.
 * If @rdrid is %SAHPI_FIRST_ENTRY, the first RDR in the repository will be returned.
 * All rdr interface funtions, except oh_add_rdr() will act in the context of
 * the first RPT entry in the table, if @rid is %SAHPI_FIRST_ENTRY.
 *
 * Returns:
 * Reference to the RDR looked up or NULL if no RDR was found.
 **/
SaHpiRdrT *oh_get_rdr_by_id(RPTable *table, SaHpiResourceIdT rid, SaHpiEntryIdT rdrid)
{
        RPTEntry *rptentry;
        RDRecord *rdrecord;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return NULL; /* No resource found by that id */
        }

        rdrecord = get_rdrecord_by_id(rptentry, rdrid);
        if (!rdrecord) {
                /*DBG("Warning: RDR not found. Returning NULL.");*/
                return NULL;
        }

        return &(rdrecord->rdr);
}

/**
 * oh_get_rdr_by_type
 * @table: Pointer to RPT table containig the RPT entry tha has the RDR
 * being looked up.
 * @rid: Id of the RPT entry containing the RDR being looked up.
 * @type: RDR Type of the RDR being looked up.
 * @num: RDR id within the RDR type for the specified RPT entry.
 *
 * Get a reference to an RDR by its type and number.
 * All rdr interface funtions, except oh_add_rdr() will act in the context of
 * the first RPT entry in the table, if @rid is %SAHPI_FIRST_ENTRY.
 *
 * Returns:
 * Reference to the RDR looked up or NULL if no RDR was found.
 **/
SaHpiRdrT *oh_get_rdr_by_type(RPTable *table, SaHpiResourceIdT rid,
                              SaHpiRdrTypeT type, SaHpiInstrumentIdT num)
{
        RPTEntry *rptentry = NULL;
        RDRecord *rdrecord = NULL;
        SaHpiEntryIdT rdr_uid;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return NULL; /* No resource found by that id */
        }
        
        /* Get rdr_uid from type/num combination */
        rdr_uid = oh_get_rdr_uid(type, num);
        rdrecord = get_rdrecord_by_id(rptentry, rdr_uid);
        if (!rdrecord) {
                /*DBG("Warning: RDR not found. Returning NULL.");*/
                return NULL;
        }

        return &(rdrecord->rdr);        
}

/**
 * oh_get_rdr_next
 * @table: Pointer to the RPT containing the RPT entry to look up the RDR in.
 * @rid: Id of the RPT entry containing the RDR being looked up.
 * @rdrid_prev: Record id of the RDR previous to the one being looked up, relative
 * to the specified RPT entry.
 *
 * Get the RDR next to the specified RDR in the specified
 * RPT entry's repository. If @rdrid_prev is %SAHPI_FIRST_ENTRY, the first RDR
 * in the repository will be returned.
 * All rdr interface funtions, except oh_add_rdr() will act in the context of
 * the first RPT entry in the table, if @rid is %SAHPI_FIRST_ENTRY.
 *
 * Returns:
 * Pointer to the RDR found or NULL if the previous RDR by that
 * id was not found. If the @rdrid_prev was %SAHPI_FIRST_ENTRY, the first RDR in the list
 * will be returned.
 **/
SaHpiRdrT *oh_get_rdr_next(RPTable *table, SaHpiResourceIdT rid, SaHpiEntryIdT rdrid_prev)
{
        RPTEntry *rptentry = NULL;
        RDRecord *rdrecord = NULL;
        GSList *rdrnode = NULL;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return NULL; /* No resource found by that id */
        }

        if (rdrid_prev == SAHPI_FIRST_ENTRY) {
                rdrecord = get_rdrecord_by_id(rptentry, rdrid_prev);
        } else {
                rdrnode = get_rdrnode_by_id(rptentry, rdrid_prev);
                if (rdrnode && rdrnode->next) {
                        rdrecord = (RDRecord *)rdrnode->next->data;
                }
        }

        return rdrecord ? &(rdrecord->rdr) : NULL;
}

SaHpiRdrT *oh_get_rdr_by_type_first(RPTable *table, SaHpiResourceIdT rid,
                                    SaHpiRdrTypeT type)
{
        RPTEntry *rptentry = NULL;
        RDRecord *rdrecord = NULL;
        GSList *node = NULL;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return NULL; /* No resource found by that id */
        }
        
        /* Get first RDR matching the type */
        for (node = rptentry->rdrlist; node; node = node->next) {
                RDRecord *temp = (RDRecord *)node->data;
                if (temp->rdr.RdrType == type) {
                        rdrecord = temp;
                        break;
                }
        }                
        if (!rdrecord) return NULL;

        return &(rdrecord->rdr);
}

SaHpiRdrT *oh_get_rdr_by_type_next(RPTable *table, SaHpiResourceIdT rid,
                                   SaHpiRdrTypeT type, SaHpiInstrumentIdT num)
{
        RPTEntry *rptentry = NULL;
        RDRecord *rdrecord = NULL;
        GSList *node = NULL;

        rptentry = get_rptentry_by_rid(table, rid);
        if (!rptentry) {
                return NULL; /* No resource found by that id */
        }
        
        /* Get rdr_uid from type/num combination */
        node = get_rdrnode_by_id(rptentry, oh_get_rdr_uid(type, num));
        if (!node) return NULL;
        
        for (node = node->next; node; node = node->next) {
                RDRecord *temp = (RDRecord *)node->data;
                if (temp->rdr.RdrType == type) {
                        rdrecord = temp;
                        break;
                }
        }
        if (!rdrecord) return NULL;

        return &(rdrecord->rdr);
}
