/* 
 * Labels plugin
 */

/**
 * \ingroup rplabel
 * \page rplabeloverview RpLabel Plugin Overview 
 * 
 * The RpLabel plug-in provides an API which developers can use to set and
 * retrieve text labels stored with a RenderWare frame hierarchy.
 * 
 * Labels can contain one or more comments.
 *
 * Labels can also be defined by 
 * artists when they export artwork using the User Properties option
 * (currently supported only using 3DS MAX).
 *
 * Copyright (c) Criterion Software Limited
 */

#include <string.h>

#include <rwcore.h>
#include <rpworld.h>

#include "rpplugin.h"
#include <rpdbgerr.h>
#include "rplabel.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rplabel.c,v 1.21 2001/02/02 14:35:38 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

#define RPLABELFRAMEGETLABEL(frame) \
    ((RpLabel*)(((RwUInt8*)frame) + labelFrameOffset))

#define RPLABELCONSTFRAMEGETLABEL(frame) \
    ((const RpLabel*)(((const RwUInt8*)frame) + labelFrameOffset))

#define RPLABELREADLABELBLOCKSIZE 256

/* typedef struct RpLabel RpLabel; */

struct RpLabel
{
    RwChar            **comments;
};

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

RwModuleInfo        labelModule;

static RwInt32      labelFrameOffset = 0;
static RwInt32      labelFrameStreamOffset = 0;

static              RwBool
LabelCopyLabel(RpLabel * destinationLabel, const RpLabel * sourceLabel)
{
    RwUInt32            numberOfComments = 0;
    RwUInt32            index;
    RwChar             *comment;
    RwUInt32            sizeOfComments = 0;
    RwInt32             offset;

    RWFUNCTION(RWSTRING("LabelCopyLabel"));
    RWASSERT(destinationLabel);
    RWASSERT(sourceLabel);

    if (destinationLabel->comments != NULL)
    {
        RwFree(destinationLabel->comments);
        destinationLabel->comments = (RwChar **)NULL;
    }
    if (sourceLabel->comments != NULL)
    {
        comment = sourceLabel->comments[numberOfComments];
        while (comment != NULL)
        {
            sizeOfComments += rwstrlen(comment) + 1;
            numberOfComments++;
            comment = sourceLabel->comments[numberOfComments];
        }
        sizeOfComments =
            (sizeof(RwChar *) * (numberOfComments + 1)) +
            (sizeof(RwChar) * sizeOfComments);
        destinationLabel->comments =
            (RwChar **) RwMalloc(sizeOfComments);
        if (destinationLabel->comments == NULL)
        {
            RWRETURN(FALSE);
        }
        memcpy(destinationLabel->comments, sourceLabel->comments,
               sizeOfComments);
        offset =
            (RwUInt8 *) (destinationLabel->comments) -
            (RwUInt8 *) (sourceLabel->comments);
        for (index = 0; index < numberOfComments; index++)
        {
            destinationLabel->comments[index] += offset;
        }
    }
    RWRETURN(TRUE);
}

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

    labelModule.numInstances++;

    RWRETURN(instance);
}

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

    labelModule.numInstances--;

    RWRETURN(instance);
}

static void        *
LabelFrameConstruct(void *object, RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    RwFrame            *frame;
    RpLabel            *label;

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

    frame = (RwFrame *) object;
    label = RPLABELFRAMEGETLABEL(frame);
    label->comments = (RwChar **)NULL;

    RWRETURN(object);
}

static void        *
LabelFrameDestruct(void *object, RwInt32 __RWUNUSED__ offset,
                   RwInt32 __RWUNUSED__ size)
{
    RwFrame            *frame;
    RpLabel            *label;

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

    frame = (RwFrame *) object;
    label = RPLABELFRAMEGETLABEL(frame);
    if (label->comments != NULL)
    {
        RwFree(label->comments);
        label->comments = (RwChar **)NULL;
    }

    RWRETURN(object);
}

static void        *
LabelFrameCopy(void *destinationObject, const void *sourceObject,
               RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RwFrame            *destinationFrame;
    const RwFrame      *sourceFrame;
    RpLabel            *destinationLabel;
    const RpLabel      *sourceLabel;

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

    destinationFrame = (RwFrame *) destinationObject;
    sourceFrame = (const RwFrame *) sourceObject;
    destinationLabel = RPLABELFRAMEGETLABEL(destinationFrame);
    sourceLabel = RPLABELCONSTFRAMEGETLABEL(sourceFrame);

    LabelCopyLabel(destinationLabel, sourceLabel);

    RWRETURN(destinationObject);
}

static RwStream    *
LabelFrameRead(RwStream * stream, RwInt32 __RWUNUSED__ binaryLength,
               void *object, RwInt32 __RWUNUSED__ offset,
               RwInt32 __RWUNUSED__ size)
{
    RwFrame            *frame;
    RpLabel            *label;
    RwChar             *buffer = (RwChar *)NULL;
    RwChar             *newBuffer;
    RwUInt32            bufferSize = 0;
    RwUInt32            charactersRead;

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

    frame = (RwFrame *) object;
    label = RPLABELFRAMEGETLABEL(frame);

    do
    {
        charactersRead = 0;
        do
        {
            /* if( charactersRead == bufferSize ) */
            if ((sizeof(RwChar) * (charactersRead + 1)) > bufferSize)
            {
                newBuffer = (RwChar *)
                    RwRealloc(buffer,
                              bufferSize + RPLABELREADLABELBLOCKSIZE);
                if (newBuffer == NULL)
                {
                    RwFree(buffer);
                    RWRETURN((RwStream *)NULL);
                }
                buffer = newBuffer;
                bufferSize += RPLABELREADLABELBLOCKSIZE;
            }
            if (!RwStreamRead
                (stream, &buffer[charactersRead], sizeof(RwChar)))
            {
                RwFree(buffer);
                RWRETURN((RwStream *)NULL);
            }
            charactersRead++;

        }
        while (buffer[charactersRead - 1] != '\0');
        /* if( rwstrlen( buffer ) > 0 ) */
        if (charactersRead > 1)
        {
            if (!RpLabelAddComment(label, buffer))
            {
                RwFree(buffer);
                RWRETURN((RwStream *)NULL);
            }
        }
        else
        {
            break;
        }
    }
    while (TRUE);
    RwFree(buffer);

    RWRETURN(stream);
}

static RwStream    *
LabelFrameWrite(RwStream * stream, RwInt32 __RWUNUSED__ binaryLength,
                const void *object, RwInt32 __RWUNUSED__ offset,
                RwInt32 __RWUNUSED__ size)
{
    const RwFrame      *frame;
    const RpLabel      *label;
    RwChar             *comment;
    RwUInt32            numberOfComments;
    RwChar              emptyString[1] = { '\0' };

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

    frame = (const RwFrame *) object;
    label = RPLABELCONSTFRAMEGETLABEL(frame);

    if (label->comments != NULL)
    {
        numberOfComments = 0;
        comment = label->comments[numberOfComments];
        while (comment != NULL)
        {
            if (!RwStreamWrite
                (stream, comment,
                 sizeof(RwChar) * (rwstrlen(comment) + 1)))
            {
                RWRETURN((RwStream *)NULL);
            }
            numberOfComments++;
            comment = label->comments[numberOfComments];
        }
        if (!RwStreamWrite(stream, emptyString, sizeof(RwChar)))
        {
            RWRETURN((RwStream *)NULL);
        }
    }
    else
    {

        /* write nothing as we can return a size of nothing and be skipped? */

#if (0)
        if (!RwStreamWrite(stream, emptyString, sizeof(RwChar)))
        {
            RWRETURN(NULL);
        }
#endif /* (0) */
    }
    RWRETURN(stream);
}

static              RwInt32
LabelFrameGetSize(const void *object,
                  RwInt32 __RWUNUSED__ offsetInObject,
                  RwInt32 __RWUNUSED__ sizeInObject)
{
    const RwFrame      *frame;
    const RpLabel      *label;
    RwInt32             size;
    RwUInt32            numberOfComments;
    RwChar             *comment;

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

    frame = (const RwFrame *) object;
    label = RPLABELCONSTFRAMEGETLABEL(frame);

    size = 0;
    if (label->comments != NULL)
    {
        numberOfComments = 0;
        comment = label->comments[numberOfComments];
        while (comment != NULL)
        {
            size += sizeof(RwChar) * (rwstrlen(comment) + 1);
            numberOfComments++;
            comment = label->comments[numberOfComments];
        }
        size += sizeof(RwChar); /* terminator */
    }
    else
    {

/*size += 1; *//* have to write a single NULL character, to say no comment! */
        /* apparently returning a size of zero means the write function isn't called and therefore
         * there will be no header of that type in the stream for that frame, so the read function
         * also isn't called.
         */
    }

    RWRETURN(size);
}

/**
 * \ingroup rplabel
 * \ref RpLabelFrameGetLabel retreives the label for a given frame.  A
 * RwFrame can store multiple comments; these comments are maintained in an
 * RpLabel structure that is attached to the RwFrame object.
 *
 * The label plugin must be attached before using this function.
 *
 * \param frame  Pointer to the frame.
 *
 * \return Returns pointer to the label if successful or NULL if there is 
 * an error.
 *
 * \see RpLabelForAllComments
 * \see RpLabelPluginAttach
 * \see RpLabelAddComment
 * \see RpLabelCallBack 
 */
RpLabel            *
RpLabelFrameGetLabel(RwFrame * frame)
{
    RpLabel            *label;

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

    label = RPLABELFRAMEGETLABEL(frame);

    RWRETURN(label);
}

/**
 * \ingroup rplabel
 * \ref RpLabelForAllComments calls the user supplied callback function
 * passing each comment in turn to that function. The behaviour is unspecified
 * if the user supplied function attempts to add another comment.
 *
 * The label plugin must be attached before using this function.
 *
 * \param label  Pointer to the label.
 * \param callback  Pointer to the users callback function.
 * \param data  Pointer which will be passed to the callback function.
 *
 * \return Returns pointer to the label if successful or NULL if there is 
 * an error.
 *
 * \see RpLabelFrameGetLabel
 * \see RpLabelPluginAttach
 * \see RpLabelAddComment
 * \see RpLabelCallBack 
 */
RpLabel            *
RpLabelForAllComments(RpLabel * label, RpLabelCallBack callback,
                      void *data)
{
    RwChar             *comment;
    int                 commentIndex;

    RWAPIFUNCTION(RWSTRING("RpLabelForAllComments"));
    RWASSERT(label);
    RWASSERT(callback);

    if (label->comments == NULL)
    {
        RWRETURN(label);
    }
    commentIndex = 0;
    comment = label->comments[commentIndex];
    while (comment != NULL)
    {
        if (!callback(comment, data))
        {
            RWRETURN(label);
        }
        commentIndex++;
        comment = label->comments[commentIndex];
    }
    RWRETURN(label);
}

/**
 * \ingroup rplabel
 * \ref RpLabelAddComment is used to add a comment string to the 
 * specified label.  This function calls RwMalloc or RwRealloc to create space
 * into which the comment text is copied.  Multiple comments can be attached
 * to a RwFrame.
 *
 * Note that the header file rplabel.h must be included, and the program
 * should be linked to rplabel.lib.  The label plugin must be attached
 * before using this function.
 *
 * \param label  The label to which the comment will be added.
 * \param comment  Comment text.
 *
 * \return Returns a pointer to the label if successful or NULL if there is 
 * an error.
 *
 * \see RpLabelFrameGetLabel
 * \see RpLabelPluginAttach
 * \see RpLabelForAllComments
 * \see RpLabelCallBack
 *
 */
RpLabel            *
RpLabelAddComment(RpLabel * label, const RwChar * comment)
{
    RwUInt32            index;
    RwUInt32            numberOfComments = 0;
    RwUInt32            sizeOfComments = 0;
    RwChar             *currentComment;
    RwChar            **newComments;
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpLabelAddComment"));
    RWASSERT(label);
    RWASSERT(comment);

    if (rwstrlen(comment) > 0)
    {
        if (label->comments == NULL)
        {
            label->comments = (RwChar **) RwMalloc(sizeof(RwChar *));
            label->comments[0] = (RwChar *)NULL;
        }
        currentComment = label->comments[numberOfComments];
        while (currentComment != NULL)
        {
            sizeOfComments +=
                rwstrlen(label->comments[numberOfComments]) + 1;
            numberOfComments++;
            currentComment = label->comments[numberOfComments];
        }
        newComments = ((RwChar **)
                       RwRealloc(label->comments,
                                 (sizeof(RwChar *) *
                                  (numberOfComments + 2)) +
                                 (sizeof(RwChar) * sizeOfComments) +
                                 (sizeof(RwChar) * (rwstrlen(comment) +
                                                    1))));
        if (newComments == NULL)
        {
            RWRETURN((RpLabel *)NULL);
        }
        offset =
            ((RwUInt8 *) (newComments)) -
            ((RwUInt8 *) (label->comments));
        /* make room for new entry in lookup table at start */
        offset += sizeof(RwChar *);
        label->comments = newComments;
        /* move strings up by sizeof( RwChar* ) so as 
         * to allow for the new entry in table at start */
        if (sizeOfComments > 0)
        {
            memmove(((RwUInt8 *) (label->comments)) +
                    (sizeof(RwChar *) * (numberOfComments + 2)),
                    ((RwUInt8 *) (label->comments)) +
                    (sizeof(RwChar *) * (numberOfComments + 1)),
                    (sizeof(RwChar) * sizeOfComments));
        }
        /* correct indices in table at start */
        for (index = 0; index < numberOfComments; index++)
        {
            label->comments[index] =
                (RwChar *) (((RwUInt8 *) (label->comments[index])) +
                            offset);
        }
        label->comments[numberOfComments] =
            (RwChar *) (((RwUInt8 *) (label->comments)) +
                        (sizeof(RwChar *) * (numberOfComments + 2)) +
                        (sizeof(RwChar) * sizeOfComments));
        label->comments[numberOfComments + 1] = (RwChar *)NULL;
        rwstrcpy(label->comments[numberOfComments], comment);
    }
    RWRETURN(label);
}

/**
 * \ingroup rplabel
 * \ref RpLabelPluginAttach is used to attach the Label plug-in to the
 * RenderWare system to enable access to the labels and comments stored in the
 * 3D model data.  The plug-in must be attached between initializing the system
 * with RwEngineInit and opening it with RwEngineOpen. 
 *
 * Note that the include file rplabel.h is required and must be included by an
 * application wishing to use this facility.  The Label library is contained in
 * the file rplabel.lib.
 *
 * \return Returns TRUE if successful, FALSE otherwise
 * an error.
 *
 * \see RpLabelFrameGetLabel
 * \see RpLabelAddComment
 * \see RpLabelForAllComments
 * \see RpLabelCallBack
 *
 */
RwBool
RpLabelPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpLabelPluginAttach"));

    offset =
        RwEngineRegisterPlugin(0, rwID_LABELPLUGIN, LabelOpen,
                               LabelClose);
    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    labelFrameOffset =
        RwFrameRegisterPlugin(sizeof(RpLabel), rwID_LABELPLUGIN,
                              LabelFrameConstruct, LabelFrameDestruct,
                              LabelFrameCopy);
    if (labelFrameOffset < 0)
    {
        RWRETURN(FALSE);
    }

    labelFrameStreamOffset =
        RwFrameRegisterPluginStream(rwID_LABELPLUGIN, LabelFrameRead,
                                    LabelFrameWrite, LabelFrameGetSize);
    if (labelFrameStreamOffset < 0)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}
