
/****************************************************************************
 *                                                                          *
 * module : native.c                                                        *
 *                                                                          *
 * purpose: Write/Read native data                                          *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

#include <rwcore.h>
#include "baworld.h"

#include "p2stdclsw.h"

#include "skyisms.h"
#include "matputil.h"
#include "ps2clusterattribs.h"
#include "nodeps2matinstance.h"

#include "native.h"

#if (!defined(DOXYGEN))
static const char __RWUNUSED__ rcsid[] = 
    "@@@@(#)$Id: native.c,v 1.11 2001/07/12 10:05:26 johns Exp $";
#endif /* (!defined(DOXYGEN)) */


/****************************************************************************
 Externed globals
 */

static RwBool
PointerToOffset(rwPS2ResEntryHeader *ps2ResHeader)
{
    RwUInt32    *data;
    RwBool      done = FALSE;
    RwBool      ref = TRUE;

    RWFUNCTION(RWSTRING("PointerToOffset"));

    data = (RwUInt32 *)ps2ResHeader->data;

    while (!done)
    {
        RwInt32 qwc;
        RwInt32 id;

        /* Bits 0:15 are the QWC */
        qwc = (*data) & 0xFFFF;

        /* Bits 28:30 are the ID */
        id = ((*data) >> 28) & 0x7;

        switch (id)
        {
        case 0: /* refe */
            done = TRUE;
            break;
        case 1: /* cnt */
            {
                /* Jump to next tag */
                data += ((qwc + 1) * 4);
            }
            break;
        case 2: /* next */
            break;
        case 3: /* ref */
            {
                data++;

                /* Convert the pointer to an offset */
                *data = (RwUInt32)(((u_long128 *)(*data)) - ps2ResHeader->data);

                /* Jump to next tag */
                data += 3;

                ref = FALSE;
            }
            break;
        case 4: /* refs */
            break;
        case 5: /* call */
            break;
        case 6: /* ret */
            done = TRUE;
            break;
        case 7: /* end */
            done = TRUE;
            break;
        default:
            break;
        }
    }

    RWRETURN(ref);
}

static RwBool
OffsetToPointer(rwPS2ResEntryHeader *ps2ResHeader)
{
    RwUInt32    *data;
    RwBool      done = FALSE;
    RwBool      ref = TRUE;

    RWFUNCTION(RWSTRING("OffsetToPointer"));

    data = (RwUInt32 *)ps2ResHeader->data;

    while (!done)
    {
        RwInt32 qwc;
        RwInt32 id;

        /* Bits 0:15 are the QWC */
        qwc = (*data) & 0xFFFF;

        /* Bits 28:30 are the ID */
        id = ((*data) >> 28) & 0x7;

        switch (id)
        {
        case 0: /* refe */
            done = TRUE;
            break;
        case 1: /* cnt */
            {
                /* Jump to next tag */
                data += ((qwc + 1) * 4);
            }
            break;
        case 2: /* next */
            break;
        case 3: /* ref */
            {
                data++;

                /* Convert the offset to a pointer */
                *((u_long128 **)(data)) = ps2ResHeader->data + (*data);

                /* Jump to next tag */
                data += 3;

                ref = FALSE;
            }
            break;
        case 4: /* refs */
            break;
        case 5: /* call */
            break;
        case 6: /* ret */
            done = TRUE;
            break;
        case 7: /* end */
            done = TRUE;
            break;
        default:
            break;
        }
    }

    RWRETURN(ref);
}

RwStream *
_rpGeometryNativeWrite(RwStream *stream, 
                       const RpGeometry *geometry)
{
    static RwPlatformID id = rwID_PS2;

    RWFUNCTION(RWSTRING("_rpGeometryNativeWrite"));
    RWASSERT(stream);
    RWASSERT(geometry);

    if (geometry->instanceFlags & rpGEOMETRYPERSISTENT)
    {
        const RwMeshCache * const meshCache  = (const RwMeshCache *)
            RWMESHCACHEFROMCONSTGEOMETRY(geometry);
        RwUInt32    size = 0;
        const RwUInt32    lengthOfMeshesArray = 
            meshCache->lengthOfMeshesArray;
        RwUInt32    i;

        /* Platfrom specific ID */
        size += sizeof(RwPlatformID);

        for (i = 0; i < lengthOfMeshesArray; i++)
        {
            RwResEntry          *repEntry;
            rwPS2ResEntryHeader *ps2ResHeader;

            repEntry = *rwMeshCacheGetEntryRef(meshCache, i);
            
            /* Wait for resEntry to be finished with before modifying */
            reDestroyCallback(repEntry);

            ps2ResHeader = (rwPS2ResEntryHeader *)(repEntry + 1);

            /* RwUInt32 - material index & opaque flag & size of resEntry, 
             * resEntry data size */
            size += ( (sizeof(RwUInt32) * 3) + 
                      sizeof(rwPS2ResEntryHeader) +
                      (repEntry->size - (((RwUInt8 *)ps2ResHeader->data) - 
                                         ((RwUInt8 *)repEntry))) );
        }

        /* Write a chunk header so we get a VERSION NUMBER */
        if (!RwStreamWriteChunkHeader(stream, rwID_STRUCT, size))
        {
            RWRETURN((RwStream *)NULL);
        }

        /* Write a platform unique identifier */
        if (!RwStreamWrite(stream, (void *)&id, sizeof(RwPlatformID)))
        {
            RWRETURN((RwStream *)NULL);
        }

        /* Write some native data */
        for (i = 0; i < meshCache->lengthOfMeshesArray; i++)
        {
            RwResEntry          *repEntry;
            rwPS2ResEntryHeader *ps2ResHeader;
            RwUInt32            size;
            RwUInt32            opaque;

            repEntry = *rwMeshCacheGetEntryRef(meshCache, i);
            ps2ResHeader = (rwPS2ResEntryHeader *)(repEntry + 1);

            /* Minimum 16Byte alignment */
            size = (repEntry->size -
                (((RwUInt8 *)ps2ResHeader->data) - ((RwUInt8 *)repEntry)));

            if (!RwStreamWrite(stream, (void *)&size, sizeof(RwUInt32)))
            {
                RWRETURN((RwStream *)NULL);
            }

            /* Convert offsets into pointers using the base address */
            opaque = PointerToOffset(ps2ResHeader);

            if (!RwStreamWrite(stream, (void *)&opaque, sizeof(RwUInt32)))
            {
                RWRETURN((RwStream *)NULL);
            }

            /* NOTE: We do not serialise the ps2ResHeader any more. Its
             * data (aside from the refCnt/clrCnt members, should not
             * be accessed, given that the data is meant to be PERSISTENT.
             * In the persistent case, we'll not pass cluster data pointers to the
             * mesh instance CB, so it can ONLY upload static data, etc.
             * Resheader values are initialised to zero on load. */

            /* Write the native data */
            if (!RwStreamWrite(stream, (void *)ps2ResHeader->data, size))
            {
                RWRETURN((RwStream *)NULL);
            }

            /* Convert pointers to offsets from the base address */
            OffsetToPointer(ps2ResHeader);

            /* Material pointer was used before when MarkJ had the meshHeader/Meshes in
             * the object removed. They've been reinstated (minus indices) so storing
             * material pointer seems to be redundant (it was serialised as a matList
             * index and restored on load), this reminder's here juuuust in case...*/

            SyncDCache(repEntry, (RwUInt8 *)repEntry + repEntry->size + sizeof(RwResEntry));
        }
    }

    RWRETURN(stream);
}

RpGeometry *
_rpGeometryNativeRead(RwStream *stream, RpGeometry *geometry)
{
    RwUInt32        version;
    RwInt32         lengthOfMeshesArray;
    RwInt32         i;
    RwPlatformID    id;
    RwMeshCache     *meshCache;

    RWFUNCTION(RWSTRING("_rpGeometryNativeRead"));
    RWASSERT(stream);
    RWASSERT(geometry);

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

    if ((version < rwLIBRARYBASEVERSION) ||
        (version > rwLIBRARYCURRENTVERSION))
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RpGeometry *)NULL);
    }

    /* Read the platform unique identifier */
    if (RwStreamRead(stream, (void *)&id, sizeof(RwPlatformID)) !=
        sizeof(RwPlatformID))
    {
        RWRETURN((RpGeometry *)NULL);
    }

    /* Check this data is funky for this platfrom */
    if (rwID_PS2 != id)
    {
        RWASSERT(rwID_PS2 == id);

        RWRETURN((RpGeometry *)NULL);
    }

    /* Assume a mesh for every material - This is very scary */
    meshCache =
        rpGeometryGetMeshCache(geometry, geometry->matList.numMaterials);

    lengthOfMeshesArray = meshCache->lengthOfMeshesArray;
    
    for (i = 0; i < lengthOfMeshesArray; i++)
    {
        RwResEntry          **repEntry;
        rwPS2ResEntryHeader *ps2ResHeader;
        RwUInt32            size;
        RwUInt32            opaque;

        /* Size of data to load */
        if (RwStreamRead(stream, (void *)&size, sizeof(RwUInt32)) !=
            sizeof(RwUInt32))
        {
            RWRETURN((RpGeometry *)NULL);
        }

        repEntry = rwMeshCacheGetEntryRef(meshCache, i);

        /* Allocate the memory */
        if (!_rwResourcesAllocateResEntry((void *)geometry,
                                          repEntry,
                                          size + sizeof(rwPS2ResEntryHeader) + 127,
                                          rwMEMORYPOOLDEFAULT,
                                          reDestroyCallback))
        {
            RWRETURN((RpGeometry *)NULL);
        }

        ps2ResHeader = (rwPS2ResEntryHeader *)(*repEntry + 1);

        if (RwStreamRead(stream, (void *)&opaque, sizeof(RwUInt32)) !=
            sizeof(RwUInt32))
        {
            RWRETURN((RpGeometry *)NULL);
        }

        /* Init rwPS2ResEntryHeader */
        ps2ResHeader->refCnt = 0;
        ps2ResHeader->clrCnt = 0;
        /* Zero all other members to indicate they shouldn't be accessed */
        memset(ps2ResHeader, 0, sizeof(rwPS2ResEntryHeader));

        /* Cache-line-align the start of data in all circumstances */
        ps2ResHeader->data = (u_long128 *)(((unsigned int)(ps2ResHeader + 1) + 63) & ~63);
#ifdef DMAALIGN
        if (opaque)
        {
            ps2ResHeader->data = (u_long128*)(((unsigned int)ps2ResHeader->data + 127) & ~0x7f);
        }
#endif /* DMAALIGN */

        /* ... */

        /* Read the native data */
        if (RwStreamRead(stream, (void *)ps2ResHeader->data, size) != size)
        {
            RWRETURN((RpGeometry *)NULL);
        }

        /* Convert pointers to offsets from the base address */
        OffsetToPointer(ps2ResHeader);

        /* Material pointer was used before when MarkJ had the meshHeader/Meshes in
         * the object removed. They've been reinstated (minus indices) so storing
         * material pointer seems to be redundant (it was serialised as a matList
         * index and restored on load), this reminder's here juuuust in case...*/

        SyncDCache(*repEntry, (RwUInt8 *)*repEntry + (*repEntry)->size + sizeof(RwResEntry));
    }

    geometry->instanceFlags = rpGEOMETRYPERSISTENT;

    RWRETURN(geometry);
}

RwInt32
_rpGeometryNativeSize(const RpGeometry *geometry)
{
    RwUInt32    size = 0;

    RWFUNCTION(RWSTRING("_rpGeometryNativeSize"));
    RWASSERT(geometry);

    if (geometry->instanceFlags & rpGEOMETRYPERSISTENT)
    {
        const RwMeshCache * const meshCache  = (const RwMeshCache *)
            RWMESHCACHEFROMCONSTGEOMETRY(geometry);
        const RwUInt32    lengthOfMeshesArray = 
            meshCache->lengthOfMeshesArray;
        RwUInt32    i;

        /* Chunk header for version number + platfrom specific ID */
        size += sizeof(RwPlatformID) + rwCHUNKHEADERSIZE;

        /* Calculate the native data size */
        for (i = 0; i < lengthOfMeshesArray; i++)
        {
            RwResEntry          *repEntry;
            rwPS2ResEntryHeader *ps2ResHeader;

            repEntry = *rwMeshCacheGetEntryRef(meshCache, i);

            ps2ResHeader = (rwPS2ResEntryHeader *)(repEntry + 1);

            /* RwUInt32 - size of resEntry
                        - opaque
               res Data - resEntry data size
             */
            size += (sizeof(RwUInt32) * 2) +
                (repEntry->size -
                (((RwUInt8 *)ps2ResHeader->data) - ((RwUInt8 *)repEntry)));
        }
    }

    RWRETURN(size);
}
