/*----------------------------------------------------------------------------
 *
 * File:
 * eas_xmf.c
 *  5
 * Contents and purpose:
 * XMF File Parser
 *
 * Copyright Sonic Network Inc. 2005

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *----------------------------------------------------------------------------
 * Revision Control:
 *   $Revision: 501 $
 *   $Date: 2006-12-11 17:53:36 -0800 (Mon, 11 Dec 2006) $
 *----------------------------------------------------------------------------
*/

#include <log/log.h>

#include "eas_data.h"
#include "eas_miditypes.h"
#include "eas_parser.h"
#include "eas_report.h"
#include "eas_host.h"
#include "eas_midi.h"
#include "eas_xmf.h"
#include "eas_xmfdata.h"
#include "eas_config.h"
#include "eas_vm_protos.h"
#include "eas_mdls.h"
#include "eas_smf.h"

#if defined (_ZLIB_UNPACKER)
#include <zlib.h>
#include <string.h>

typedef struct {
    char* ptr;
    int size;
} MemSpan;

// ReadAt func for ZLIB packed data
static int MemReadAt(void *handle, void *buf, int offset, int size){
    MemSpan* span = handle;
    if (size > span->size - offset) {
        size = span->size - offset;
    }

    if (size > 0) {
        memcpy(buf, span->ptr + offset, size);
    }
    return size;
}

static int MemSize(void *handle) {
    MemSpan* span = handle;
    return span->size;
}
#endif


/* XMF header file type */
#define XMF_IDENTIFIER          0x584d465f
#define XMF_VERSION_1_00        0x312e3030
#define XMF_VERSION_1_01        0x312e3031
#define XMF_VERSION_2_00        0x322e3030
#define XMF_FILE_TYPE           0x00000002
#define XMF_SPEC_LEVEL          0x00000001
#define XMF_RIFF_CHUNK          0x52494646
#define XMF_RIFF_DLS            0x444c5320
#define XMF_SMF_CHUNK           0x4d546864

/* local prototypes */
static EAS_RESULT XMF_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset);
static EAS_RESULT XMF_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT XMF_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime);
static EAS_RESULT XMF_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode);
static EAS_RESULT XMF_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_STATE *pState);
static EAS_RESULT XMF_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT XMF_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT XMF_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT XMF_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT XMF_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_IPTR value);
static EAS_RESULT XMF_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_IPTR *pValue);
static EAS_RESULT XMF_FindFileContents (EAS_HW_DATA_HANDLE hwInstData, S_XMF_DATA *pXMFData);
static EAS_RESULT XMF_ReadNode (EAS_HW_DATA_HANDLE hwInstData, S_XMF_DATA *pXMFData, EAS_I32 nodeOffset, EAS_I32 endOffset, EAS_I32 *pLength, EAS_I32 depth);
static EAS_RESULT XMF_ReadVLQ (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *remainingBytes, EAS_I32 *value);


/*----------------------------------------------------------------------------
 *
 * XMF_Parser
 *
 * This structure contains the functional interface for the XMF parser
 *----------------------------------------------------------------------------
*/
const S_FILE_PARSER_INTERFACE EAS_XMF_Parser =
{
    XMF_CheckFileType,
    XMF_Prepare,
    XMF_Time,
    XMF_Event,
    XMF_State,
    XMF_Close,
    XMF_Reset,
    XMF_Pause,
    XMF_Resume,
    NULL,
    XMF_SetData,
    XMF_GetData,
    NULL
};

/*----------------------------------------------------------------------------
 * XMF_CheckFileType()
 *----------------------------------------------------------------------------
 * Purpose:
 * Check the file type to see if we can parse it
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset)
{
    S_XMF_DATA *pXMFData;
    EAS_RESULT result;
    EAS_U32 temp;

    /* assume we don't recognize it initially */
    *ppHandle = NULL;

    /* read the file identifier */
    if ((result = EAS_HWGetDWord(pEASData->hwInstData, fileHandle, &temp, EAS_TRUE))  != EAS_SUCCESS)
        return result;
    if (temp != XMF_IDENTIFIER)
        return EAS_SUCCESS;

    /* read the version */
    if ((result = EAS_HWGetDWord(pEASData->hwInstData, fileHandle, &temp, EAS_TRUE))  != EAS_SUCCESS)
        return result;

    if (temp == XMF_VERSION_2_00)
    {
        /* read the file type */
        result = EAS_HWGetDWord(pEASData->hwInstData, fileHandle, &temp, EAS_TRUE);
        if (result != EAS_SUCCESS)
            return result;

        if (temp != XMF_FILE_TYPE)
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR,
                          "XMF file type was 0x%08x, expected 0x%08x\n", temp, XMF_FILE_TYPE); */ }
            return EAS_SUCCESS;
        }

        /* read the spec level */
        result = EAS_HWGetDWord(pEASData->hwInstData, fileHandle, &temp, EAS_TRUE);
        if (result != EAS_SUCCESS)
            return result;

        if (temp != XMF_SPEC_LEVEL)
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR,
                          "XMF file spec was 0x%08x, expected 0x%08x\n", temp, XMF_SPEC_LEVEL); */ }
            return EAS_SUCCESS;
        }
    }
    else if (temp != XMF_VERSION_1_00 && temp != XMF_VERSION_1_01)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "XMF file version was 0x%08x\n", temp); */ }
        return EAS_SUCCESS;
    }

    /* check for static memory allocation */
    if (pEASData->staticMemoryModel)
        pXMFData = EAS_CMEnumData(EAS_CM_XMF_DATA);
    else
        pXMFData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_XMF_DATA));
    if (!pXMFData)
        return EAS_ERROR_MALLOC_FAILED;

    /* zero the memory to insure complete initialization */
    EAS_HWMemSet((void *)pXMFData,0, sizeof(S_XMF_DATA));

    pXMFData->fileHandle = fileHandle;
    pXMFData->fileOffset = offset;

    /* locate the SMF and DLS contents */
    if ((result = XMF_FindFileContents(pEASData->hwInstData, pXMFData)) != EAS_SUCCESS)
    {
        EAS_Report(_EAS_SEVERITY_ERROR, "Failed to parse XMF file: %ld\n", result);
        EAS_HWFree(pEASData->hwInstData, pXMFData);
        return result;
    }

    /* let the SMF parser take over */
    if ((result = EAS_HWFileSeek(pEASData->hwInstData, pXMFData->smfFileHandle, pXMFData->smfOffset)) != EAS_SUCCESS) {
        EAS_HWFree(pEASData->hwInstData, pXMFData);
        return result;
    }
    if ((result = SMF_CheckFileType(pEASData, pXMFData->smfFileHandle, &pXMFData->pSMFData, pXMFData->smfOffset)) != EAS_SUCCESS) {
        EAS_HWFree(pEASData->hwInstData, pXMFData);
        return result;
    }
    *ppHandle = pXMFData;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * XMF_Prepare()
 *----------------------------------------------------------------------------
 * Purpose:
 * Prepare to parse the file. Allocates instance data (or uses static allocation for
 * static memory model).
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_XMF_DATA* pXMFData;
    EAS_RESULT result;

    /* parse DLS collection */
    pXMFData = (S_XMF_DATA*) pInstData;
    if (pXMFData->dlsOffset != 0)
    {
        if ((result = DLSParser(pEASData->hwInstData, pXMFData->dlsFileHandle, pXMFData->dlsOffset, &pXMFData->pDLS)) != EAS_SUCCESS)
        {
            EAS_Report(_EAS_SEVERITY_WARNING, "Error converting XMF DLS data: %ld\n", result);
            return result;
        }
#if defined (_ZLIB_UNPACKER)
        if (pXMFData->dlsFileSpan != NULL) {
            // dls is in memory, close and free it now
            MemSpan* span = pXMFData->dlsFileSpan;
            EAS_HWCloseFile(pEASData->hwInstData, pXMFData->dlsFileHandle);
            EAS_HWFree(pEASData->hwInstData, span->ptr);
            EAS_HWFree(pEASData->hwInstData, span);
            pXMFData->dlsFileSpan = NULL;
            pXMFData->dlsFileHandle = NULL;
        }
#endif
    }

    /* Prepare the SMF parser */
    if ((result = SMF_Prepare(pEASData, pXMFData->pSMFData)) != EAS_SUCCESS)
        return result;

    /* if no DLS file, skip this step */
    if (pXMFData->pDLS == NULL)
        return EAS_SUCCESS;

    /* tell the synth to use the DLS collection */
    result = VMSetDLSLib(((S_SMF_DATA*) pXMFData->pSMFData)->pSynth, pXMFData->pDLS);
    if (result == EAS_SUCCESS)
    {
        DLSAddRef(pXMFData->pDLS);
        VMInitializeAllChannels(pEASData->pVoiceMgr, ((S_SMF_DATA*) pXMFData->pSMFData)->pSynth);
    }
    return result;
}

/*----------------------------------------------------------------------------
 * XMF_Time()
 *----------------------------------------------------------------------------
 * Purpose:
 * Returns the time of the next event in msecs
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 * pTime            - pointer to variable to hold time of next event (in msecs)
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime)
{
    return SMF_Time(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData, pTime);
}

/*----------------------------------------------------------------------------
 * XMF_Event()
 *----------------------------------------------------------------------------
 * Purpose:
 * Parse the next event in the file
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode)
{
    return SMF_Event(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData, parserMode);
}

/*----------------------------------------------------------------------------
 * XMF_State()
 *----------------------------------------------------------------------------
 * Purpose:
 * Returns the current state of the stream
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 * pState           - pointer to variable to store state
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_STATE *pState)
{
    return SMF_State(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData, pState);
}

/*----------------------------------------------------------------------------
 * XMF_Close()
 *----------------------------------------------------------------------------
 * Purpose:
 * Close the file and clean up
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_XMF_DATA* pXMFData;
    EAS_RESULT result;

    pXMFData = (S_XMF_DATA *)pInstData;

    // if dls is in memory, its handle has been closed already after parsed
    // close the SMF stream, it will close the smffilehandle
    if ((result = SMF_Close(pEASData, pXMFData->pSMFData)) != EAS_SUCCESS)
        return result;
    // however smffilehandle could be a memfile, in this case xmf file handle is still open
    // so check it
#if defined (_ZLIB_UNPACKER)
    if (pXMFData->smfFileSpan != NULL) {
        // smf is in memory, free it now
        MemSpan* span = pXMFData->smfFileSpan;
        EAS_HWFree(pEASData->hwInstData, span->ptr);
        EAS_HWFree(pEASData->hwInstData, span);

        // then close xmf
        EAS_HWCloseFile(pEASData->hwInstData, pXMFData->fileHandle);
    }
#endif

    if (pXMFData->pDLS)
        DLSCleanup(pEASData->hwInstData, pXMFData->pDLS);

    /* if using dynamic memory, free it */
    if (!pEASData->staticMemoryModel)
    {
        /* free the instance data */
        EAS_HWFree(pEASData->hwInstData, pXMFData);
    }

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * XMF_Reset()
 *----------------------------------------------------------------------------
 * Purpose:
 * Reset the sequencer. Used for locating backwards in the file.
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    return SMF_Reset(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData);
}

/*----------------------------------------------------------------------------
 * XMF_Pause()
 *----------------------------------------------------------------------------
 * Purpose:
 * Pauses the sequencer. Mutes all voices and sets state to pause.
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    return SMF_Pause(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData);
}

/*----------------------------------------------------------------------------
 * XMF_Resume()
 *----------------------------------------------------------------------------
 * Purpose:
 * Resume playing after a pause, sets state back to playing.
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    return SMF_Resume(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData);
}

/*----------------------------------------------------------------------------
 * XMF_SetData()
 *----------------------------------------------------------------------------
 * Purpose:
 * Sets the playback rate of the underlying SMF file
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 * rate             - rate (28-bit fraction)
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_IPTR value)
{
    return SMF_SetData(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData, param, value);
}

/*----------------------------------------------------------------------------
 * XMF_GetData()
 *----------------------------------------------------------------------------
 * Purpose:
 * Gets the file type
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 * rate             - rate (28-bit fraction)
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_IPTR *pValue)
{
    EAS_RESULT result;

    /* call SMF parser to get value */
    if ((result = SMF_GetData(pEASData, ((S_XMF_DATA*) pInstData)->pSMFData, param, pValue)) != EAS_SUCCESS)
        return result;

    /* special case for file type */
    if (param == PARSER_DATA_FILE_TYPE)
    {
        if (*pValue == EAS_FILE_SMF0)
            *pValue = EAS_FILE_XMF0;
        else if (*pValue == EAS_FILE_SMF1)
            *pValue = EAS_FILE_XMF1;
    }

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * XMF_FindFileContents()
 *----------------------------------------------------------------------------
 * Purpose:
 * Finds SMF data and DLS data in XMF file, and remembers offset for each.
 * If more than one is found, uses the first one found of each.
 * Makes assumptions about the format of a mobile XMF file
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * pXMFData         - pointer to XMF parser instance data
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_FindFileContents (EAS_HW_DATA_HANDLE hwInstData, S_XMF_DATA *pXMFData)
{
    EAS_RESULT result;
    EAS_I32 value;
    EAS_I32 treeStart;
    EAS_I32 treeEnd;
    EAS_I32 length;
    EAS_I32 node_depth = 0 ;
    EAS_I32 fileLength;
    EAS_U32 remainingBytes;

    /* initialize file handles to see if there is smf and dls */
    pXMFData->smfFileHandle = pXMFData->dlsFileHandle = NULL;

    /* read file length. We're arbitrarily limiting the size of this field to
     * 16 bytes, since we don't yet have an actual limit. Once the field is
     * read, we correct remainingBytes using the actual value that we had read.
     * Since upon return from XMF_ReadVLQ(), remainingBytes represents how much
     * is left unread, it will be set to (16 - size_of_field). Therefore, size
     * of field is (16 - remainingBytes), which is what we need to subtract from
     * the total size. */
    const EAS_U32 kInitialRemainingBytes = 16;

    remainingBytes = kInitialRemainingBytes;
    if ((result = XMF_ReadVLQ(hwInstData,
                              pXMFData->fileHandle,
                              &remainingBytes,
                              &fileLength)) != EAS_SUCCESS)
        return result;
    if (fileLength < 0)
        return EAS_ERROR_FILE_FORMAT;
    remainingBytes = fileLength + remainingBytes - kInitialRemainingBytes;

    /* read MetaDataTypesTable length and skip over it */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingBytes, &value)) != EAS_SUCCESS)
        return result;
    if (value > remainingBytes)
        return EAS_ERROR_FILE_FORMAT;
    if ((result = EAS_HWFileSeekOfs(hwInstData, pXMFData->fileHandle, value)) != EAS_SUCCESS)
        return result;
    remainingBytes -= value;

    /* get TreeStart and TreeEnd offsets */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingBytes, &treeStart)) != EAS_SUCCESS)
        return result;
    if (treeStart < 0)
        return EAS_ERROR_FILE_FORMAT;

    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingBytes, &treeEnd)) != EAS_SUCCESS)
        return result;
    if (treeEnd < treeStart || treeEnd >= fileLength)
        return EAS_ERROR_FILE_FORMAT;

    if ((result = XMF_ReadNode(hwInstData, pXMFData, treeStart, treeEnd + 1, &length, node_depth)) != EAS_SUCCESS)
        return result;

    /* check for SMF data */
    if (pXMFData->smfFileHandle == NULL)
    {
        EAS_Report(_EAS_SEVERITY_ERROR, "No SMF data found in XMF file\n");
        return EAS_ERROR_FILE_FORMAT;
    }

    /* check for SFM in wrong order */
    if ((pXMFData->dlsOffset > 0) && (pXMFData->smfOffset < pXMFData->dlsOffset))
        EAS_Report(_EAS_SEVERITY_WARNING, "DLS data must precede SMF data in Mobile XMF file\n");

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * XMF_ReadNode()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 * Inputs:
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_ReadNode (EAS_HW_DATA_HANDLE hwInstData, S_XMF_DATA *pXMFData, EAS_I32 nodeOffset, EAS_I32 endOffset, EAS_I32 *pLength, EAS_I32 depth)
{
    EAS_RESULT result;
    EAS_I32 refType;
    EAS_I32 numItems;
    EAS_I32 offset;
    EAS_I32 length;
    EAS_I32 headerLength;
    EAS_U32 chunkType;
    EAS_U32 remainingInlineBytes;
    EAS_U32 deltaBytes;
    EAS_BOOL zlibPacked = EAS_FALSE;
    EAS_U32 unpackedSize = 0;

    /* check the depth of current node*/
    if ( depth > 100 )
        return EAS_ERROR_FILE_FORMAT;

    /* this will be used to check we don't read past the node limits */
    remainingInlineBytes = endOffset - nodeOffset;

    /* seek to start of node */
    if ((result = EAS_HWFileSeek(hwInstData, pXMFData->fileHandle, nodeOffset)) != EAS_SUCCESS)
        return result;

    /* get node length */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, pLength)) != EAS_SUCCESS)
        return result;
    if ((*pLength < 0) || (*pLength > endOffset - nodeOffset))
        return EAS_ERROR_FILE_FORMAT;

    /* recalculate our end offset and remaining bytes. */
    deltaBytes = endOffset - nodeOffset - *pLength;
    if (remainingInlineBytes < deltaBytes)
        return EAS_ERROR_FILE_FORMAT;

    remainingInlineBytes -= deltaBytes;
    endOffset = nodeOffset + *pLength;

    /* get number of contained items */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, &numItems)) != EAS_SUCCESS)
        return result;
    if (numItems < 0)
        return EAS_ERROR_FILE_FORMAT;

    /* get node header length */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, &headerLength)) != EAS_SUCCESS)
        return result;
    if (headerLength < 0 || headerLength > *pLength)
        return EAS_ERROR_FILE_FORMAT;

    /* get metadata length */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, &length)) != EAS_SUCCESS)
        return result;
    if (length < 0)
        return EAS_ERROR_FILE_FORMAT;

    // skip metadata and go to node unpackers
    if ((result = EAS_HWFileSeekOfs(hwInstData, pXMFData->fileHandle, length)) != EAS_SUCCESS)
        return result;

    // get node unpackers length
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, &length)) != EAS_SUCCESS)
        return result;

    EAS_U32 nodeUnpackerRemaining = length;
    while (nodeUnpackerRemaining != 0) {
        EAS_U32 temp;
        result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &nodeUnpackerRemaining, &temp);
        if (result != EAS_SUCCESS)
            return result;
        if (temp != 0) {
            EAS_Report(_EAS_SEVERITY_ERROR, "Unrecognized UnpackerTypeID 0x%x\n", temp);
            return EAS_ERROR_FILE_FORMAT;
        }
        
        result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &nodeUnpackerRemaining, &temp);
        if (result != EAS_SUCCESS)
            return result;
        if (temp == 0) {
            // no unpacker???
        } else if (temp == 1) {
            // ZLIB
#if !defined (_ZLIB_UNPACKER)
            EAS_Report(_EAS_SEVERITY_ERROR, "ZLIB unpacker is not supported in this build\n");
            return EAS_ERROR_FILE_FORMAT;
#endif
            if (zlibPacked) {
                EAS_Report(_EAS_SEVERITY_ERROR, "Multiple ZLIB unpackers found in one XMF node???\n");
                return EAS_ERROR_FILE_FORMAT;
            }
            zlibPacked = EAS_TRUE;
        } else {
            EAS_Report(_EAS_SEVERITY_ERROR, "Unrecognized StandardUnpackerID 0x%x\n", temp);
            return EAS_ERROR_FILE_FORMAT;
        }
        result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &nodeUnpackerRemaining, &unpackedSize);
        if (result != EAS_SUCCESS)
            return result;
    }

    /* get the current location */
    if ((result = EAS_HWFilePos(hwInstData, pXMFData->fileHandle, &offset)) != EAS_SUCCESS)
        return result;

    /* check that we didn't go past the header. */
    if (offset - nodeOffset > headerLength)
        return EAS_FAILURE;

    /* skip to node contents */
    if ((result = EAS_HWFileSeek(hwInstData, pXMFData->fileHandle, nodeOffset + headerLength)) != EAS_SUCCESS)
        return result;
    remainingInlineBytes = endOffset - (nodeOffset + headerLength);

    /* get reference type */
    if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, &refType)) != EAS_SUCCESS)
        return result;

    /* get the current location */
    if ((result = EAS_HWFilePos(hwInstData, pXMFData->fileHandle, &offset)) != EAS_SUCCESS)
        return result;

    /* process file node */
    if (numItems == 0)

    {
        /* if in-file resource, find out where it is and jump to it */
        if (refType == 2)
        {
            if ((result = XMF_ReadVLQ(hwInstData, pXMFData->fileHandle, &remainingInlineBytes, &offset)) != EAS_SUCCESS)
                return result;
            offset += pXMFData->fileOffset;
            if ((result = EAS_HWFileSeek(hwInstData, pXMFData->fileHandle, offset)) != EAS_SUCCESS)
                return result;
        }

        /* or else it must be an inline resource */
        else if (refType != 1)
        {
            EAS_Report(_EAS_SEVERITY_ERROR, "Unexpected reference type %d\n", refType);
            return EAS_ERROR_FILE_FORMAT;
        }

        /* at this point we stop enforcing reading past the node. */
        
        // now we are at the start of data

        EAS_FILE_HANDLE dataFileHandle = pXMFData->fileHandle;
        void* dataSpan = NULL;
#if defined (_ZLIB_UNPACKER)
        // if zlib:
        if (zlibPacked)
        {
            unsigned char inputBuf[16384];
            unsigned char* unpackedData = EAS_HWMalloc(hwInstData, unpackedSize);
            if (!unpackedData)
            {
                EAS_Report(_EAS_SEVERITY_ERROR, "Failed to allocate memory in 0x%x bytes for unpacked XMF data\n", unpackedSize);
                return EAS_ERROR_MALLOC_FAILED;
            }

            // init ZLIB
            EAS_I32 bytesRead = 0;
            z_stream strm;
            strm.zalloc = Z_NULL;
            strm.zfree = Z_NULL;
            strm.opaque = Z_NULL;
            strm.avail_in = 0;
            strm.next_in = Z_NULL;
            strm.next_out = unpackedData;
            strm.avail_out = unpackedSize;
            result = inflateInit(&strm);
            if (result != Z_OK) {
                EAS_Report(_EAS_SEVERITY_ERROR, "Failed to initialize zlib for unpacking XMF data: %ld\n", result);
                EAS_HWFree(hwInstData, unpackedData);
                return EAS_FAILURE;
            }
            // read the data
            while (1) {
                result = EAS_HWReadFile(hwInstData, pXMFData->fileHandle, inputBuf, sizeof(inputBuf), &bytesRead);
                if (result != EAS_SUCCESS && result != EAS_EOF) {
                    EAS_HWFree(hwInstData, unpackedData);
                    inflateEnd(&strm);
                    return result;
                }
                if (bytesRead == 0) {
                    EAS_Report(_EAS_SEVERITY_ERROR, "Unexpected EOF while reading ZLIB data\n");
                    EAS_HWFree(hwInstData, unpackedData);
                    inflateEnd(&strm);
                    return EAS_ERROR_FILE_FORMAT;
                }
                strm.avail_in = bytesRead;
                strm.next_in = inputBuf;
                // inflate
                while (strm.avail_in != 0) {
                    if (strm.avail_out == 0) {
                        EAS_Report(_EAS_SEVERITY_ERROR, "Inconsistent ZLIB uncompressed size in XMF node\n");
                        EAS_HWFree(hwInstData, unpackedData);
                        inflateEnd(&strm);
                        return EAS_ERROR_FILE_FORMAT;
                    }
                    result = inflate(&strm, Z_NO_FLUSH);
                    if (result == Z_STREAM_END) {
                        goto ZLIBReadFin;
                    }
                    if (result != Z_OK) {
                        EAS_Report(_EAS_SEVERITY_ERROR, "ZLIB unpacking error in XMF node: %ld\n", result);
                        EAS_HWFree(hwInstData, unpackedData);
                        inflateEnd(&strm);
                        return EAS_ERROR_FILE_FORMAT;
                    }
                }
            }
            ZLIBReadFin:
            inflateEnd(&strm);

            EAS_FILE memfile;
            MemSpan* pSpan = EAS_HWMalloc(hwInstData, sizeof(MemSpan));
            pSpan->ptr = unpackedData;
            pSpan->size = unpackedSize;
            memfile.handle = pSpan;
            memfile.readAt = MemReadAt;
            memfile.size = MemSize;
            result = EAS_HWOpenFile(hwInstData, &memfile, &dataFileHandle, EAS_FILE_READ);
            if (result != EAS_SUCCESS)
            {
                EAS_HWFree(hwInstData, unpackedData);
                EAS_HWFree(hwInstData, pSpan);
                return result;
            }
            offset = 0;
            dataSpan = pSpan;
        }
#endif

        /* get the chunk type */
        if ((result = EAS_HWGetDWord(hwInstData, dataFileHandle, &chunkType, EAS_TRUE)) != EAS_SUCCESS)
            return result;

        /* found a RIFF chunk, check for DLS type */
        if (chunkType == XMF_RIFF_CHUNK)
        {
            /* skip length */
            if ((result = EAS_HWFileSeekOfs(hwInstData, dataFileHandle, 4)) != EAS_SUCCESS)
                return result;

            /* get RIFF file type */
            if ((result = EAS_HWGetDWord(hwInstData, dataFileHandle, &chunkType, EAS_TRUE)) != EAS_SUCCESS)
                return result;
            if (chunkType == XMF_RIFF_DLS) {
                pXMFData->dlsOffset = offset;
                pXMFData->dlsFileHandle = dataFileHandle;
                pXMFData->dlsFileSpan = dataSpan;
            }
        }

        /* found an SMF chunk */
        else if (chunkType == XMF_SMF_CHUNK) {
            pXMFData->smfOffset = offset;
            pXMFData->smfFileHandle = dataFileHandle;
            pXMFData->smfFileSpan = dataSpan;
        }
    }

    /* folder node, process the items in the list */
    else
    {
        for ( ; numItems > 0; numItems--)
        {
            /* process this item */
            if ((result = XMF_ReadNode(hwInstData, pXMFData, offset, endOffset, &length, depth+1)) != EAS_SUCCESS)
                return result;

            /* seek to start of next item */
            offset += length;
            if ((result = EAS_HWFileSeek(hwInstData, pXMFData->fileHandle, offset)) != EAS_SUCCESS)
                return result;
        }
    }

    return EAS_SUCCESS;
}

#if 0
/*----------------------------------------------------------------------------
 * XMF_FindFileContents()
 *----------------------------------------------------------------------------
 * Purpose:
 * Finds SMF data and DLS data in XMF file, and remembers offset for each.
 * If more than one is found, uses the first one found of each.
 * Makes assumptions about the format of a mobile XMF file
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * pXMFData         - pointer to XMF parser instance data
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_FindFileContents(S_EAS_DATA *pEASData, S_XMF_DATA *pXMFData, EAS_FILE_HANDLE fileHandle)
{
    EAS_RESULT result;
    EAS_I32 offset;
    EAS_I32 value;
    EAS_I32 numItems;
    EAS_I32 length;
    EAS_CHAR id[4];
    EAS_I32 location;

    /* init dls offset, so that we know we haven't found a dls chunk yet */
    pXMFData->dlsOffset = 0;

    /* read file length, ignore it for now */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
        return result;

    /* read MetaDataTypesTable length and skip over it */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
        return result;
    if ((result = EAS_HWFileSeekOfs(pEASData, fileHandle, value)) != EAS_SUCCESS)
        return result;

    /* get TreeStart offset and jump to it */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &offset)) != EAS_SUCCESS)
        return result;
    if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset)) != EAS_SUCCESS)
        return result;

    /* read node length, ignore it for now */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
        return result;

    /* read number of contained items */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &numItems)) != EAS_SUCCESS)
        return result;

    /*read node header length */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
        return result;

    /*go to the node offset */
    if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset + value)) != EAS_SUCCESS)
        return result;

    /* read Reference Type */
    if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
        return result;

    /* make sure it is an in-line resource, for now */
    if (value != 1)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Problem parsing XMF file tree\n"); */ }
        return EAS_FAILURE;
    }

    /* parse through the list of items */
    while (numItems > 0)
    {
        /*get current offset */
        if ((result = EAS_HWFilePos(pEASData->hwInstData, fileHandle, &offset)) != EAS_SUCCESS)
            return result;

        /*read node length */
        if ((result = XMF_ReadVLQ(pEASData, fileHandle, &length)) != EAS_SUCCESS)
            return result;

        /* read number of items */
        if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
            return result;

        /* make sure not a folder */
        if (value != 0)
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Problem parsing XMF file node\n"); */ }
            return EAS_FAILURE;
        }

        /* read offset to resource and jump to it */
        if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
            return result;
        if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset + value)) != EAS_SUCCESS)
            return result;

        /* read Reference Type */
        if ((result = XMF_ReadVLQ(pEASData, fileHandle, &value)) != EAS_SUCCESS)
            return result;

        /* make sure it is an in-line resource */
        if (value != 1)
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Problem parsing XMF file node\n"); */ }
            return EAS_FAILURE;
        }

        /* get current offset as a possible location for SMF file or DLS file */
        if ((result = EAS_HWFilePos(pEASData->hwInstData, fileHandle, &location)) != EAS_SUCCESS)
            return result;

        /* read four bytes */
        if ((result = EAS_HWReadFile(pEASData->hwInstData, fileHandle, id, sizeof(id), &value)) != EAS_SUCCESS)
            return result;

        /* check if DLS */
        if (pXMFData->dlsOffset == 0 && id[0] == 'R' && id[1] == 'I' && id[2] == 'F' && id[3] == 'F')
        {
            //remember offset
            pXMFData->dlsOffset = location;
        }

        /* else check if SMF */
        else if (id[0] == 'M' && id[1] == 'T' && id[2] == 'h' && id[3] == 'd')
        {
            //remember offset
            pXMFData->midiOffset = location;

            //we are done
            return EAS_SUCCESS;
        }

        //one less item
        numItems--;

        //if more data, go to the next item
        if (numItems >0)
        {
            if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset + length)) != EAS_SUCCESS)
                return result;
        }
    }

    return EAS_FAILURE;

}
#endif

/*----------------------------------------------------------------------------
 * XMF_ReadVLQ()
 *----------------------------------------------------------------------------
 * Purpose:
 * Reads a VLQ encoded value from the file referenced by fileHandle
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * fileHandle       - pointer to file handle
 *
 * Outputs:
 * value            - pointer to the value decoded from the VLQ data
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT XMF_ReadVLQ (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *remainingBytes, EAS_I32 *value)
{
    EAS_RESULT result;
    EAS_U8 c;
    *value = 0;

    if ((*remainingBytes)-- == 0)
        return EAS_ERROR_FILE_FORMAT;

    if ((result = EAS_HWGetByte(hwInstData, fileHandle, &c)) != EAS_SUCCESS)
        return result;

    while (c > 0x7F)
    {
        /*lint -e{703} shift for performance */
        *value = (*value << 7) | (c & 0x7F);

        if ((*remainingBytes)-- == 0)
            return EAS_ERROR_FILE_FORMAT;

        if ((result = EAS_HWGetByte(hwInstData, fileHandle, &c)) != EAS_SUCCESS)
            return result;
    }

    /*lint -e{703} shift for performance */
    *value = (*value << 7) | c;

    return EAS_SUCCESS;
}

