/*
 * User Data plugin
 */

/**
 * \ingroup rpuserdata
 * \page rpusrdatoverview RpUserData Plugin Overview
 *
 * The RpUserData plugin provides functionality for storing user defined
 * data with geometry. The plugin extends RpGeometry and RpWorldSector
 * objects. Notionally, the plugin allows array of \ref RpUserData structures
 * to be attached to these objects. Each \ref RpUserData structure has an
 * identifier string, a data type (int, float, string), a number of elements
 * and a pointer to the data.
 *
 * Requires: Core Library and RpWorld Plugin.
 *
 */

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

#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include "rpusrdat.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: rpusrdat.c,v 1.15 2001/07/18 14:32:04 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

#define RPGEOMETRYGETUSERDATALIST(geometry) \
    ((RpUserDataList *)(((RwUInt8 *)geometry) + \
                         userDataGeometryOffset))

#define RPGEOMETRYGETCONSTUSERDATALIST(geometry) \
    ((const RpUserDataList *)(((const RwUInt8 *)geometry) + \
                                userDataGeometryOffset))

#define RPWORLDSECTORGETUSERDATALIST(sector) \
    ((RpUserDataList *)(((RwUInt8 *)sector) + userDataWorldSectorOffset))

#define RPWORLDSECTORGETCONSTUSERDATALIST(sector) \
    ((const RpUserDataList *)(((const RwUInt8 *)sector) + userDataWorldSectorOffset))

#define RWFRAMEGETUSERDATALIST(frame) \
    ((RpUserDataList *)(((RwUInt8 *)frame) + \
                         userDataFrameOffset))

#define RWFRAMEGETCONSTUSERDATALIST(frame) \
    ((const RpUserDataList *)(((const RwUInt8 *)frame) + \
                                userDataFrameOffset))

#if (defined(RWDEBUG))
long                rpUserDataStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

RwModuleInfo        userDataModule;

static RwInt32      userDataGeometryOffset = 0;
static RwInt32      userDataGeometryStreamOffset = 0;

static RwInt32      userDataWorldSectorOffset = 0;
static RwInt32      userDataWorldSectorStreamOffset = 0;

static RwInt32      userDataFrameOffset = 0;
static RwInt32      userDataFrameStreamOffset = 0;

typedef struct RpUserDataList RpUserDataList;

struct RpUserDataList
{
    RwInt32             numElements;
    RpUserDataArray    *userData;
};

/* User Data functions */

static void        *
UserDataOpen(void *instance, RwInt32 __RWUNUSED__ offset,
             RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("UserDataOpen"));
    RWASSERT(instance);

    userDataModule.numInstances++;

    RWRETURN(instance);
}

static void        *
UserDataClose(void *instance, RwInt32 __RWUNUSED__ offset,
              RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("UserDataClose"));
    RWASSERT(instance);

    userDataModule.numInstances--;

    RWRETURN(instance);
}

static void
UserDataDestruct(RpUserDataArray * userData)
{
    RWFUNCTION(RWSTRING("UserDataDestruct"));

    if (userData->name != NULL)
    {
        RwFree(userData->name);
    }

    if (userData->format == rpSTRINGUSERDATA)
    {
        RwInt32             i;
        RwChar            **charData = (RwChar **) userData->data;

        for (i = 0; i < userData->numElements; i++)
        {
            if (charData[i] != NULL)
            {
                RwFree(charData[i]);
            }
        }
    }

    if (userData->data != NULL)
    {
        RwFree(userData->data);
    }

    RWRETURNVOID();
}

static void
UserDataCopy(RpUserDataArray * dstUserData,
             RpUserDataArray * srcUserData)
{
    RwInt32             dataSize;

    RWFUNCTION(RWSTRING("UserDataCopy"));

    /* dstUserData must be empty */

    dstUserData->format = srcUserData->format;
    dstUserData->numElements = srcUserData->numElements;

    if (srcUserData->name != NULL)
    {
        rwstrdup(dstUserData->name, srcUserData->name);
    }

    if (srcUserData->data != NULL)
    {
        dataSize =
            dstUserData->numElements *
            RpUserDataGetFormatSize(dstUserData->format);

        dstUserData->data = RwMalloc(dataSize);

        if (dstUserData->format == rpSTRINGUSERDATA)
        {
            RwInt32             i;
            RwChar            **srcCharData =
                (RwChar **) srcUserData->data;
            RwChar            **dstCharData =
                (RwChar **) dstUserData->data;

            for (i = 0; i < dstUserData->numElements; i++)
            {
                if (srcCharData[i] == NULL)
                {
                    dstCharData[i] = (RwChar *) NULL;
                }
                else
                {
                    rwstrdup(dstCharData[i], srcCharData[i]);
                }
            }
        }
        else
        {
            memcpy(dstUserData->data, srcUserData->data, dataSize);
        }
    }

    RWRETURNVOID();
}

static RwStream    *
UserDataStreamRead(RpUserDataArray * userData, RwStream * stream)
{
    RwInt32             i, length;
    RwInt32            *intData;
    RwReal             *realData;
    RwChar            **charData;

    RWFUNCTION(RWSTRING("UserDataStreamRead"));

    if (!RwStreamReadInt(stream, &length, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    if (length > 0)
    {
        userData->name = (RwChar *) RwMalloc(sizeof(RwChar) * length);

        if (userData->name == NULL)
        {
            RWRETURN((RwStream *) NULL);
        }

        if (!RwStreamRead
            (stream, userData->name, sizeof(RwChar) * length))
        {
            RWRETURN((RwStream *) NULL);
        }
    }

    if (!RwStreamReadInt
        (stream, (RwInt32 *) & userData->format, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    if (!RwStreamReadInt
        (stream, &userData->numElements, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    switch (userData->format)
    {
        case rpINTUSERDATA:
            userData->data =
                RwMalloc(sizeof(RwInt32) * userData->numElements);

            if (userData->data == NULL)
            {
                RWRETURN((RwStream *) NULL);
            }

            intData = (RwInt32 *) userData->data;

            for (i = 0; i < userData->numElements; i++)
            {
                if (!RwStreamReadInt
                    (stream, &intData[i], sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *) NULL);
                }

            }
            break;
        case rpREALUSERDATA:
            userData->data =
                RwMalloc(sizeof(RwReal) * userData->numElements);

            if (userData->data == NULL)
            {
                RWRETURN((RwStream *) NULL);
            }

            realData = (RwReal *) userData->data;

            for (i = 0; i < userData->numElements; i++)
            {
                if (!RwStreamReadReal
                    (stream, &realData[i], sizeof(RwReal)))
                {
                    RWRETURN((RwStream *) NULL);
                }
            }
            break;
        case rpSTRINGUSERDATA:
            userData->data =
                RwMalloc(sizeof(RwChar *) * userData->numElements);

            if (userData->data == NULL)
            {
                RWRETURN((RwStream *) NULL);
            }

            charData = (RwChar **) userData->data;

            for (i = 0; i < userData->numElements; i++)
            {
                if (!RwStreamReadInt(stream, &length, sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *) NULL);
                }

                if (length > 0)
                {
                    charData[i] =
                        (RwChar *) RwMalloc(sizeof(RwChar) * length);

                    if (charData[i] == NULL)
                    {
                        RWRETURN((RwStream *) NULL);
                    }

                    if (!RwStreamRead
                        (stream, charData[i], sizeof(RwChar) * length))
                    {
                        RWRETURN((RwStream *) NULL);
                    }
                }
                else
                {
                    charData[i] = (RwChar *) NULL;
                }
            }
            break;
        default:
            RWRETURN((RwStream *) NULL);
            break;
    }

    RWRETURN(stream);
}

static RwStream    *
UserDataStreamWrite(RpUserDataArray * userData, RwStream * stream)
{
    RwInt32             i, length;
    RwInt32            *intData;
    RwReal             *realData;
    RwChar            **charData;

    RWFUNCTION(RWSTRING("UserDataStreamWrite"));

    if (userData->name != NULL)
    {
        length = rwstrlen(userData->name) + 1;
    }
    else
    {
        length = 0;
    }

    if (!RwStreamWriteInt(stream, &length, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    if (length > 0)
    {
        if (!RwStreamWrite
            (stream, userData->name, sizeof(RwChar) * length))
        {
            RWRETURN((RwStream *) NULL);
        }
    }

    if (!RwStreamWriteInt
        (stream, (RwInt32 *) & userData->format, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    if (!RwStreamWriteInt
        (stream, &userData->numElements, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    switch (userData->format)
    {
        case rpINTUSERDATA:
            intData = (RwInt32 *) userData->data;
            for (i = 0; i < userData->numElements; i++)
            {
                if (!RwStreamWriteInt
                    (stream, &intData[i], sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *) NULL);
                }

            }
            break;
        case rpREALUSERDATA:
            realData = (RwReal *) userData->data;
            for (i = 0; i < userData->numElements; i++)
            {
                if (!RwStreamWriteReal
                    (stream, &realData[i], sizeof(RwReal)))
                {
                    RWRETURN((RwStream *) NULL);
                }
            }
            break;
        case rpSTRINGUSERDATA:
            charData = (RwChar **) userData->data;
            for (i = 0; i < userData->numElements; i++)
            {
                if (charData[i] != NULL)
                {
                    length = rwstrlen(charData[i]) + 1;
                }
                else
                {
                    length = 0;
                }

                if (!RwStreamWrite(stream, &length, sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *) NULL);
                }

                if (length > 0)
                {
                    if (!RwStreamWrite
                        (stream, charData[i], sizeof(RwChar) * length))
                    {
                        RWRETURN((RwStream *) NULL);
                    }
                }
            }
            break;
        default:
            RWRETURN((RwStream *) NULL);
            break;
    }

    RWRETURN(stream);
}

static              RwInt32
UserDataGetSize(RpUserDataArray * userData)
{
    RwInt32             i, length;
    RwInt32             size = 0;
    RwChar            **charData;

    RWFUNCTION(RWSTRING("UserDataGetSize"));

    if (userData != NULL)
    {
        size += sizeof(RwInt32);

        if (userData->name != NULL)
        {
            length = rwstrlen(userData->name) + 1;
            size += (sizeof(RwChar) * length);
        }

        size += (2 * sizeof(RwInt32));

        switch (userData->format)
        {
            case rpINTUSERDATA:
                size += (sizeof(RwInt32) * userData->numElements);
                break;
            case rpREALUSERDATA:
                size += (sizeof(RwReal) * userData->numElements);
                break;
            case rpSTRINGUSERDATA:
                charData = (RwChar **) userData->data;
                for (i = 0; i < userData->numElements; i++)
                {
                    size += sizeof(RwInt32);

                    if (charData[i] != NULL)
                    {
                        length = rwstrlen(charData[i]) + 1;

                        size += (sizeof(RwChar) * length);
                    }
                }
                break;
            default:
                break;
        }
    }

    RWRETURN(size);
}

/* User Data list functions */

static void
UserDataListConstruct(RpUserDataList * userDataList)
{
    RWFUNCTION(RWSTRING("UserDataListConstruct"));

    userDataList->numElements = 0;
    userDataList->userData = (RpUserDataArray *) NULL;

    RWRETURNVOID();
}

static void
UserDataListDestruct(RpUserDataList * userDataList)
{
    RwInt32             i;

    RWFUNCTION(RWSTRING("UserDataListDestruct"));

    if (userDataList->userData != NULL)
    {
        for (i = 0; i < userDataList->numElements; i++)
        {
            UserDataDestruct(&userDataList->userData[i]);
        }

        RwFree(userDataList->userData);
    }

    RWRETURNVOID();
}

static void
UserDataListCopy(RpUserDataList * dstList,
                 const RpUserDataList * srcList)
{
    RwInt32             i;

    RWFUNCTION(RWSTRING("UserDataListCopy"));

    UserDataListDestruct(dstList);

    dstList->numElements = srcList->numElements;

    if (dstList->numElements > 0)
    {
        dstList->userData = (RpUserDataArray *)
            RwMalloc(sizeof(RpUserDataArray) * dstList->numElements);

        for (i = 0; i < dstList->numElements; i++)
        {
            UserDataCopy(&(dstList->userData[i]),
                         &(srcList->userData[i]));
        }
    }

    RWRETURNVOID();
}

static RwStream    *
UserDataListStreamRead(RpUserDataList * list, RwStream * stream)
{
    RwInt32             i, numElements;

    RWFUNCTION(RWSTRING("UserDataListStreamRead"));

    if (list != NULL)
    {
        if (!RwStreamReadInt(stream, &numElements, sizeof(RwInt32)))
        {
            RWRETURN((RwStream *) NULL);
        }

        list->numElements = numElements;

        list->userData = (RpUserDataArray *)
            RwMalloc(sizeof(RpUserDataArray) * list->numElements);

        for (i = 0; i < list->numElements; i++)
        {
            stream = UserDataStreamRead(&list->userData[i], stream);
        }

        RWRETURN(stream);
    }
    RWRETURN((RwStream *) NULL);
}

static RwStream    *
UserDataListStreamWrite(const RpUserDataList * list, RwStream * stream)
{
    RwInt32             i;

    RWFUNCTION(RWSTRING("UserDataListStreamWrite"));

    if (list != NULL)
    {
        if (!RwStreamWriteInt
            (stream, &list->numElements, sizeof(RwInt32)))
        {
            RWRETURN((RwStream *) NULL);
        }

        for (i = 0; i < list->numElements; i++)
        {
            stream = UserDataStreamWrite(&list->userData[i], stream);
        }
        RWRETURN(stream);
    }
    RWRETURN((RwStream *) NULL);
}

static              RwInt32
UserDataListGetSize(const RpUserDataList * list)
{
    RwInt32             i;
    RwInt32             size = 0;

    RWFUNCTION(RWSTRING("UserDataListGetSize"));

    if (list != NULL)
    {
        size += sizeof(RwInt32);

        for (i = 0; i < list->numElements; i++)
        {
            size += UserDataGetSize(&list->userData[i]);
        }
    }

    RWRETURN(size);
}

static              RwInt32
UserDataListAddElement(RpUserDataList * list, RwChar * name,
                       RpUserDataFormat format, RwInt32 numElements)
{
    void               *newUserData = NULL;
    RpUserDataArray    *userData;

    RWFUNCTION(RWSTRING("UserDataListAddElement"));

    if (list->userData != NULL)
    {
        newUserData = RwMalloc(sizeof(RpUserDataArray) *
                               (list->numElements + 1));

        if (newUserData == NULL)
        {
            RWRETURN(-1);
        }

        memcpy(newUserData,
               list->userData,
               sizeof(RpUserDataArray) * (list->numElements));

        RwFree(list->userData);
        list->userData = (RpUserDataArray *) newUserData;
    }
    else
    {
        list->userData = (RpUserDataArray *)
            RwMalloc(sizeof(RpUserDataArray) * (list->numElements + 1));

        if (list->userData == NULL)
        {
            RWRETURN(-1);
        }
    }
    userData = &list->userData[list->numElements];

    userData->data = RwMalloc(RpUserDataGetFormatSize(format) *
                              numElements);

    if (userData->data == NULL)
    {
        RWRETURN(-1);
    }

    rwstrdup(userData->name, name);
    userData->format = format;
    userData->numElements = numElements;

    list->numElements++;

    RWRETURN(list->numElements - 1);
}

/* User Data Geometry functions */

static void        *
UserDataGeometryConstruct(void *object, RwInt32 __RWUNUSED__ offset,
                          RwInt32 __RWUNUSED__ size)
{
    RpGeometry         *geometry;
    RpUserDataList     *userDataList;

    RWFUNCTION(RWSTRING("UserDataGeometryConstruct"));
    RWASSERT(object);

    geometry = (RpGeometry *) object;

    userDataList = RPGEOMETRYGETUSERDATALIST(geometry);

    UserDataListConstruct(userDataList);

    RWRETURN(object);
}

static void        *
UserDataGeometryDestruct(void *object, RwInt32 __RWUNUSED__ offset,
                         RwInt32 __RWUNUSED__ size)
{
    RpGeometry         *geometry;
    RpUserDataList     *userDataList;

    RWFUNCTION(RWSTRING("UserDataGeometryDestruct"));
    RWASSERT(object);

    geometry = (RpGeometry *) object;

    userDataList = RPGEOMETRYGETUSERDATALIST(geometry);

    UserDataListDestruct(userDataList);

    RWRETURN(object);
}

static void        *
UserDataGeometryCopy(void *destinationObject, const void *sourceObject,
                     RwInt32 __RWUNUSED__ offset,
                     RwInt32 __RWUNUSED__ size)
{
    RpGeometry         *dstGeometry;
    const RpGeometry   *srcGeometry;
    RpUserDataList     *dstUserDataList;
    const RpUserDataList *srcUserDataList;

    RWFUNCTION(RWSTRING("UserDataGeometryCopy"));
    RWASSERT(destinationObject);
    RWASSERT(sourceObject);

    dstGeometry = (RpGeometry *) destinationObject;
    srcGeometry = (const RpGeometry *) sourceObject;

    dstUserDataList = RPGEOMETRYGETUSERDATALIST(dstGeometry);
    srcUserDataList = RPGEOMETRYGETCONSTUSERDATALIST(srcGeometry);

    UserDataListCopy(dstUserDataList, srcUserDataList);

    RWRETURN(destinationObject);
}

static RwStream    *
UserDataGeometryStreamRead(RwStream * stream,
                           RwInt32 __RWUNUSED__ binaryLength,
                           void *object, RwInt32 __RWUNUSED__ offset,
                           RwInt32 __RWUNUSED__ size)
{
    RpGeometry         *geometry;
    RpUserDataList     *userDataList;

    RWFUNCTION(RWSTRING("UserDataGeometryStreamRead"));
    RWASSERT(stream);
    RWASSERT(object);

    geometry = (RpGeometry *) object;

    userDataList = RPGEOMETRYGETUSERDATALIST(geometry);

    UserDataListStreamRead(userDataList, stream);

    RWRETURN(stream);
}

static RwStream    *
UserDataGeometryStreamWrite(RwStream * stream,
                            RwInt32 __RWUNUSED__ binaryLength,
                            const void *object,
                            RwInt32 __RWUNUSED__ offset,
                            RwInt32 __RWUNUSED__ size)
{
    const RpGeometry   *geometry;
    const RpUserDataList *userDataList;

    RWFUNCTION(RWSTRING("UserDataGeometryStreamWrite"));
    RWASSERT(stream);
    RWASSERT(object);

    geometry = (const RpGeometry *) object;

    userDataList = RPGEOMETRYGETCONSTUSERDATALIST(geometry);

    UserDataListStreamWrite(userDataList, stream);

    RWRETURN(stream);
}

static              RwInt32
UserDataGeometryGetSize(const void *object,
                        RwInt32 __RWUNUSED__ offsetInObject,
                        RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpGeometry   *geometry;
    const RpUserDataList *userDataList;
    RwInt32             size = 0;

    RWFUNCTION(RWSTRING("UserDataGeometryGetSize"));
    RWASSERT(object);

    geometry = (const RpGeometry *) object;

    userDataList = RPGEOMETRYGETCONSTUSERDATALIST(geometry);

    size += UserDataListGetSize(userDataList);

    RWRETURN(size);
}

/* User Data World Sector functions */

static void        *
UserDataWorldSectorConstruct(void *object, RwInt32 __RWUNUSED__ offset,
                             RwInt32 __RWUNUSED__ size)
{
    RpWorldSector      *sector;
    RpUserDataList     *userDataList;

    RWFUNCTION(RWSTRING("UserDataWorldSectorConstruct"));
    RWASSERT(object);

    sector = (RpWorldSector *) object;

    userDataList = RPWORLDSECTORGETUSERDATALIST(sector);

    UserDataListConstruct(userDataList);

    RWRETURN(object);
}

static void        *
UserDataWorldSectorDestruct(void *object, RwInt32 __RWUNUSED__ offset,
                            RwInt32 __RWUNUSED__ size)
{
    RpWorldSector      *sector;
    RpUserDataList     *userDataList;

    RWFUNCTION(RWSTRING("UserDataWorldSectorDestruct"));
    RWASSERT(object);

    sector = (RpWorldSector *) object;

    userDataList = RPWORLDSECTORGETUSERDATALIST(sector);

    UserDataListDestruct(userDataList);

    RWRETURN(object);
}

static void        *
UserDataWorldSectorCopy(void *destinationObject,
                        const void *sourceObject,
                        RwInt32 __RWUNUSED__ offset,
                        RwInt32 __RWUNUSED__ size)
{
    RpWorldSector      *dstSector;
    const RpWorldSector *srcSector;
    RpUserDataList     *dstUserDataList;
    const RpUserDataList *srcUserDataList;

    RWFUNCTION(RWSTRING("UserDataWorldSectorCopy"));
    RWASSERT(destinationObject);
    RWASSERT(sourceObject);

    dstSector = (RpWorldSector *) destinationObject;
    srcSector = (const RpWorldSector *) sourceObject;

    dstUserDataList = RPWORLDSECTORGETUSERDATALIST(dstSector);
    srcUserDataList = RPWORLDSECTORGETCONSTUSERDATALIST(srcSector);

    UserDataListCopy(dstUserDataList, srcUserDataList);

    RWRETURN(destinationObject);
}

static RwStream    *
UserDataWorldSectorStreamRead(RwStream * stream,
                              RwInt32 __RWUNUSED__ binaryLength,
                              void *object, RwInt32 __RWUNUSED__ offset,
                              RwInt32 __RWUNUSED__ size)
{
    RpWorldSector      *sector;
    RpUserDataList     *userDataList;

    RWFUNCTION(RWSTRING("UserDataWorldSectorStreamRead"));
    RWASSERT(stream);
    RWASSERT(object);

    sector = (RpWorldSector *) object;

    userDataList = RPWORLDSECTORGETUSERDATALIST(sector);

    UserDataListStreamRead(userDataList, stream);

    RWRETURN(stream);
}

static RwStream    *
UserDataWorldSectorStreamWrite(RwStream * stream,
                               RwInt32 __RWUNUSED__ binaryLength,
                               const void *object,
                               RwInt32 __RWUNUSED__ offset,
                               RwInt32 __RWUNUSED__ size)
{
    const RpWorldSector *sector;
    const RpUserDataList *userDataList;

    RWFUNCTION(RWSTRING("UserDataWorldSectorStreamWrite"));
    RWASSERT(stream);
    RWASSERT(object);

    sector = (const RpWorldSector *) object;

    userDataList = RPWORLDSECTORGETCONSTUSERDATALIST(sector);

    UserDataListStreamWrite(userDataList, stream);

    RWRETURN(stream);
}

static              RwInt32
UserDataWorldSectorGetSize(const void *object,
                           RwInt32 __RWUNUSED__ offsetInObject,
                           RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpWorldSector *sector;
    const RpUserDataList *userDataList;
    RwInt32             size = 0;

    RWFUNCTION(RWSTRING("UserDataWorldSectorGetSize"));
    RWASSERT(object);

    sector = (const RpWorldSector *) object;

    userDataList = RPWORLDSECTORGETCONSTUSERDATALIST(sector);

    size += UserDataListGetSize(userDataList);

    RWRETURN(size);
}

/* RwFrame API */

static void        *
UserDataFrameConstruct(void *object, RwInt32 __RWUNUSED__ offset,
                        RwInt32 __RWUNUSED__ size)
{
    RwFrame             *frame;
    RpUserDataList      *userDataList;

    RWFUNCTION(RWSTRING("UserDataFrameConstruct"));
    RWASSERT(object);

    frame = (RwFrame *) object;

    userDataList = RWFRAMEGETUSERDATALIST(frame);

    UserDataListConstruct(userDataList);

    RWRETURN(object);
}

static void        *
UserDataFrameDestruct(void *object, RwInt32 __RWUNUSED__ offset,
                        RwInt32 __RWUNUSED__ size)
{
    RwFrame         *frame;
    RpUserDataList  *userDataList;

    RWFUNCTION(RWSTRING("UserDataFrameDestruct"));
    RWASSERT(object);

    frame = (RwFrame *) object;

    userDataList = RWFRAMEGETUSERDATALIST(frame);

    UserDataListDestruct(userDataList);

    RWRETURN(object);
}

static void        *
UserDataFrameCopy(void *destinationObject,
                    const void *sourceObject,
                    RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    RwFrame                 *dstFrame;
    const RwFrame           *srcFrame;
    RpUserDataList          *dstUserDataList;
    const RpUserDataList    *srcUserDataList;

    RWFUNCTION(RWSTRING("UserDataFrameCopy"));
    RWASSERT(destinationObject);
    RWASSERT(sourceObject);

    dstFrame = (RwFrame *) destinationObject;
    srcFrame = (const RwFrame *) sourceObject;

    dstUserDataList = RWFRAMEGETUSERDATALIST(dstFrame);
    srcUserDataList = RWFRAMEGETCONSTUSERDATALIST(srcFrame);

    UserDataListCopy(dstUserDataList, srcUserDataList);

    RWRETURN(destinationObject);
}

static RwStream    *
UserDataFrameStreamRead(RwStream * stream,
                        RwInt32 __RWUNUSED__ binaryLength,
                        void *object, RwInt32 __RWUNUSED__ offset,
                        RwInt32 __RWUNUSED__ size)
{
    RwFrame         *frame;
    RpUserDataList  *userDataList;

    RWFUNCTION(RWSTRING("UserDataFrameStreamRead"));
    RWASSERT(stream);
    RWASSERT(object);

    frame = (RwFrame *) object;

    userDataList = RWFRAMEGETUSERDATALIST(frame);

    UserDataListStreamRead(userDataList, stream);

    RWRETURN(stream);
}

static RwStream    *
UserDataFrameStreamWrite(RwStream * stream,
                        RwInt32 __RWUNUSED__ binaryLength,
                        const void *object,
                        RwInt32 __RWUNUSED__ offset,
                        RwInt32 __RWUNUSED__ size)
{
    const RwFrame           *frame;
    const RpUserDataList    *userDataList;

    RWFUNCTION(RWSTRING("UserDataFrameStreamWrite"));
    RWASSERT(stream);
    RWASSERT(object);

    frame = (const RwFrame *) object;

    userDataList = RWFRAMEGETCONSTUSERDATALIST(frame);

    UserDataListStreamWrite(userDataList, stream);

    RWRETURN(stream);
}

static              RwInt32
UserDataFrameGetSize(const void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    const RwFrame           *frame;
    const RpUserDataList    *userDataList;
    RwInt32                 size = 0;

    RWFUNCTION(RWSTRING("UserDataFrameGetSize"));
    RWASSERT(object);

    frame = (const RwFrame *) object;

    userDataList = RWFRAMEGETCONSTUSERDATALIST(frame);

    size += UserDataListGetSize(userDataList);

    RWRETURN(size);
}

/* Geometry API */

/**
 * \ingroup rpuserdata
 * \ref RpGeometryAddUserDataArray is used to allocate and add a new
 * array of user data to a geometry object.
 * The UserData plugin must be attached before using this function.
 *
 * \param geometry the RpGeometry object
 * \param name an identifier string for the the user data
 * \param format the format of the user data array elements
 * \param numElements the number of elements on the user data array
 *
 * \return an index to the UserData on success, -1 on failure.
 *
 * \see RpGeometryGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RpGeometryAddUserDataArray(RpGeometry * geometry, RwChar * name,
                           RpUserDataFormat format, RwInt32 numElements)
{
    RwInt32             index;
    RpUserDataList     *userDataList;

    RWAPIFUNCTION(RWSTRING("RpGeometryAddUserDataArray"));
    RWASSERT(geometry);

    userDataList = RPGEOMETRYGETUSERDATALIST(geometry);

    index =
        UserDataListAddElement(userDataList, name, format, numElements);

    RWRETURN(index);
}

/**
 * \ingroup rpuserdata
 * \ref RpGeometryGetUserDataArrayCount is used to retrive the number of
 * user data arrays stored with a geometry.
 * The UserData plugin must be attached before using this function.
 *
 * \param geometry the RpGeometry object
 *
 * \return the number of user data arrays stored with the geometry
 *
 * \see RpGeometryGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RpGeometryGetUserDataArrayCount(const RpGeometry * geometry)
{
    const RpUserDataList *userDataList;

    RWAPIFUNCTION(RWSTRING("RpGeometryGetUserDataArrayCount"));
    RWASSERT(geometry);

    userDataList = RPGEOMETRYGETCONSTUSERDATALIST(geometry);

    RWRETURN(userDataList->numElements);
}

/**
 * \ingroup rpuserdata
 * \ref RpGeometryGetUserDataArray is used to retrive a UserDataArray object
 * stored on a geometry.
 * The UserData plugin must be attached before using this function.
 *
 * \param geometry the RpGeometry object
 * \param data the index of the UserDataArray object to retrieve
 *
 * \return the UserData object if it exists, NULL otherwise
 *
 * \see RpGeometryGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RpUserDataArray    *
RpGeometryGetUserDataArray(const RpGeometry * geometry, RwInt32 data)
{
    const RpUserDataList *userDataList;

    RWAPIFUNCTION(RWSTRING("RpGeometryGetUserDataArray"));
    RWASSERT(geometry);

    userDataList = RPGEOMETRYGETCONSTUSERDATALIST(geometry);

    if (data < userDataList->numElements)
    {
        RWRETURN(&(userDataList->userData[data]));
    }
    else
    {
        RWRETURN((RpUserDataArray *) NULL);
    }
}

/* World Sector API */

/**
 * \ingroup rpuserdata
 * \ref RpWorldSectorAddUserDataArray is used to allocate and add a new
 * array of user data to a world sector object.
 * The UserData plugin must be attached before using this function.
 *
 * \param sector the RpWorldSector object
 * \param name an identifier string for the the user data array
 * \param format the format of the user data array elements
 * \param numElements the number of elements on the user data array
 *
 * \return an index to the UserData on success, -1 on failure.
 *
 * \see RpWorldSectorGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RpWorldSectorAddUserDataArray(RpWorldSector * sector, RwChar * name,
                              RpUserDataFormat format,
                              RwInt32 numElements)
{
    RwInt32             index;
    RpUserDataList     *userDataList;

    RWAPIFUNCTION(RWSTRING("RpWorldSectorAddUserDataArray"));
    RWASSERT(sector);

    userDataList = RPWORLDSECTORGETUSERDATALIST(sector);

    index =
        UserDataListAddElement(userDataList, name, format, numElements);

    RWRETURN(index);
}

/**
 * \ingroup rpuserdata
 * \ref RpWorldSectorGetUserDataArrayCount is used to retrive the number of
 * user data arrays stored with a world sector.
 * The UserData plugin must be attached before using this function.
 *
 * \param sector the RpWorldSector object
 *
 * \return the number of user data arrays stored with the world sector
 *
 * \see RpWorldSectorGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RpWorldSectorGetUserDataArrayCount(const RpWorldSector * sector)
{
    const RpUserDataList *userDataList;

    RWAPIFUNCTION(RWSTRING("RpWorldSectorGetUserDataArrayCount"));
    RWASSERT(sector);

    userDataList = RPWORLDSECTORGETCONSTUSERDATALIST(sector);

    RWRETURN(userDataList->numElements);
}

/**
 * \ingroup rpuserdata
 * \ref RpWorldSectorGetUserDataArray is used to retrive a UserDataArray
 * object stored on a world sector.
 * The UserData plugin must be attached before using this function.
 *
 * \param sector the RpWorldSector object
 * \param data the index of the UserData object to retrieve
 *
 * \return the UserData object if it exists, NULL otherwise
 *
 * \see RpWorldSectorGetUserDataArrayCount
 * \see RpUserDataPluginAttach
 */
RpUserDataArray    *
RpWorldSectorGetUserDataArray(const RpWorldSector * sector,
                              RwInt32 data)
{
    const RpUserDataList *userDataList;

    RWAPIFUNCTION(RWSTRING("RpWorldSectorGetUserDataArray"));
    RWASSERT(sector);

    userDataList = RPWORLDSECTORGETCONSTUSERDATALIST(sector);

    if (data < userDataList->numElements)
    {
        RWRETURN(&(userDataList->userData[data]));
    }
    else
    {
        RWRETURN((RpUserDataArray *) NULL);
    }
}


/* Frame API */

/**
 * \ingroup rpuserdata
 * \ref RwFrameAddUserDataArray is used to allocate and add a new
 * array of user data to a frame object.
 * The UserData plugin must be attached before using this function.
 *
 * \param frame the RwFrame object
 * \param name an identifier string for the the user data array
 * \param format the format of the user data array elements
 * \param numElements the number of elements on the user data array
 *
 * \return an index to the UserData on success, -1 on failure.
 *
 * \see RwFrameGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RwFrameAddUserDataArray(RwFrame * frame, RwChar * name,
                        RpUserDataFormat format,
                        RwInt32 numElements)
{
    RwInt32             index;
    RpUserDataList     *userDataList;

    RWAPIFUNCTION(RWSTRING("RwFrameAddUserDataArray"));
    RWASSERT(frame);

    userDataList = RWFRAMEGETUSERDATALIST(frame);

    index =
        UserDataListAddElement(userDataList, name, format, numElements);

    RWRETURN(index);
}

/**
 * \ingroup rpuserdata
 * \ref RwFrameGetUserDataArrayCount is used to retrive the number of
 * user data arrays stored with a frame.
 * The UserData plugin must be attached before using this function.
 *
 * \param frame the RwFrame object
 *
 * \return the number of user data arrays stored with the frame
 *
 * \see RwFrameGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RwFrameGetUserDataArrayCount(const RwFrame * frame)
{
    const RpUserDataList *userDataList;

    RWAPIFUNCTION(RWSTRING("RwFrameGetUserDataArrayCount"));
    RWASSERT(frame);

    userDataList = RWFRAMEGETCONSTUSERDATALIST(frame);

    RWRETURN(userDataList->numElements);
}

/**
 * \ingroup rpuserdata
 * \ref RwFrameGetUserDataArray is used to retrive a UserDataArray
 * object stored on a frame.
 * The UserData plugin must be attached before using this function.
 *
 * \param frame the RwFrame object
 * \param data the index of the UserData object to retrieve
 *
 * \return the UserData object if it exists, NULL otherwise
 *
 * \see RwFrameGetUserDataArrayCount
 * \see RpUserDataPluginAttach
 */
RpUserDataArray    *
RwFrameGetUserDataArray(const RwFrame * frame,
                        RwInt32 data)
{
    const RpUserDataList *userDataList;

    RWAPIFUNCTION(RWSTRING("RwFrameGetUserDataArray"));
    RWASSERT(frame);

    userDataList = RWFRAMEGETCONSTUSERDATALIST(frame);

    if (data < userDataList->numElements)
    {
        RWRETURN(&(userDataList->userData[data]));
    }
    else
    {
        RWRETURN((RpUserDataArray *) NULL);
    }
}

/* User Data Array API */

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArrayGetName is used to retrieve the identifier string
 * attached to a a UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 *
 * \return the identifier string of the UserDataArray object.
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwChar             *
RpUserDataArrayGetName(RpUserDataArray * userData)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArrayGetName"));
    RWASSERT(userData);

    RWRETURN(userData->name);
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArrayGetFormat is used to get format of the data elements
 * stored by a UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 *
 * \return the format of the elements stored by the UserDataArray object.
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RpUserDataFormat
RpUserDataArrayGetFormat(RpUserDataArray * userData)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArrayGetFormat"));
    RWASSERT(userData);

    RWRETURN(userData->format);
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArrayGetNumElements
 * is used to get the number of data elements
 * stored by a UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 *
 * \return the number of elements stored by the UserData object.
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataPluginAttach
 */
RwInt32
RpUserDataArrayGetNumElements(RpUserDataArray * userData)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArrayGetNumElements"));
    RWASSERT(userData);

    RWRETURN(userData->numElements);
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArrayGetInt is used to get the integer stored at entry
 * index in the UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 * \param index the index in the data array to be retrieved
 *
 * \return the integer element
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataArraySetInt
 * \see RpUserDataPluginAttach
 */
RwInt32
RpUserDataArrayGetInt(RpUserDataArray * userData, RwInt32 index)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArrayGetInt"));

    RWASSERT(userData);
    RWASSERT(index < userData->numElements);
    RWASSERT(userData->format == rpINTUSERDATA);

    RWRETURN(((RwInt32 *) userData->data)[index]);
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArrayGetReal is used to get the real stored at entry
 * index in the UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 * \param index the index in the data array to be retrieved
 *
 * \return the real element
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataArraySetReal
 * \see RpUserDataPluginAttach
 */
RwReal
RpUserDataArrayGetReal(RpUserDataArray * userData, RwInt32 index)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArrayGetReal"));

    RWASSERT(userData);
    RWASSERT(index < userData->numElements);
    RWASSERT(userData->format == rpREALUSERDATA);

    RWRETURN(((RwReal *) userData->data)[index]);
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArrayGetString is used to get the string stored at entry
 * index in the UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 * \param index the index in the data array to be retrieved
 *
 * \return the string element
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataArraySetString
 * \see RpUserDataPluginAttach
 */
RwChar             *
RpUserDataArrayGetString(RpUserDataArray * userData, RwInt32 index)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArrayGetString"));

    RWASSERT(userData);
    RWASSERT(index < userData->numElements);
    RWASSERT(userData->format == rpSTRINGUSERDATA);

    RWRETURN(((RwChar **) userData->data)[index]);
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArraySetInt is used to set the integer stored at entry
 * index in the UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 * \param index the index in the data array to be stored
 * \param value the integer value to be stored
 *
 * \return
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataArrayGetInt
 * \see RpUserDataPluginAttach
 */
void
RpUserDataArraySetInt(RpUserDataArray * userData, RwInt32 index,
                      RwInt32 value)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArraySetInt"));

    RWASSERT(userData);
    RWASSERT(index < userData->numElements);
    RWASSERT(userData->format == rpINTUSERDATA);

    ((RwInt32 *) userData->data)[index] = value;

    RWRETURNVOID();
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArraySetReal is used to set the real stored at entry
 * index in the UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 * \param index the index in the data array to be stored
 * \param value the real value to be stored
 *
 * \return
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataArrayGetReal
 * \see RpUserDataPluginAttach
 */
void
RpUserDataArraySetReal(RpUserDataArray * userData, RwInt32 index,
                       RwReal value)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArraySetReal"));

    RWASSERT(userData);
    RWASSERT(index < userData->numElements);
    RWASSERT(userData->format == rpREALUSERDATA);

    ((RwReal *) userData->data)[index] = value;

    RWRETURNVOID();
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataArraySetString is used to set the string stored at entry
 * index in the UserDataArray object.
 * The UserData plugin must be attached before using this function.
 *
 * \param userData the UserDataArray object
 * \param index the index in the data array to be stored
 * \param value the string value to be stored
 *
 * \return
 *
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 * \see RpUserDataArrayGetReal
 * \see RpUserDataPluginAttach
 */
void
RpUserDataArraySetString(RpUserDataArray * userData,
                         RwInt32 index, RwChar * value)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataArraySetString"));

    RWASSERT(userData);
    RWASSERT(index < userData->numElements);
    RWASSERT(userData->format == rpSTRINGUSERDATA);

    if (value == NULL)
    {
        ((RwChar **) userData->data)[index] = (RwChar *)NULL;
    }
    else
    {
        rwstrdup(((RwChar **) userData->data)[index], value);
    }


    RWRETURNVOID();
}

/**
 * \ingroup rpuserdata
 * \ref RpUserDataGetFormatSize is used get the size (in bytes) of a given
 * UserData element format.
 * The UserData plugin must be attached before using this function.
 *
 * \param format the UserData format
 *
 * \return size of the UserData format in bytes.
 *
 * \see RpUserDataPluginAttach
 */
RwInt32
RpUserDataGetFormatSize(RpUserDataFormat format)
{
    RWAPIFUNCTION(RWSTRING("RpUserDataGetFormatSize"));

    switch (format)
    {
        case rpINTUSERDATA:
            RWRETURN(sizeof(RwInt32));
            break;
        case rpREALUSERDATA:
            RWRETURN(sizeof(RwReal));
            break;
        case rpSTRINGUSERDATA:
            RWRETURN(sizeof(RwChar *));
            break;
        default:
            RWRETURN(0);
            break;
    }
}

/* Plugin API */

/**
 * \ingroup rpuserdata
 * \ref RpUserDataPluginAttach is used to attach the UserData plugin to the
 * RenderWare system. The plugin must be attached between initializing the
 * system with RwEngineInit and opening it with RwEngineOpen.
 *
 * Note that the include file rpusrdat.h is required and must be included by
 * an application wishing to use this facility. The UserData library is
 * contained in the file rpusrdat.lib.
 *
 * \return Returns TRUE if successful, FALSE otherwise
 * an error.
 *
 * \see RpGeometryAddUserDataArray
 * \see RpWorldSectorAddUserDataArray
 * \see RwFrameAddUserDataArray
 * \see RpGeometryGetUserDataArray
 * \see RpWorldSectorGetUserDataArray
 * \see RwFrameGetUserDataArray
 *
 */
RwBool
RpUserDataPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpUserDataPluginAttach"));

    /* Register Engine plugin */
    offset =
        RwEngineRegisterPlugin(0, rwID_USERDATAPLUGIN, UserDataOpen,
                               UserDataClose);
    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Register RpGeometry plugin */
    userDataGeometryOffset =
        RpGeometryRegisterPlugin(sizeof(RpUserDataList),
                                 rwID_USERDATAPLUGIN,
                                 UserDataGeometryConstruct,
                                 UserDataGeometryDestruct,
                                 UserDataGeometryCopy);

    if (userDataGeometryOffset < 0)
    {
        RWRETURN(FALSE);
    }

    userDataGeometryStreamOffset =
        RpGeometryRegisterPluginStream(rwID_USERDATAPLUGIN,
                                       UserDataGeometryStreamRead,
                                       UserDataGeometryStreamWrite,
                                       UserDataGeometryGetSize);

    if (userDataGeometryStreamOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Register RpWorldSector plugin */
    userDataWorldSectorOffset =
        RpWorldSectorRegisterPlugin(sizeof(RpUserDataList),
                                    rwID_USERDATAPLUGIN,
                                    UserDataWorldSectorConstruct,
                                    UserDataWorldSectorDestruct,
                                    UserDataWorldSectorCopy);

    if (userDataGeometryOffset < 0)
    {
        RWRETURN(FALSE);
    }

    userDataWorldSectorStreamOffset =
        RpWorldSectorRegisterPluginStream(rwID_USERDATAPLUGIN,
                                          UserDataWorldSectorStreamRead,
                                          UserDataWorldSectorStreamWrite,
                                          UserDataWorldSectorGetSize);
    
    if (userDataGeometryStreamOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Register RwFrame plugin */
    userDataFrameOffset =
        RwFrameRegisterPlugin(sizeof(RpUserDataList),
                                rwID_USERDATAPLUGIN,
                                UserDataFrameConstruct,
                                UserDataFrameDestruct,
                                UserDataFrameCopy);

    if (userDataFrameOffset < 0)
    {
        RWRETURN(FALSE);
    }

    userDataFrameStreamOffset =
        RwFrameRegisterPluginStream(rwID_USERDATAPLUGIN,
                                    UserDataFrameStreamRead,
                                    UserDataFrameStreamWrite,
                                    UserDataFrameGetSize);

    if (userDataFrameStreamOffset < 0)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}
