/*
 * Handling binary texture representations.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 Includes
 */

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

#include "batypes.h"
#include "balibtyp.h"
#include "badebug.h"
#include "bamemory.h"
#include "baimmedi.h"

#include "babinary.h"
#include "batkreg.h"
#include "batkbin.h"

/* Abstraction of string functionality - for unicode support */
#include "rwstring.h"

#include "babintex.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: babintex.c,v 1.85 2001/09/18 17:13:17 mattt Exp $";
#endif /* (!defined(DOXYGEN)) */

/****************************************************************************
 Local Types
 */

/* Texture stream format */
typedef struct _rwStreamTexture rwStreamTexture;
struct _rwStreamTexture
{
    RwInt32             filterAndAddress;
};

/* Texture dictionary stream format */
typedef struct _rwStreamTexDictionary rwStreamTexDictionary;
struct _rwStreamTexDictionary
{
    RwInt32             numTextures;
};


typedef union _unionStreamSize unionStreamSize;
union _unionStreamSize
{
    RwStream           *stream;
    RwUInt32            size;
};

typedef struct _nativeTextureWriteInfo nativeTextureWriteInfo;
struct _nativeTextureWriteInfo
{
    unionStreamSize     u;
    RwBool              allOK;
};

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

static RwBool babintexIssuedVersionWarning = FALSE;

static const RwChar nullString[] = RWSTRING("");

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Texture Binary Format Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/****************************************************************************
 _rwStringDestroy

 On entry   : String to destroy
 On exit    : TRUE on success
 */

RwBool
_rwStringDestroy(RwChar * string)
{
    RWFUNCTION(RWSTRING("_rwStringDestroy"));
    RWASSERT(string);
    RwFree(string);
    RWRETURN(TRUE);
}

/****************************************************************************
 _rwStringStreamGetSize

 On entry   :
 On exit    : Size of Binary String
 */

RwUInt32
_rwStringStreamGetSize(const RwChar * string)
{
    RWFUNCTION(RWSTRING("_rwStringStreamGetSize"));

    /* Provide an empty string if we are writing a NULL string 
     * - aren't we kind!!! */
    if (!string)
    {
        string = nullString;
    }

    /* +1 for the terminator, ((+3) &~3) to 
     * round up to nearest 4 byte boundary */
    RWRETURN((((rwstrlen(string) + 1) * sizeof(RwChar)) + 3) & ~3);
}

/****************************************************************************
 _rwStringStreamWrite

 On entry   : Stream to write to
 On exit    :
 */
const RwChar       *
_rwStringStreamWrite(const RwChar * string, RwStream * stream)
{
    RwUInt32            stringSize;

    RWFUNCTION(RWSTRING("_rwStringStreamWrite"));
    RWASSERT(stream);

    /* Provide an empty string if we are writing a NULL string - aren't we kind!!! */
    if (!string)
    {
        string = nullString;
    }

    /* Get the binary size */
    stringSize = _rwStringStreamGetSize(string);

    /* Write out the correct chunk header */
#ifdef RWUNICODE
    if (!RwStreamWriteChunkHeader(stream, rwID_UNICODESTRING, stringSize))
    {
        RWRETURN(NULL);
    }
#else /* RWUNICODE */
    if (!RwStreamWriteChunkHeader(stream, rwID_STRING, stringSize))
    {
        RWRETURN((const char *)NULL);
    }
#endif /* RWUNICODE */

    if (!RwStreamWrite(stream, string, stringSize))
    {
        RWRETURN((const char *)NULL);
    }

    RWRETURN(string);
}

/****************************************************************************
 StringStreamRead

 This reads a non unicode string, converting to unicode if necessary

 On entry   : String to fill, or NULL if malloc required
            : Stream to read from
            : Length of the string to read
 On exit    : String created
 */

static RwChar      *
StringStreamRead(RwChar *nativeString, RwStream * stream, RwUInt32 length)
{
    /* NOTE: This is one of the rare places we really do need to use char.
     *     : The binary standard defines the things in the file as being chars.
     */
    RWALIGN(char multiByteString[64], 64);
    RwChar *baseString;
    RwBool  mallocced;

    RWFUNCTION(RWSTRING("StringStreamRead"));
    RWASSERT(stream);

    /* The length is the length in bytes in the stream.  However, since
     * we are reading a non unicode string, it is also the num of chars.
     * NOTE The malloc has been put in to support the RpAnim plugin which
     * sneakily calls this function via StringStreamFindAndRead(), and
     * which for the time being does not support fixed length strings.
     */

    if (nativeString == NULL)
    {
        nativeString = (RwChar *) RwMalloc(length * sizeof(RwChar));
        if (!nativeString)
        {
            RWERROR((E_RW_NOMEM, length * sizeof(RwChar)));
            RWRETURN((char *)NULL);
        }
        mallocced = TRUE;
    }

    /* We make the assumption that few strings will be greater then 64 bytes,
     * so these are treated as a special case 
     */
    baseString = nativeString;
    while(length>0)
    {
        RwUInt32  bytesToRead = (length>64) ? 64 : length;
        RwUInt32  i;

        if (RwStreamRead(stream, multiByteString, bytesToRead) != bytesToRead)
        {
            RWRETURN((char *)NULL);
        }
    
        /* Reduce by the amount we read */
        length -= bytesToRead;

        /* Now we need to convert into native string format 
         * - this might be chars, it might be wchar_t */
        for (i=0; i<bytesToRead; i++)
        {
            /* We just cast it to an RwChar */
            baseString[i] = (RwChar) multiByteString[i];
        }
        baseString += bytesToRead;
    }

    RWRETURN(nativeString);
}

/****************************************************************************
 UnicodeStringStreamRead

 This reads a non unicode string, converting to unicode if necessary

 On entry   : String to fill, or NULL if malloc required
            : Stream to read from
            : Length of the string to read
 On exit    : String created
 */

static RwChar      *
UnicodeStringStreamRead(RwChar *nativeString, RwStream * stream, RwUInt32 length)
{
    /* NOTE: This is one of the rare places we really do need to use unsigned shorts.
     *     : The binary standard defines the things in the file as being unsigned shorts.
     */
    RWALIGN(unsigned short uniCodeString[64], 64);
    RwChar            *baseString;
    RwBool             mallocced = FALSE;

    RWFUNCTION(RWSTRING("UnicodeStringStreamRead"));
    RWASSERT(stream);

    /* The length is the length in bytes in the stream.  However, since
     * we are reading a unicode string, it is also the num of chars*2.
     * NOTE The malloc has been put in to support the RpAnim plugin which
     * sneakily calls this function via StringStreamFindAndRead(), and
     * which for the time being does not support fixed length strings.
     */
    if (nativeString == NULL)
    {
        nativeString = (RwChar *) RwMalloc(length);
        if (!nativeString)
        {
            RWERROR((E_RW_NOMEM, length));
            RWRETURN((char *)NULL);
        }
        mallocced = TRUE;
    }

    /* We make the assumption that few strings will be greater then 64 bytes, 
     * so these are treated as a special case 
     */
    baseString = nativeString;
    while(length>0)
    {
        RwUInt32  bytesToRead = (length>64*2) ? 64*2 : length;
        RwUInt32  i,CharCount;

        if (RwStreamRead(stream, uniCodeString, bytesToRead) != bytesToRead)
        {
            if (mallocced)
            {
                RwFree(nativeString);
            }
            RWRETURN((char *)NULL);
        }
    
        /* Reduce by the amount we read */
        length -= bytesToRead;

        /* Now we need to convert into native string format 
         * - this might be chars, it might be wchar_t */
        CharCount = bytesToRead >> 1;
        for (i=0; i<CharCount; i++)
        {
            /* We just cast it to an RwChar */
            baseString[i] = (RwChar) uniCodeString[i];
        }
        baseString += CharCount;
    }
    
    RWRETURN(nativeString);
}

/****************************************************************************
 _rwStringStreamFindAndRead

 On entry   : String to destroy
 On exit    : TRUE on success
 */

RwChar             *
_rwStringStreamFindAndRead(RwChar *string, RwStream * stream)
{
    RwUInt32            type, length, version;

    RWFUNCTION(RWSTRING("_rwStringStreamFindAndRead"));
    RWASSERT(stream);

    while (_rwStreamReadChunkHeader(
                stream, &type, &length, &version, (RwUInt32 *)NULL))
    {
        const RwBool valid = ( (version >= rwLIBRARYBASEVERSION) &&
                               (version <= rwLIBRARYCURRENTVERSION) );
        const RwBool issue = ( (!babintexIssuedVersionWarning) &&
                               (version < rwLIBRARYCURRENTVERSION) );

        /* Reject invalid versions */
        if (!valid)
        {
            RWERROR((E_RW_BADVERSION));
            RWRETURN((char *)NULL);
        }

        if (issue)
        {
            RWMESSAGE((RWSTRING("version 0x%0x < rwLIBRARYCURRENTVERSION 0x%x; content may be sub-optimal"),
                       (unsigned int)version,
                       rwLIBRARYCURRENTVERSION));
            babintexIssuedVersionWarning |= issue;
        }

        if (type == rwID_STRING)
        {
            RWRETURN(StringStreamRead(string, stream, length));
        }
        else if (type == rwID_UNICODESTRING)
        {
            RWRETURN(UnicodeStringStreamRead(string, stream, length));
        }

        if (!RwStreamSkip(stream, length))
        {
            RWRETURN((char *)NULL);
        }
    }

    RWRETURN((char *)NULL);
}



/**
 * \ingroup rwtexture
 * \ref RwTextureRegisterPluginStream is used to associate a set of
 * binary stream functionality with a previously registered texture plugin.
 *
 * \param pluginID  A RwInt32 value equal to the plugin ID (must be unique; used
 *       to identify binary chunks).
 * \param readCB  Callback used when a chunk is read that is identified as being for
 *       this plugin.
 * \param writeCB  Callback used when a chunk should be written out for this plugin.
 * \param getSizeCB  Callback used to determine the binary stream size required for this
 *       plugin (return negative to suppress chunk writing).
 *
 * \return Returns an RwInt32 containing the byte offset within the texture of
 * memory reserved for this plugin, or a negative value if there is an error.
 *
 * \see RwTextureSetStreamAlwaysCallBack
 * \see RwTextureRegisterPlugin
 * \see RwTextureGetPluginOffset
 * \see RwTextureValidatePlugins
 *
 */
RwInt32
RwTextureRegisterPluginStream(RwUInt32 pluginID,
                              RwPluginDataChunkReadCallBack readCB,
                              RwPluginDataChunkWriteCallBack writeCB,
                              RwPluginDataChunkGetSizeCallBack getSizeCB)
{
    RwInt32             plug;

    RWAPIFUNCTION(RWSTRING("RwTextureRegisterPluginStream"));
    RWASSERT(readCB);
    RWASSERT(writeCB);
    RWASSERT(getSizeCB);

    /* Everything's cool, so pass it on */
    plug = _rwPluginRegistryAddPluginStream(&textureTKList, pluginID,
                                           readCB, writeCB, getSizeCB);

    RWRETURN(plug);
}

/**
 * \ingroup rwtexture
 * \ref RwTextureSetStreamAlwaysCallBack is used to associate a
 * binary stream functionality with a previously registered texture plugin.
 * This callback is called for all plugins after stream data reading has
 * completed.
 *
 * \param pluginID  A RwInt32 value equal to the plugin ID (must be unique; used
 *       to identify binary chunks).
 * \param alwaysCB  Callback used when object base and plugin data reading is complete.
 *
 * \return Returns an RwInt32 containing the byte offset within the texture of
 * memory reserved for this plugin, or a negative value if there is an error.
 *
 * \see RwTextureRegisterPluginStream
 * \see RwTextureRegisterPlugin
 * \see RwTextureGetPluginOffset
 * \see RwTextureValidatePlugins
 *
 */
RwInt32
RwTextureSetStreamAlwaysCallBack(
    RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB)
{
    RwInt32             plug;

    RWAPIFUNCTION(RWSTRING("RwTextureSetStreamAlwaysCallBack"));
    RWASSERT(alwaysCB);

    /* Everything's cool, so pass it on */
    plug = _rwPluginRegistryAddPlgnStrmlwysCB(
               &textureTKList, pluginID, alwaysCB);

    RWRETURN(plug);
}

/**
 * \ingroup rwtexture
 * \ref RwTextureStreamGetSize is used to determine the size in bytes of
 * the binary representation of the specified texture. This is used in the
 * binary chunk header to indicate the size of the texture chunk.
 * The size does not include the size of the chunk header.
 *
 * \param texture  Pointer to the texture whose binary size is required.
 *
 * \return Returns the chunk size of the texture.
 *
 * \see RwTextureStreamRead
 * \see RwTextureStreamWrite
 * \see RwTextureRead
 */
RwUInt32
RwTextureStreamGetSize(const RwTexture *texture)
{
    RwUInt32            size;

    RWAPIFUNCTION(RWSTRING("RwTextureStreamGetSize"));
    RWASSERT(texture);

    /* First get the size of the texture without the extension chunks */
    size = sizeof(rwStreamTexture) + rwCHUNKHEADERSIZE;

    size += _rwStringStreamGetSize(texture->name) + rwCHUNKHEADERSIZE;
    size += _rwStringStreamGetSize(texture->mask) + rwCHUNKHEADERSIZE;

    /* Then add the size of the extension chunks */
    size += (_rwPluginRegistryGetSize(&textureTKList, texture) + rwCHUNKHEADERSIZE);

    RWRETURN(size);
}

/**
 * \ingroup rwtexture
 * \ref RwTextureStreamWrite is used to write the specified texture to the
 * given binary stream. The stream must have been opened prior to this
 * function call.
 *
 * \param texture  Pointer to the texture to be written.
 * \param stream  Pointer to the binary stream.
 *
 * \return Returns a pointer to the texture if successful, or NULL if there is
 * an errror.
 *
 * \see RwTextureStreamGetSize
 * \see RwTextureStreamRead
 * \see RwStreamOpen
 * \see RwStreamClose
 *
 */
const RwTexture *
RwTextureStreamWrite(const RwTexture *texture, RwStream *stream)
{
    RwTextureFilterMode     filtering;
    RwTextureAddressMode    addressingU;
    RwTextureAddressMode    addressingV;
    RwTextureStreamFlags    flags;
    rwStreamTexture         texFiltAddr;

    RWAPIFUNCTION(RWSTRING("RwTextureStreamWrite"));
    RWASSERT(texture);
    RWASSERT(stream);

    if (!RwStreamWriteChunkHeader(stream, rwID_TEXTURE, RwTextureStreamGetSize(texture)))
    {
        RWRETURN((const RwTexture *)NULL);
    }

    if (!RwStreamWriteChunkHeader(stream, rwID_STRUCT, sizeof(rwStreamTexture)))
    {
        RWRETURN((const RwTexture *)NULL);
    }

    /* Scribble out a structure with the filtering in */
    filtering = RwTextureGetFilterMode(texture);

    /* Combine and put into the binary structure */
    addressingU = RwTextureGetAddressingU(texture);
    addressingV = RwTextureGetAddressingV(texture);

    if ((texture->raster) &&
        (!(texture->raster->cFormat & (rwRASTERFORMATAUTOMIPMAP >> 8))))
    {
        flags = rwTEXTURESTREAMFLAGSUSERMIPMAPS;
    }
    else
    {
        flags = rwNATEXTURESTREAMFLAG;
    }

    /* shift up the V an extra 4 bits to pack into 8 bits with addressing U */
    texFiltAddr.filterAndAddress = (((RwInt32)filtering) & 0xFF) |
                                   ((((RwInt32)addressingU) & 0x0F) << 8) |
                                   ((((RwInt32)addressingV) & 0x0F) << 12) |
                                   ((((RwInt32)flags) & 0xFF) << 16);

    /* Convert it */
    RwMemLittleEndian(&texFiltAddr, sizeof(texFiltAddr));

    /* And write it out */
    if (!RwStreamWrite(stream, &texFiltAddr, sizeof(texFiltAddr)))
    {
        RWRETURN((const RwTexture *)NULL);
    }

    /* and texture names */
    if (!_rwStringStreamWrite(texture->name, stream))
    {
        RWRETURN((const RwTexture *)NULL);
    }
    if (!_rwStringStreamWrite(texture->mask, stream))
    {
        RWRETURN((const RwTexture *)NULL);
    }

    /* And then the texture extension chunk */
    if (!_rwPluginRegistryWriteDataChunks(&textureTKList, stream, texture))
    {
        /* Failed to write extension data */
        RWRETURN((const RwTexture *)NULL);
    }

    RWRETURN(texture);
}

/**
 * \ingroup rwtexture
 * \ref RwTextureStreamRead is used to read a texture from the specified
 * binary stream. Prior to this function call a binary texture chunk must
 * have been found in the stream.
 *
 * \param stream  Pointer to the binary stream the texture will be read from.
 *
 * \return Returns pointer to the texture if successful or NULL if there is
 * an error.
 *
 * \see RwTextureStreamGetSize
 * \see RwTextureStreamWrite
 * \see RwStreamOpen
 * \see RwStreamClose
 * \see RwStreamFindChunk
 *
 * \verbatim
   The sequence to locate and read a texture from a binary stream connected
   to a disk file is as follows: 
 
   RwStream *stream;
   RwTexture *NewTexture;
  
   stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "mybinary.xxx");
   if( stream )
   {
       if( RwStreamFindChunk(stream, rwID_TEXTURE, NULL, NULL) )
       {
           NewTexture = RwTextureStreamRead(stream);
       }
  
       RwStreamClose(stream, NULL);
   }
   \endverbatim
 *
 */
RwTexture *
RwTextureStreamRead(RwStream *stream)
{
    RwUInt32    size, version;

    RWAPIFUNCTION(RWSTRING("RwTextureStreamRead"));
    RWASSERT(stream);

    if (!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
    {
        RWRETURN((RwTexture *)NULL);
    }

    if ((version >= rwLIBRARYBASEVERSION) && (version <= rwLIBRARYCURRENTVERSION))
    {
        RwTexture               *texture;
        RwChar                  textureName[rwTEXTUREBASENAMELENGTH*4];
        RwChar                  textureMask[rwTEXTUREBASENAMELENGTH*4];
        RwTextureFilterMode     filtering;
        RwTextureAddressMode    addressingU;
        RwTextureAddressMode    addressingV;
        rwStreamTexture         texFiltAddr;
        RwBool                  mipmapState;
        RwBool                  autoMipmapState;
        RwTextureStreamFlags    flags;

        /* Read the filtering mode */
        RWASSERT(size <= sizeof(texFiltAddr));
        memset(&texFiltAddr, 0, sizeof(texFiltAddr));
        if (RwStreamRead(stream, &texFiltAddr, size) != size)
        {
            RWRETURN((RwTexture *)NULL);
        }

        /* Convert it */
        RwMemNative(&texFiltAddr, sizeof(texFiltAddr));

        /* Extract filtering */
        filtering = (RwTextureFilterMode)
                    (texFiltAddr.filterAndAddress & 0xFF);

        /* Extract addressing */
        addressingU = (RwTextureAddressMode)
                      ((texFiltAddr.filterAndAddress >> 8) & 0x0F);

        addressingV = (RwTextureAddressMode)
                      ((texFiltAddr.filterAndAddress >> 12) & 0x0F);

        /* Make sure addressingV is valid so files old than 3.04 still work */
        if (addressingV == rwTEXTUREADDRESSNATEXTUREADDRESS)
        {
            addressingV = addressingU;
        }

        /* Extract user mipmap flags */
        flags = (RwTextureStreamFlags)((texFiltAddr.filterAndAddress >> 16) & 0xFF);

        mipmapState = RwTextureGetMipmapping();
        autoMipmapState = RwTextureGetAutoMipmapping();

        /* Use it */
        if ((filtering == rwFILTERMIPNEAREST) ||
            (filtering == rwFILTERMIPLINEAR) ||
            (filtering == rwFILTERLINEARMIPNEAREST) ||
            (filtering == rwFILTERLINEARMIPLINEAR))
        {
            /* Lets mip map it */
            RwTextureSetMipmapping(TRUE);
            if (flags & rwTEXTURESTREAMFLAGSUSERMIPMAPS)
            {
                RwTextureSetAutoMipmapping(FALSE);
            }
            else
            {
                RwTextureSetAutoMipmapping(TRUE);
            }
        }
        else
        {
            /* Lets not */
            RwTextureSetMipmapping(FALSE);
            RwTextureSetAutoMipmapping(FALSE);
        }

        /* Search for a string or a unicode string */
        if (!_rwStringStreamFindAndRead(textureName, stream))
        {
            RwTextureSetMipmapping(mipmapState);
            RwTextureSetAutoMipmapping(autoMipmapState);

            RWRETURN((RwTexture *)NULL);
        }

        /* Search for a string or a unicode string */
        if (!_rwStringStreamFindAndRead(textureMask,stream))
        {
            RwTextureSetMipmapping(mipmapState);
            RwTextureSetAutoMipmapping(autoMipmapState);
            RWRETURN((RwTexture *)NULL);
        }

        /* Get the textures */
        if (!(texture = RwTextureRead(textureName, textureMask)))
        {
            /* Skip any extension chunks */
            _rwPluginRegistrySkipDataChunks(&textureTKList, stream);

            RwTextureSetMipmapping(mipmapState);
            RwTextureSetAutoMipmapping(autoMipmapState);

            RWRETURN((RwTexture *)NULL);
        }

        /* Set the filtering and addressing */
        /* By testing the reference count here, 
         * we can tell if we just loaded it!!! */

        RWASSERT(0 < texture->refCount);

        if (texture->refCount == 1)
        {
            RwTextureSetFilterMode(texture, filtering);
            RwTextureSetAddressingU(texture, addressingU);
            RwTextureSetAddressingV(texture, addressingV);
        }

        /* clean up */
        RwTextureSetMipmapping(mipmapState);
        RwTextureSetAutoMipmapping(autoMipmapState);

        /* Read the extension chunks */
        if (!_rwPluginRegistryReadDataChunks(&textureTKList, stream, texture))
        {
            RWRETURN((RwTexture *)NULL);
        }

        RWRETURN(texture);
    }
    else
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RwTexture *)NULL);
    }
}

/**
 * \ingroup rwtexdict
 * \ref RwTexDictionaryRegisterPluginStream is used to associate a set of
 * binary stream functionality with a previously registered TexDictionary
 * Plugin.
 *
 * \param pluginID  A RwInt32 value equal to the plugin ID (must be unique. It is used
 *       to identify binary chunks).
 * \param readCB  Callback used when a chunk is read that is identified as being for
 *       this plugin.
 * \param writeCB  Callback used when a chunk should be written out for this plugin.
 * \param getSizeCB  Callback used to determine the binary stream size required for this
 *       plugin (return negative to suppress chunk writing).
 *
 * \return Returns an RwInt32 containing the byte offset within the texture 
 * of memory reserved for this plugin, or -1 if there is an error.
 *
 * \see RwTexDictionarySetStreamAlwaysCallBack
 * \see RwTexDictionaryRegisterPlugin
 * \see RwTexDictionaryGetPluginOffset
 * \see RwTexDictionaryValidatePlugins
 *
 */
RwInt32
RwTexDictionaryRegisterPluginStream(RwUInt32 pluginID,
                                    RwPluginDataChunkReadCallBack readCB,
                                    RwPluginDataChunkWriteCallBack writeCB,
                                    RwPluginDataChunkGetSizeCallBack
                                    getSizeCB)
{
    RwInt32             plug;

    RWAPIFUNCTION(RWSTRING("RwTexDictionaryRegisterPluginStream"));
    RWASSERT(readCB);
    RWASSERT(writeCB);
    RWASSERT(getSizeCB);

    /* Everything's cool, so pass it on */
    plug = _rwPluginRegistryAddPluginStream(&texDictTKList, pluginID,
                                           readCB, writeCB, getSizeCB);

    RWRETURN(plug);
}

/**
 * \ingroup rwtexdict
 * \ref RwTexDictionarySetStreamAlwaysCallBack is used to
 * associate a binary stream functionality with a previously registered
 * TexDictionary Plugin. This callback is called for all plugins after
 * stream data reading has completed.
 *
 * \param pluginID  A RwInt32 value equal to the plugin ID (must be unique. It is used
 *       to identify binary chunks).
 * \param alwaysCB  Callback used when object base and plugin data reading is complete.
 *
 * \return Returns an RwInt32 containing the byte offset within the texture 
 * of memory reserved for this plugin, or -1 if there is an error.
 *
 * \see RwTexDictionaryRegisterPluginStream
 * \see RwTexDictionaryRegisterPlugin
 * \see RwTexDictionaryGetPluginOffset
 * \see RwTexDictionaryValidatePlugins
 *
 */
RwInt32
RwTexDictionarySetStreamAlwaysCallBack(
    RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB)
{
    RwInt32             plug;

    RWAPIFUNCTION(RWSTRING("RwTexDictionarySetStreamAlwaysCallBack"));
    RWASSERT(alwaysCB);

    /* Everything's cool, so pass it on */
    plug = _rwPluginRegistryAddPlgnStrmlwysCB(
               &texDictTKList, pluginID, alwaysCB);

    RWRETURN(plug);
}

/****************************************************************************
 addNativeTextureSize

 On entry   : Texture pointer
            : User data
 On exit    : Texture pointer on success
 */

static RwTexture   *
addNativeTextureSize(RwTexture * texture, void *data)
{
    nativeTextureWriteInfo *info = (nativeTextureWriteInfo *) data;
    RwUInt32            length;

    RWFUNCTION(RWSTRING("addNativeTextureSize"));
    RWASSERT(texture);
    RWASSERT(info);

    /* Call to device to find size of device specific serialised texture */
    if (!RWSRCGLOBAL
        (stdFunc[rwSTANDARDNATIVETEXTUREGETSIZE] (&length, texture, 0)))
    {
        /* It's all gone horribly wrong */
        info->allOK = FALSE;
        RWRETURN((RwTexture *)NULL);
    }

    /* Add the size of the data (and the header we put on) */
    info->u.size += (length + rwCHUNKHEADERSIZE);

    /* And the plugin data 
     * (we don't want the driver to be concerned about all this) */
    info->u.size += ( _rwPluginRegistryGetSize(&textureTKList, texture) +
                      rwCHUNKHEADERSIZE );

    RWRETURN(texture);
}

/**
 * \ingroup rwtexdict
 * \ref RwTexDictionaryStreamGetSize is used to determine the size in
 * bytes of the binary representation of the specified texture dictionary.
 * This is used in the binary chunk header to indicate the size of the texture
 * chunk. The size does not include the size of the chunk header.
 *
 * Note that texture dictionaries are device specific, and cannot generally be
 * used on multiple platforms.
 *
 * \param texDict  Pointer to the texture dictionary whose binary size is required.
 *
 * \return Returns the chunk size of the texture dictionary if successful or
 * zero if there is an error.
 *
 * \see RwTexDictionaryStreamRead
 * \see RwTexDictionaryStreamWrite
 *
 */
RwUInt32
RwTexDictionaryStreamGetSize(const RwTexDictionary *texDict)
{
    nativeTextureWriteInfo info;

    RWAPIFUNCTION(RWSTRING("RwTexDictionaryStreamGetSize"));
    RWASSERT(texDict);

    /* First get the size of the texDictionary without the extension chunks */
    info.u.size = sizeof(rwStreamTexDictionary) + rwCHUNKHEADERSIZE;

    /* Now find out the size of all the textures */
    info.allOK = TRUE;
    RwTexDictionaryForAllTextures(texDict, addNativeTextureSize, &info);

    if (!info.allOK)
    {
        RWRETURN(0);
    }

    /* Then add the size of the extension chunks */
    info.u.size += (_rwPluginRegistryGetSize(&texDictTKList, texDict) + rwCHUNKHEADERSIZE);

    RWRETURN(info.u.size);
}

/****************************************************************************
 countTexturesInDictionary

 On entry   : Texture pointer
            : User data
 On exit    : Texture pointer on success
 */

static RwTexture   *
countTexturesInDictionary(RwTexture * __RWUNUSED__ texture , void *data)
{
    RwInt32            *countPtr = (RwInt32 *) data;
    RwInt32            count;
    
    RWFUNCTION(RWSTRING("countTexturesInDictionary"));
    RWASSERT(texture);
    RWASSERT(countPtr);

    count = *countPtr;
    *countPtr = ++count;

    RWRETURN(texture);
}

/****************************************************************************
 writeNativeTexture

 On entry   : Texture pointer
            : User data
 On exit    : Texture pointer on success
 */

static RwTexture   *
writeNativeTexture(RwTexture * texture, void *data)
{
    nativeTextureWriteInfo *info = (nativeTextureWriteInfo *) data;
    RwUInt32            length;

    RWFUNCTION(RWSTRING("writeNativeTexture"));
    RWASSERT(texture);
    RWASSERT(info);

    /* How big is it? */
    if (!RWSRCGLOBAL
        (stdFunc[rwSTANDARDNATIVETEXTUREGETSIZE] (&length, texture, 0)))
    {
        /* It's all gone horribly wrong */
        info->allOK = FALSE;
        RWRETURN((RwTexture *)NULL);
    }

    /* And the plugin data 
     * (we don't want the driver to be concerned about all this) */
    length += ( _rwPluginRegistryGetSize(&textureTKList, texture) +
                rwCHUNKHEADERSIZE );

    /* Chunk header to write... */
    if (!RwStreamWriteChunkHeader(info->u.stream, rwID_TEXTURENATIVE, length))
    {
        info->allOK = FALSE;
        RWRETURN((RwTexture *)NULL);
    }

    /* ...then call the device to write it */
    if (!RWSRCGLOBAL
        (stdFunc[rwSTANDARDNATIVETEXTUREWRITE]
         (info->u.stream, texture, length)))
    {
        /* It's all gone horribly wrong */
        info->allOK = FALSE;
        RWRETURN((RwTexture *)NULL);
    }

    /* And write out the plugin data */
    if (!_rwPluginRegistryWriteDataChunks
        (&textureTKList, info->u.stream, texture))
    {
        /* Failed to write extension data */
        info->allOK = FALSE;
        RWRETURN((RwTexture *)NULL);
    }

    /* All done now */
    RWRETURN(texture);
}

/**
 * \ingroup rwtexdict
 * \ref RwTexDictionaryStreamWrite is used to write the specified texture
 * dictionary to the given binary stream. The stream must have been opened
 * prior to this function call.
 *
 * Note that texture dictionaries are device specific, and cannot generally be
 * used on multiple platforms.
 *
 * \param texDict  Pointer to the texture dictionary to be written.
 * \param stream  Pointer to the binary stream.
 *
 * \return Returns pointer to the texture dictionary if successful or NULL if
 * there is an errror.
 *
 * \see RwTexDictionaryStreamGetSize
 * \see RwTexDictionaryStreamRead
 * \see RwStreamOpen
 * \see RwStreamClose
 *
 */
const RwTexDictionary *
RwTexDictionaryStreamWrite(const RwTexDictionary *texDict, RwStream *stream)
{
    rwStreamTexDictionary    binTexDict;
    nativeTextureWriteInfo info;
    RwUInt32            length;

    RWAPIFUNCTION(RWSTRING("RwTexDictionaryStreamWrite"));
    RWASSERT(texDict);
    RWASSERT(stream);

    length = RwTexDictionaryStreamGetSize(texDict);
    if (!length)
    {
        RWRETURN((const RwTexDictionary *)NULL);
    }

    if (!RwStreamWriteChunkHeader(stream, rwID_TEXDICTIONARY, length))
    {
        RWRETURN((const RwTexDictionary *)NULL);
    }

    if (!RwStreamWriteChunkHeader
        (stream, rwID_STRUCT, sizeof(rwStreamTexDictionary)))
    {
        RWRETURN((const RwTexDictionary *)NULL);
    }

    /* Write out structure with texture count in */
    binTexDict.numTextures = 0;
    RwTexDictionaryForAllTextures(texDict, countTexturesInDictionary,
                                  &binTexDict.numTextures);
    RwMemLittleEndian(&binTexDict, sizeof(binTexDict));
    if (!RwStreamWrite(stream, &binTexDict, sizeof(binTexDict)))
    {
        RWRETURN((const RwTexDictionary *)NULL);
    }

    /* Now we just need to write all the textures - easy, right? */
    info.u.stream = stream;
    info.allOK = TRUE;
    RwTexDictionaryForAllTextures(texDict, writeNativeTexture, &info);

    if (!_rwPluginRegistryWriteDataChunks(&texDictTKList, stream, texDict))
    {
        /* Oooops */
        info.allOK = FALSE;
    }

    /* Was it good for you? :-) */
    if (!info.allOK)
    {
        RWRETURN((const RwTexDictionary *)NULL);
    }

    /* And we're all done */
    RWRETURN(texDict);
}

/****************************************************************************
 destroyTexture

 On entry   : Texture pointer
            : User data
 On exit    : Texture pointer on success
 */

static RwTexture   *
destroyTexture(RwTexture * texture, 
               void * __RWUNUSED__ data )
{
    RWFUNCTION(RWSTRING("destroyTexture"));
    RWASSERT(texture);

    RwTextureDestroy(texture);

    RWRETURN(texture);
}

/**
 * \ingroup rwtexdict
 * \ref RwTexDictionaryStreamRead is used to read a texture dictionary
 * from the specified binary stream. Prior to this function call a binary
 * texture dictionary chunk must have been found in the stream.
 *
 * Note that texture dictionaries are device specific, and cannot generally
 * be used on multiple platforms.
 *
 * \param stream  Pointer to the binary stream the texture dictionary will be read from.
 *
 * \return Returns pointer to the texture dictionary if successful or NULL if
 * there is an error.
 *
 * \see RwTexDictionaryStreamGetSize
 * \see RwTexDictionaryStreamWrite
 * \see RwStreamOpen
 * \see RwStreamClose
 * \see RwStreamFindChunk
 *
 * \verbatim
   The sequence to locate and read a texture dictionary from a binary
   stream connected to a disk file is as follows: 
  
   RwStream *stream;
   RwTexDictionary *NewTexDictionary;
  
   stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "mybinary.xxx");
   if( stream )
   {
       if( RwStreamFindChunk(stream, rwID_TEXDICTIONARY, NULL, NULL) )
       {
           NewTexDictionary = RwTexDictionaryStreamRead(stream);
       }
   
       RwStreamClose(stream, NULL);
   }
   \endverbatim
 */
RwTexDictionary *
RwTexDictionaryStreamRead(RwStream *stream)
{
    RwUInt32            size, version;

    RWAPIFUNCTION(RWSTRING("RwTexDictionaryStreamRead"));
    RWASSERT(stream);

    if (!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
    {
        RWRETURN((RwTexDictionary *)NULL);
    }

    if ((version >= rwLIBRARYBASEVERSION) && (version <= rwLIBRARYCURRENTVERSION))
    {
        RwTexDictionary    *texDict;
        rwStreamTexDictionary    binTexDict;

        /* Read the texture dictionary back */
        RWASSERT(size <= sizeof(binTexDict));
        memset(&binTexDict, 0, sizeof(binTexDict));
        if (RwStreamRead(stream, &binTexDict, size) != size)
        {
            RWRETURN((RwTexDictionary *)NULL);
        }

        RwMemNative(&binTexDict, sizeof(binTexDict));

        texDict = RwTexDictionaryCreate();
        if (!texDict)
        {
            RWRETURN((RwTexDictionary *)NULL);
        }

        while (binTexDict.numTextures--)
        {
            RwTexture          *newTex;

            /* Find the native texture */
            if (!RwStreamFindChunk(stream, rwID_TEXTURENATIVE, &size, &version))
            {
                /* Tidy up and exit */
                RwTexDictionaryForAllTextures(texDict, destroyTexture, NULL);
                RwTexDictionaryDestroy(texDict);

                RWRETURN((RwTexDictionary *)NULL);
            }

            if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
            {
                /* Tidy up and exit */
                RwTexDictionaryForAllTextures(texDict, destroyTexture, NULL);
                RwTexDictionaryDestroy(texDict);

                RWERROR((E_RW_BADVERSION));
                RWRETURN((RwTexDictionary *)NULL);
            }

            /* Call the driver to read the texture */
            if (!RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, &newTex, size)))
            {
                /* Tidy up and exit */
                RwTexDictionaryForAllTextures(texDict, destroyTexture, NULL);
                RwTexDictionaryDestroy(texDict);

                RWRETURN((RwTexDictionary *)NULL);
            }

            if (!newTex)
            {
                /* Tidy up and exit */
                RwTexDictionaryForAllTextures(texDict, destroyTexture, NULL);
                RwTexDictionaryDestroy(texDict);

                RWRETURN((RwTexDictionary *)NULL);
            }

            /* And read in the data chunks for the texture */
            if (!_rwPluginRegistryReadDataChunks(&textureTKList, stream, newTex))
            {
                /* Tidy up and exit */
                RwTexDictionaryForAllTextures(texDict, destroyTexture, NULL);
                RwTexDictionaryDestroy(texDict);

                RWRETURN((RwTexDictionary *)NULL);
            }

            RwTexDictionaryAddTexture(texDict, newTex);
        }

        /* And read in the data chunks for the texture dictionary */
        if (!_rwPluginRegistryReadDataChunks(&texDictTKList, stream, texDict))
        {
            /* Tidy up and exit */
            RwTexDictionaryForAllTextures(texDict, destroyTexture, NULL);
            RwTexDictionaryDestroy(texDict);

            RWRETURN((RwTexDictionary *)NULL);
        }

        /* And we're all done */
        RWRETURN(texDict);
    }
    else
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RwTexDictionary *)NULL);
    }
}

/**
 * \ingroup rwtexture
 * \ref RwTextureChunkInfoRead extracts Texture Chunk Info data from
 * a RenderWare stream. The data is converted from its original format and
 * inserted into an RwTextureChunkInfo structure. A pointer to this structure 
 * is returned on success.
 *
 * \param stream  Pointer to the stream from which to read.
 * \param textureChunkInfo  Pointer to the Chunk Info structure to be filled in.
 * \param bytesRead  Pointer to an RwInt32 which will be filled with the number of bytes
 * read.
 *
 * \return Returns a pointer to the filled Chunk Info structure if successful,
 * or NULL if an error occurred.
 *
 * \see RwTextureStreamRead
 * \see RwTextureStreamWrite
 *
 */
RwTextureChunkInfo *
RwTextureChunkInfoRead(RwStream *stream,
                       RwTextureChunkInfo *textureChunkInfo,
                       RwInt32 *bytesRead)
{
    RwUInt32            size, readSize;
    rwStreamTexture     texFiltAddr;

    RWAPIFUNCTION(RWSTRING("RwTextureChunkInfoRead"));
    RWASSERT(stream);
    RWASSERT(textureChunkInfo);

    if (!RwStreamFindChunk(stream, (RwUInt32)rwID_STRUCT, 
                           &size, (RwUInt32 *)NULL))
    {
        RWRETURN((RwTextureChunkInfo *)NULL);
    }

    /* Read the filtering mode */
    RWASSERT(size <= sizeof(texFiltAddr));
    readSize = sizeof(rwStreamTexture);
    memset(&texFiltAddr, 0, readSize);
    if (RwStreamRead(stream, &texFiltAddr, readSize) != readSize)
    {
        RWRETURN((RwTextureChunkInfo *)NULL);
    }

    *bytesRead = size + (sizeof(RwInt32) * 3);
    /* move on to known place */
    RwStreamSkip(stream, size - readSize);

    /* Convert it */
    RwMemNative(&texFiltAddr, sizeof(texFiltAddr));

    /* Extract filtering and addressing */
    textureChunkInfo->filtering = (RwTextureFilterMode) 
        (texFiltAddr.filterAndAddress & 0xFF);
    textureChunkInfo->addressing = (RwTextureAddressMode) 
        ((texFiltAddr.filterAndAddress >> 8) & 0xFF);

    RWRETURN(textureChunkInfo);
}

