/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE OGG VORBIS PROJECT SOURCE CODE.         *
 *                                                                  *
 * THE OGG VORBIS PROJECT SOURCE CODE IS (C) COPYRIGHT 1994-2001    *
 * by the XIPHOPHORUS Company http://www.xiph.org/                  *

 ********************************************************************

 function: implementation of the file format plugin for RealSystem

********************************************************************/
/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: oggfformat.cpp,v 1.3.2.6 2004/11/24 18:02:52 acolwell Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#define INITGUID

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

#include "hxtypes.h"
#include "hxcom.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxplugn.h"
#include "hxfiles.h"
#include "hxformt.h"
#include "chxpckts.h" // CHXHeader::mergeHeaders

#include "oggfformat.h"
#include "codec_info.h"
#include "group_info.h"
#include "oggutil.h"
#include "ogg_stream.h"

#include "debug.h" // DPRINTF()
#define D_OGG_FF 0 //0x4000000

// static variables
const char *COggFileFormat::zm_pDescription = DESCRIPTION;
const char *COggFileFormat::zm_pCopyright = COPYRIGHT;
const char *COggFileFormat::zm_pMoreInfoURL = MORE_INFO_URL;
const char *COggFileFormat::zm_pFileMimeTypes[] = FILE_MIME_TYPES;
const char *COggFileFormat::zm_pFileExtensions[] = FILE_EXTENSIONS;
const char *COggFileFormat::zm_pFileOpenNames[] = FILE_OPEN_NAMES;

STDAPI RMACreateInstance(IUnknown **ppExFileFormatObj)
{
#ifdef _DEBUG
    debug_level() |= D_OGG_FF;
#endif /* _DEBUG */

    DPRINTF(D_OGG_FF, ("RMACreateInstance() called\n"));

    *ppExFileFormatObj = (IUnknown *)(IHXPlugin *)new COggFileFormat();
    if (*ppExFileFormatObj != NULL) {
        (*ppExFileFormatObj)->AddRef();
        return HXR_OK;  
    }

    return HXR_OUTOFMEMORY;
}

// constructor
COggFileFormat::COggFileFormat(void)
    :   m_RefCount(0),
        m_pResponse(NULL),
        m_pPageReader(NULL),
        m_State(Start),
        m_pGetPacketPending(NULL),
        m_bInDispatchPendingRequest(FALSE),
        m_pCurrentStrategy(NULL),
        m_ulLastCachedPageOffset(0),
        m_bIsLive(FALSE),
        m_uCurrentGroup(0)
{
    DPRINTF(D_OGG_FF, ("COFF::COggFileFormat()\n"));
}

STDMETHODIMP COggFileFormat::GetPluginInfo(
    REF(BOOL) bLoadMultiple,
    REF(const char *) pDescription,
    REF(const char *) pCopyright,
    REF(const char *) pMoreInfoURL,
    REF(UINT32) versionNumber
    )
{
    DPRINTF(D_OGG_FF, ("Entering GetPluginInfo()\n"));

    // File Format plugins must be able to be multiply instantiated
    bLoadMultiple = TRUE;

    pDescription = zm_pDescription;
    pCopyright = zm_pCopyright;
    pMoreInfoURL = zm_pMoreInfoURL;
    versionNumber = PLUGIN_VERSION_NUM;

    DPRINTF(D_OGG_FF, ("Exiting GetPluginInfo()\n"));

    return HXR_OK;
}

STDMETHODIMP COggFileFormat::GetFileFormatInfo(
    REF(const char **) pFileMimeTypes,
    REF(const char **) pFileExtensions,
    REF(const char **) pFileOpenNames
    )
{
    DPRINTF(D_OGG_FF, ("Entering GetFileFormatInfo()\n"));

    pFileMimeTypes = zm_pFileMimeTypes;
    pFileExtensions = zm_pFileExtensions;
    pFileOpenNames = zm_pFileOpenNames;

    DPRINTF(D_OGG_FF, ("Exiting GetFileFormatInfo()\n"));

    return HXR_OK;
}

STDMETHODIMP COggFileFormat::InitPlugin(IUnknown* pContext)
{
    DPRINTF(D_OGG_FF, ("Entering InitPlugin()\n"));

    HX_RESULT res = HXR_FAILED;

    if (pContext)
    {
        res = m_streamHdlr.Init(pContext);
    }

    DPRINTF(D_OGG_FF, ("Exiting InitPlugin()\n"));

    return res;
}

STDMETHODIMP COggFileFormat::InitFileFormat(
    IHXRequest *pRequest,
    IHXFormatResponse *pFormatResponse,
    IHXFileObject *pFileObject
    )
{
    HX_RESULT res = HXR_FAILED;

    if (pRequest && pFormatResponse && pFileObject)
    {
        DPRINTF(D_OGG_FF, ("Entering InitFileFormat()\n"));

        // the format response object is used to notify rma core of our status
        m_pResponse = pFormatResponse;
        HX_ADDREF(m_pResponse);

        HX_RELEASE(m_pPageReader);
        m_pPageReader = new COggPageReader();

        if (m_pPageReader)
        {
            changeState(InitPending);

            m_pPageReader->AddRef();

            res = m_pPageReader->Init(this, pFileObject);

            if (HXR_OK != res)
            {
                changeState(Start);
            }
        }
        else
        {
            res = HXR_OUTOFMEMORY;
        }
    } 

    DPRINTF(D_OGG_FF, ("Exiting InitFileFormat()\n"));

    return HXR_OK;
}

STDMETHODIMP COggFileFormat::GetFileHeader(void)
{
    DPRINTF(D_OGG_FF, ("Entering GetFileHeader()\n"));

    HX_RESULT res = HXR_UNEXPECTED;

    if (Ready == m_State)
    {
        changeState(GetFirstStreamInfo);        
        m_pCurrentStrategy = &m_streamInfoStrategy;
        res = m_streamInfoStrategy.Init(m_pPageReader);
        
        if (HXR_OK == res)
        {
            res = m_pPageReader->ReadNextPage();
        }
    }

    DPRINTF(D_OGG_FF, ("Exiting GetFileHeader()\n"));

    return res;
}

STDMETHODIMP COggFileFormat::GetStreamHeader(UINT16 streamNo)
{
    DPRINTF(D_OGG_FF, ("Entering GetStreamHeader(%u)\n", 
                       streamNo));

    IHXValues* pStreamHdr = NULL;
    HX_RESULT res = m_streamHdlr.CreateStreamHeader(streamNo, pStreamHdr);

    if (HXR_OK == res)
    {
        if (!m_bIsLive)
        {
            COggTimestamp duration;
            
            res = m_fileInfo.GetDuration(duration);
        
            if (HXR_OK == res)
            {
                duration.SetSampleRate(1000);
                res = pStreamHdr->SetPropertyULONG32("Duration", 
                                                     duration.Samples());
            }
        }
        
        if (HXR_OK == res)
        {
            res = pStreamHdr->SetPropertyULONG32("StreamNumber", streamNo);
        }

        if (HXR_OK == res)
        { 
            m_pResponse->StreamHeaderReady(HX_STATUS_OK, pStreamHdr);
        }
    }
    HX_RELEASE(pStreamHdr);

    DPRINTF(D_OGG_FF, ("Exiting GetStreamHeader()\n"));

    return res;
}

STDMETHODIMP COggFileFormat::GetPacket(UINT16 streamNo)
{
    DPRINTF(D_OGG_FF, ("Entering GetPacket(%u)\n", streamNo));
    HX_RESULT res = HXR_UNEXPECTED;

    if (m_pPageReader && m_pGetPacketPending)
    {   
        if (Error == m_State)
        {
            m_pResponse->PacketReady(HXR_UNEXPECTED, NULL);
            res = HXR_OK;
        }
        else if (m_streamHdlr.EndOfStream(streamNo))
        {
            m_pResponse->StreamDone(streamNo);
        }
        else
        {
            m_pGetPacketPending[streamNo] = TRUE;

            res = dispatchPendingRequests();

            if (HXR_OK != res)
            {
                if (Ready == m_State)
                {
                    // Request the next page
                    changeState(GetPacketReadPending);
                    res = m_pPageReader->ReadNextPage();
                }
                else
                {
                    // A file operation is already pending.
                    // This request will be serviced when the
                    // pending operation completes
                    res = HXR_OK;
                }
            }
        }
    }

    DPRINTF(D_OGG_FF, ("Exiting GetPacket()\n"));

    return res;
}

STDMETHODIMP COggFileFormat::Seek(UINT32 requestedTime)
{
    DPRINTF(D_OGG_FF, ("COFF::Seek(%u)\n", requestedTime));

    HX_RESULT res = HXR_FAILED;

    if (m_pPageReader && m_pPageReader->IsSeekable())
    {
        // Clear all pending GetPacket() requests
        if (m_pGetPacketPending)
        {
            memset(m_pGetPacketPending, 0, 
                   sizeof(BOOL) * m_streamHdlr.StreamCount());
        }

        res = m_streamHdlr.OnSeek(requestedTime);

        if (HXR_OK == res)
        {
            const COggGroupInfo* pGroupInfo = NULL;
            UINT32 uGroupIndex;
            res = m_fileInfo.GetGroupByTimestamp(requestedTime, uGroupIndex);

            if (HXR_OK == res)
            {
                DPRINTF(D_OGG_FF, 
                        ("COFF::Seek() : GroupIndex %u\n", 
                         uGroupIndex));

                res = m_fileInfo.GetGroupInfo(uGroupIndex, pGroupInfo);
            }

            if (HXR_OK == res)
            {
                res = setupStreamHandlerFromGroupInfo(uGroupIndex);
            }

            if (HXR_OK == res)
            {
                changeState(FindSeekPointOffset);
                m_pCurrentStrategy = &m_seekStrategy;
                res = m_seekStrategy.Init(m_pPageReader,
                                          pGroupInfo,
                                          requestedTime);
            }
        }
    }

    DPRINTF(D_OGG_FF, ("COFF::Seek() : done %08x\n", res));
    return res;
}

STDMETHODIMP COggFileFormat::Close(void)
{
    DPRINTF(D_OGG_FF, ("Entering Close()\n"));

    HX_RELEASE(m_pResponse);

    m_streamInfoStrategy.Close();
    m_findEOFStrategy.Close();
    m_groupEndTimeStrategy.Close();
    m_findGroupEndStrategy.Close();

    if (m_pPageReader)
    {
        m_pPageReader->Close();
        HX_RELEASE(m_pPageReader);
    }

    HX_VECTOR_DELETE(m_pGetPacketPending);

    flushPageCache();

    DPRINTF(D_OGG_FF, ("Exiting Close()\n"));

    return HXR_OK;
}

COggFileFormat::~COggFileFormat(void)
{
    DPRINTF(D_OGG_FF, ("Entering ~COggFileFormat()\n"));
    Close();

    DPRINTF(D_OGG_FF, ("Exiting ~COggFileFormat()\n"));
}

STDMETHODIMP_(UINT32) COggFileFormat::AddRef(void)
{
    return InterlockedIncrement(&m_RefCount);
}

STDMETHODIMP_(UINT32) COggFileFormat::Release(void)
{
    if (InterlockedDecrement(&m_RefCount) > 0)
        return m_RefCount;

    delete this;
    return 0;
}

STDMETHODIMP COggFileFormat::QueryInterface(REFIID interfaceID, 
                                            void **ppInterfaceObj)
{
    if (IsEqualIID(interfaceID, IID_IUnknown)) {
        AddRef();
        *ppInterfaceObj = (IUnknown *)(IHXPlugin *)this;
        return HXR_OK;
    } else if (IsEqualIID(interfaceID, IID_IHXPlugin)) {
        AddRef();
        *ppInterfaceObj = (IHXPlugin *)this;
        return HXR_OK;
    } else if (IsEqualIID(interfaceID, IID_IHXFileFormatObject)) {
        AddRef();
        *ppInterfaceObj = (IHXFileFormatObject *)this;
        return HXR_OK;
    }

    *ppInterfaceObj = NULL;
    return HXR_NOINTERFACE;
}

STDMETHODIMP COggFileFormat::PageReaderInitDone(THIS_ HX_RESULT status)
{
    DPRINTF(D_OGG_FF, ("COFF::PageReaderInitDone(%08x)\n", status));

    HX_RESULT res = HXR_UNEXPECTED;

    if (m_State == InitPending)
    {
        changeState((HXR_OK == status) ? Ready : Start);

        m_pResponse->InitDone(status);
    }

    DPRINTF(D_OGG_FF, ("COFF::PageReaderInitDone() exiting\n"));

    return HXR_OK;
}

STDMETHODIMP COggFileFormat::ReadNextPageDone(THIS_ HX_RESULT status,
                                              ULONG32 ulFileOffset,
                                              UINT32 ulPageSize,
                                              ogg_page* pPage)
{
    DPRINTF(D_OGG_FF, 
            ("COFF::RNPD(%08x, (%u, %08x), %u)\n", 
             status, ulFileOffset, ulFileOffset, ulPageSize));
    
    HX_RESULT res = HXR_OK;

    if (m_pCurrentStrategy)
    {        
        res = m_pCurrentStrategy->ReadNextPageDone(status, 
                                                   ulFileOffset,
                                                   ulPageSize, 
                                                   pPage);

        DPRINTF(D_OGG_FF, ("COFF::RNPD() : res %08x\n", res));
        
        if ((HXR_OK == res) &&
            ((GetFirstStreamInfo == m_State) ||
             (GetNextLiveStreamInfo == m_State)))
        {
            res = cachePage(pPage);
            m_ulLastCachedPageOffset = ulFileOffset;
        }

        if ((HXR_OK == res) && m_pCurrentStrategy->Done())
        {
            switch(m_State) {
            case GetFirstStreamInfo:
                res = handleGetFirstStreamInfoDone();
                break;
            case FindLastPage:
                res = handleFindLastPageDone();
                break;
            case FindGroupEndPage:
                res = handleFindGroupEndPageDone();
                break;
            case FindGroupEndTime:
                res = handleFindGroupEndTimeDone();
                break;
            case FindLastGroupEndTime:
                res = handleFindLastGroupEndTimeDone();
                break;
            case GetNextStreamInfo:
                res = handleGetNextStreamInfoDone();
                break;
            case GetNextLiveStreamInfo:
                res = handleGetNextLiveStreamInfoDone();
                break;
            case FindSeekPointOffset:
                res = handleFindSeekPointOffsetDone();
                break;
            case CollectStreamHeaders:
                res = handleCollectHeadersDone();
                break;
            default:
                HX_ASSERT(!"Unexpected condition");
                res = HXR_UNEXPECTED;
                break;
            }
        }
    }
    else if (SeekBackToLastCachedPage == m_State)
    {
        res = handleSeekBackToLastCachedPage();
    }
    else if (GetPacketReadPending == m_State)
    {
        res = handlePacketReadPending(status, ulFileOffset,
                                      ulPageSize, pPage);
    }

    DPRINTF(D_OGG_FF, ("COFF::RNPD() : exiting res %08x\n", res));

    if (HXR_OK != res)
    {
        switch(m_State) {
        case GetFirstStreamInfo:
        case FindLastPage:
        case FindGroupEndPage:
        case FindGroupEndTime:
        case FindLastGroupEndTime:
        case GetNextStreamInfo:
        case SeekBackToLastCachedPage:
            m_pResponse->FileHeaderReady(res, 0);
            break;
        case FindSeekPointOffset:
        case CollectStreamHeaders:
            m_pResponse->SeekDone(res);
            break;
        case GetPacketReadPending:
        case GetNextLiveStreamInfo:
        {
            for (UINT32 i = 0; i < m_streamHdlr.StreamCount(); i++)
            {
                if (m_pGetPacketPending[i])
                {
                    m_pResponse->PacketReady(res, 0);
                }
            }
            changeState(Error);
        }break;
        default:
            HX_ASSERT(!"Unhandled Error");
            res = HXR_UNEXPECTED;
            break;
        }
    }

    return HXR_OK;
}

#ifdef _DEBUG
static const char* z_pStateNames[] = {
    "Start",
    "InitPending",
    "Ready",
    "GetFirstStreamInfo",
    "FindLastPage",
    "FindLastGroupEndTime",
    "FindGroupEndPage",
    "FindGroupEndTime",
    "GetNextStreamInfo",
    "SeekBackToLastCachedPage",
    "GetPacketReadPending",
    "GetNextLiveStreamInfo",
    "FindSeekPointOffset",
    "CollectStreamHeaders",
    "Error"
};
#endif /* _DEBUG */

void COggFileFormat::changeState(PluginState newState)
{
    DPRINTF(D_OGG_FF, ("COFF::changeState() : %s -> %s\n", 
                       z_pStateNames[m_State], 
                       z_pStateNames[newState]));
    m_State = newState;
}

HX_RESULT 
COggFileFormat::handleGetFirstStreamInfoDone()
{
    HX_RESULT res = HXR_UNEXPECTED;

    UINT16 uStreamCount = m_streamInfoStrategy.StreamCount();
    ULONG32 ulStartOffset;
    UINT32 uStartSize;

    if ((HXR_OK == m_streamInfoStrategy.StartPageOffset(ulStartOffset)) &&
        (HXR_OK == m_streamInfoStrategy.StartPageSize(uStartSize)))
    {

        DPRINTF(D_OGG_FF, ("COFF::hGFSID : StreamCount %u\n",
                           uStreamCount));
        DPRINTF(D_OGG_FF, ("COFF::hGFSID : StartPageOffset %u\n",
                           ulStartOffset));
        DPRINTF(D_OGG_FF, ("COFF::hGFSID : StartPageSize %u\n",
                           uStartSize));
    }
        
    for (UINT16 i = 0; i < uStreamCount;i++)
    {
        int serialNum;

        DPRINTF(D_OGG_FF, ("COFF::hGFSID : Stream %u\n",i));

        if (HXR_OK == m_streamInfoStrategy.GetStreamSerialNum(i, serialNum))
        {
            DPRINTF(D_OGG_FF, ("COFF::hGFSID :  Serial    %08x\n",
                               serialNum));

            const COggCodecInfo* pInfo = NULL;
            if (HXR_OK == m_streamInfoStrategy.GetCodecInfo(serialNum,
                                                            pInfo))
            {
                DPRINTF(D_OGG_FF, ("COFF::hGFSID :  CodecType %u\n",
                                   pInfo->Type()));
                DPRINTF(D_OGG_FF, ("COFF::hGFSID :  Dropped   %I64d\n",
                                   pInfo->GranulePosDropped()));
            }
        }
    }

    m_bIsLive = !m_pPageReader->IsSeekable();

    if (m_bIsLive)
    {
        // Live case

        UINT16 uNumAudioStreams = 0;
        UINT16 uNumVideoStreams = 0;
        
        res = getStreamTypeCountFromStreamInfo(uNumAudioStreams,
                                               uNumVideoStreams);
    
        if (HXR_OK == res)
        {
            res = m_streamHdlr.SetStreamTypeCounts(uNumAudioStreams,
                                                   uNumVideoStreams);
        }
        
        if (HXR_OK == res)
        {
            res = setupStreamHandlerFromStreamInfo();
        }
        
        if (HXR_OK == res)
        {
            res = sendCachedPagesToStreamHandler();
        }
        
        if (HXR_OK == res)
        {
            res = createAndSendFileHeader();
        }

        m_pCurrentStrategy = NULL;
    }
    else
    {
        changeState(FindLastPage);
        m_pCurrentStrategy = &m_findEOFStrategy;
        res = m_findEOFStrategy.Init(m_pPageReader);
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleFindLastPageDone()
{
    HX_RESULT res = HXR_UNEXPECTED;

    ULONG32 ulStartOffset;
    ULONG32 ulLastPageOffset;
    UINT32 uLastPageSize;
    int serialNum ;

    if ((HXR_OK == m_streamInfoStrategy.StartPageOffset(ulStartOffset)) &&
        (HXR_OK == m_findEOFStrategy.LastPageSerialNum(serialNum)) &&
        (HXR_OK == m_findEOFStrategy.LastPageOffset(ulLastPageOffset)) &&
        (HXR_OK == m_findEOFStrategy.LastPageSize(uLastPageSize)))
    {
        if (m_streamInfoStrategy.HaveSerialNum(serialNum))
        {
            // Single group case
            DPRINTF(D_OGG_FF, ("COFF::hFLPD : single group\n"));

            const COggCodecInfo* pInfo = NULL;
            res = m_streamInfoStrategy.GetCodecInfo(serialNum, pInfo);

            if (HXR_OK == res)
            {
                changeState(FindLastGroupEndTime);
                m_pCurrentStrategy = &m_groupEndTimeStrategy;
                res = m_groupEndTimeStrategy.Init(m_pPageReader,
                                                  ulStartOffset,
                                                  ulLastPageOffset,
                                                  uLastPageSize,
                                                  serialNum,
                                                  pInfo);
            }
        }
        else
        {
            // Chained case
            DPRINTF(D_OGG_FF, ("COFF::hFLPD : chained\n"));

            changeState(FindGroupEndPage);
            m_pCurrentStrategy = &m_findGroupEndStrategy;
            res = m_findGroupEndStrategy.Init(m_pPageReader,
                                              &m_streamInfoStrategy,
                                              ulLastPageOffset);
                                              
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleFindGroupEndPageDone()
{
    HX_RESULT res = HXR_UNEXPECTED;

    ULONG32 ulStartOffset;
    ULONG32 ulLastPageOffset;
    UINT32 uLastPageSize;
    int serialNum ;

    if ((HXR_OK == m_streamInfoStrategy.StartPageOffset(ulStartOffset)) &&
        (HXR_OK == m_findGroupEndStrategy.LastPageSerialNum(serialNum)) &&
        (HXR_OK == m_findGroupEndStrategy.LastPageOffset(ulLastPageOffset)) &&
        (HXR_OK == m_findGroupEndStrategy.LastPageSize(uLastPageSize)))
    {
        const COggCodecInfo* pInfo = NULL;
        res = m_streamInfoStrategy.GetCodecInfo(serialNum, pInfo);
        
        if (HXR_OK == res)
        {
            changeState(FindGroupEndTime);
            m_pCurrentStrategy = &m_groupEndTimeStrategy;
            res = m_groupEndTimeStrategy.Init(m_pPageReader,
                                              ulStartOffset,
                                              ulLastPageOffset,
                                              uLastPageSize,
                                              serialNum,
                                              pInfo);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleFindGroupEndTimeDone()
{
    HX_RESULT res = HXR_UNEXPECTED;

    ULONG32 ulLastPageOffset;
    UINT32 uLastPageSize;

    if ((HXR_OK == m_findGroupEndStrategy.LastPageOffset(ulLastPageOffset))&&
        (HXR_OK == m_findGroupEndStrategy.LastPageSize(uLastPageSize)))
    {
        res = addGroupToFileInfo(ulLastPageOffset);

        if (HXR_OK == res)
        {
            changeState(GetNextStreamInfo);
            m_pCurrentStrategy = &m_streamInfoStrategy;
            res = m_streamInfoStrategy.Init(m_pPageReader);
            
            if (HXR_OK == res)
            {
                res = m_pPageReader->Seek(ulLastPageOffset + uLastPageSize);
            }
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleFindLastGroupEndTimeDone()
{
    HX_RESULT res = HXR_UNEXPECTED;

    ULONG32 ulLastPageOffset;
    if (HXR_OK == m_findEOFStrategy.LastPageOffset(ulLastPageOffset))
    {
        res = addGroupToFileInfo(ulLastPageOffset);

        
        if (HXR_OK == res)
        {
            changeState(SeekBackToLastCachedPage);
            m_pCurrentStrategy = NULL;
            res = m_pPageReader->Seek(m_ulLastCachedPageOffset);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleGetNextStreamInfoDone()
{
HX_RESULT res = HXR_UNEXPECTED;

    UINT16 uStreamCount = m_streamInfoStrategy.StreamCount();
    ULONG32 ulStartOffset;
    UINT32 uStartSize;

    if ((HXR_OK == m_streamInfoStrategy.StartPageOffset(ulStartOffset)) &&
        (HXR_OK == m_streamInfoStrategy.StartPageSize(uStartSize)))
    {

        DPRINTF(D_OGG_FF, ("COFF::hGNSID : StreamCount %u\n",
                           uStreamCount));
        DPRINTF(D_OGG_FF, ("COFF::hGNSID : StartPageOffset %u\n",
                           ulStartOffset));
        DPRINTF(D_OGG_FF, ("COFF::hGNSID : StartPageSize %u\n",
                           uStartSize));
    }
        
    for (UINT16 i = 0; i < uStreamCount;i++)
    {
        int serialNum;

        DPRINTF(D_OGG_FF, ("COFF::hGNSID : Stream %u\n",i));

        if (HXR_OK == m_streamInfoStrategy.GetStreamSerialNum(i, serialNum))
        {
            DPRINTF(D_OGG_FF, ("COFF::hGNSID :  Serial    %08x\n",
                               serialNum));

            const COggCodecInfo* pInfo = NULL;
            if (HXR_OK == m_streamInfoStrategy.GetCodecInfo(serialNum,
                                                            pInfo))
            {
                DPRINTF(D_OGG_FF, ("COFF::hGNSID :  CodecType %u\n",
                                   pInfo->Type()));
                DPRINTF(D_OGG_FF, ("COFF::hGNSID :  Dropped   %I64d\n",
                                   pInfo->GranulePosDropped()));
            }
        }
    }

    ULONG32 ulLastPageOffset;
    UINT32 uLastPageSize;
    int lastSerialNum ;

    if ((HXR_OK == m_findEOFStrategy.LastPageSerialNum(lastSerialNum)) &&
        (HXR_OK == m_findEOFStrategy.LastPageOffset(ulLastPageOffset)) &&
        (HXR_OK == m_findEOFStrategy.LastPageSize(uLastPageSize)))
    {
        if (m_streamInfoStrategy.HaveSerialNum(lastSerialNum))
        {
            // last group in the chain
            DPRINTF(D_OGG_FF, ("COFF::hGNSID : last group\n"));

            const COggCodecInfo* pInfo = NULL;
            res = m_streamInfoStrategy.GetCodecInfo(lastSerialNum, pInfo);

            if (HXR_OK == res)
            {
                changeState(FindLastGroupEndTime);
                m_pCurrentStrategy = &m_groupEndTimeStrategy;
                res = m_groupEndTimeStrategy.Init(m_pPageReader,
                                                  ulStartOffset,
                                                  ulLastPageOffset,
                                                  uLastPageSize,
                                                  lastSerialNum,
                                                  pInfo);
            }
        }
        else
        {
            // There are more groups in the chain
            DPRINTF(D_OGG_FF, ("COFF::hGNSID : not the last group\n"));

            changeState(FindGroupEndPage);
            m_pCurrentStrategy = &m_findGroupEndStrategy;

            res = m_findGroupEndStrategy.Init(m_pPageReader,
                                              &m_streamInfoStrategy,
                                              ulLastPageOffset);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleGetNextLiveStreamInfoDone()
{
    HX_RESULT res = setupStreamHandlerFromStreamInfo();

    if (HXR_OK == res)
    {
        res = sendCachedPagesToStreamHandler();
    }

    m_pCurrentStrategy = NULL;

    if (HXR_OK == res)
    {
        res = dispatchPendingRequests();
        
        if (HXR_NO_DATA == res)
        {
            // We need more data
            res = m_pPageReader->ReadNextPage();
        }
        else if (HXR_OK == res)
        {
            changeState(Ready);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleFindSeekPointOffsetDone()
{
    ULONG32 ulSeekPointOffset;
    ULONG32 ulStartPageOffset;
    const COggGroupInfo* pGroupInfo = NULL;

    HX_RESULT res = m_seekStrategy.GetSeekPointOffset(ulSeekPointOffset);

    if (HXR_OK == res)
    {
        res = m_seekStrategy.GetGroupInfo(pGroupInfo);
    }
    
    if (HXR_OK == res)
    {
        res = pGroupInfo->GetStartPageOffset(ulStartPageOffset);
    }

    if (HXR_OK == res)
    {
        if (ulStartPageOffset == ulSeekPointOffset)
        {
            // The seek point is at the start page so
            // we don't need to worry about getting the
            // headers
            m_pCurrentStrategy = NULL;
            changeState(GetPacketReadPending);
            res = m_pPageReader->Seek(ulSeekPointOffset);

            if (HXR_OK == res)
            {
                res = m_pResponse->SeekDone(HXR_OK);
            }
        }
        else
        {
            m_pCurrentStrategy = &m_collectHeaderStrategy;
            changeState(CollectStreamHeaders);
            res = m_collectHeaderStrategy.Init(m_pPageReader,
                                               pGroupInfo);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleCollectHeadersDone()
{
    ULONG32 ulSeekPointOffset;

    HX_RESULT res = HXR_OK;

    while(HXR_OK == res)
    {
        ogg_page* pPage = NULL;

        res = m_collectHeaderStrategy.GetNextPage(pPage);

        if (HXR_OK == res)
        {
            COggStream* pStream = 
                m_streamHdlr.GetStream(ogg_page_serialno(pPage));

            if (pStream)
            {
                res = pStream->OnPage(pPage);
            }
            else
            {
                res = HXR_UNEXPECTED;
            }

            OggUtil::DestroyPage(pPage);
        }
    }

    if (HXR_NO_DATA == res)
    {
        // This is the expected case
        res = HXR_OK;
    }

    if (HXR_OK == res)
    {
        res = m_seekStrategy.GetSeekPointOffset(ulSeekPointOffset);
    }

    if (HXR_OK == res)
    {
        m_pCurrentStrategy = NULL;
        changeState(GetPacketReadPending);
        res = m_pPageReader->Seek(ulSeekPointOffset);
        
        if (HXR_OK == res)
        {
            res = m_pResponse->SeekDone(HXR_OK);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleSeekBackToLastCachedPage()
{
    UINT16 uNumAudioStreams = 0;
    UINT16 uNumVideoStreams = 0;

    HX_RESULT res = getStreamTypeCountFromFileInfo(uNumAudioStreams,
                                                   uNumVideoStreams);
    
    if (HXR_OK == res)
    {
        res = m_streamHdlr.SetStreamTypeCounts(uNumAudioStreams,
                                               uNumVideoStreams);
    }

    if (HXR_OK == res)
    {
        res = setupStreamHandlerFromGroupInfo(0);
    }

    if (HXR_OK == res)
    {
        res = sendCachedPagesToStreamHandler();
    }
    
    if (HXR_OK == res)
    {
        res = createAndSendFileHeader();
    }

    return res;
}

HX_RESULT 
COggFileFormat::handlePacketReadPending(HX_RESULT status,
                                        ULONG32 ulFileOffset,
                                        UINT32 uPageSize,
                                        ogg_page* pPage)
{
    HX_RESULT res = HXR_UNEXPECTED;

    if (HXR_OK == status)
    {
        BOOL bChangedState = FALSE;
        COggStream* pStream = m_streamHdlr.GetStream(ogg_page_serialno(pPage));

        if (pStream)
        {
            res = pStream->OnPage(pPage);
        }
        else if (m_bIsLive)
        {
            // The page is not for the current group and we are dealing with
            // a live stream
            COggTimestamp timestamp;
            res = m_streamHdlr.GetStartTimestamp(m_liveNextGroupStartTime);
            
            if (HXR_OK == res)
            {
                res = m_streamHdlr.OnEndOfGroup();
            }

            if (HXR_OK == res)
            {
                changeState(GetNextLiveStreamInfo);
                m_pCurrentStrategy = &m_streamInfoStrategy;

                res = m_streamInfoStrategy.Init(m_pPageReader);
        
                if (HXR_OK == res)
                {
                    res = m_pCurrentStrategy->ReadNextPageDone(status, 
                                                               ulFileOffset,
                                                               uPageSize, 
                                                               pPage);
                }
                
                if (HXR_OK == res)
                {
                    bChangedState = TRUE;
                    res = cachePage(pPage);
                }
                
            }
        }
        else
        {
            // The page is not for the current group and we are not dealing 
            // with a live stream

            // check to see if this page is part of the next group in
            // the file
            res = handleFileGroupChange(pPage);
        }

        if ((HXR_OK == res) && !bChangedState)
        {
            res = dispatchPendingRequests();

            if (HXR_NO_DATA == res)
            {
                // We need more data
                res = m_pPageReader->ReadNextPage();
            }
            else if ((HXR_OK == res) &&
                     (GetNextLiveStreamInfo != m_State))
            {
                changeState(Ready);
            }
        }
    }
    else if (HXR_AT_END == status)
    {
        res = m_streamHdlr.OnEndOfFile();
    }

    return res;
}

HX_RESULT 
COggFileFormat::addGroupToFileInfo(ULONG32 ulLastPageOffset)
{
    HX_RESULT res = HXR_UNEXPECTED;
    ULONG32 ulStartOffset;
    UINT32 uStartSize;
    COggTimestamp duration;
    UINT16 uStreamCount = m_streamInfoStrategy.StreamCount();

    if ((HXR_OK == m_streamInfoStrategy.StartPageOffset(ulStartOffset)) &&
        (HXR_OK == m_streamInfoStrategy.StartPageSize(uStartSize)) && 
        (HXR_OK == m_groupEndTimeStrategy.GetDuration(duration)))
    {
        COggGroupInfo* pGroupInfo = new COggGroupInfo(ulStartOffset, 
                                                      uStartSize,
                                                      ulLastPageOffset,
                                                      duration);

        if (pGroupInfo)
        {
            res = HXR_OK;

            // Add all the codec info objects to the group info
            for (UINT16 i = 0; (HXR_OK == res) && (i < uStreamCount); i++)
            {
                int serialNum;
                const COggCodecInfo* pInfo = NULL;

                res = m_streamInfoStrategy.GetStreamSerialNum(i, serialNum);

                if (HXR_OK == res)
                {
                    res = m_streamInfoStrategy.GetCodecInfo(serialNum, pInfo);
                }

                if (HXR_OK == res)
                {
                    res = pGroupInfo->AddCodecInfo(serialNum, pInfo);
                }
            }

            if (HXR_OK == res)
            {
                res = m_fileInfo.AddGroupInfo(pGroupInfo);

                if (HXR_OK != res)
                {
                    delete pGroupInfo;
                }
            }
        }
        else
        {
            res = HXR_OUTOFMEMORY;
        }
    }

    DPRINTF(D_OGG_FF, ("COFF::aGTFI : done %08x\n", res));

    return res;
}

HX_RESULT 
COggFileFormat::getStreamTypeCountFromFileInfo(UINT16& uNumAudioStreams,
                                               UINT16& uNumVideoStreams) const
{
    HX_RESULT res = HXR_OK;

    uNumAudioStreams = 0;
    uNumVideoStreams = 0;

    // Compute stream type counts
    for (UINT32 i = 0; ((HXR_OK == res) && 
                        (i < m_fileInfo.GroupCount())); i++)
    {
        const COggGroupInfo* pGroupInfo = NULL;
        
        res = m_fileInfo.GetGroupInfo(i, pGroupInfo);
        
        if (HXR_OK == res)
        {
            UINT32 uGroupAudioCount = 0;
            UINT32 uGroupVideoCount = 0;

            for (UINT16 j = 0; ((HXR_OK == res) &&
                                (j < pGroupInfo->StreamCount())); j++)
            {
                int serialNum;
                res = pGroupInfo->GetStreamSerialNum(j, serialNum);

                if (HXR_OK == res)
                {
                    const COggCodecInfo* pCodecInfo = NULL;

                    res = pGroupInfo->GetCodecInfo(serialNum, pCodecInfo);

                    if (HXR_OK == res)
                    {
                        if ((pCodecInfo->Type() == ctVorbis) ||
                            (pCodecInfo->Type() == ctSpeex))
                        {
                            uGroupAudioCount++;
                        }
                        else if (pCodecInfo->Type() == ctTheora)
                        {
                            uGroupVideoCount++;
                        }
                    }
                }
            }

            if (HXR_OK == res)
            {
                DPRINTF(D_OGG_FF, ("COFF::gSTFFI : group %u audio %u video %u\n", 
                                   i, uGroupAudioCount, uGroupVideoCount));
                
                if (uGroupAudioCount > uNumAudioStreams)
                {
                    uNumAudioStreams = uGroupAudioCount;
                }
                
                if (uGroupVideoCount > uNumVideoStreams)
                {
                    uNumVideoStreams = uGroupVideoCount;
                }
            }
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::getStreamTypeCountFromStreamInfo(UINT16& uNumAudioStreams,
                                                UINT16& uNumVideoStreams) const
{
    HX_RESULT res = HXR_OK;

    uNumAudioStreams = 0;
    uNumVideoStreams = 0;

    for (UINT16 i = 0; ((HXR_OK == res) &&
                        (i < m_streamInfoStrategy.StreamCount())); i++)
    {
        int serialNum;
        res = m_streamInfoStrategy.GetStreamSerialNum(i, serialNum);

        if (HXR_OK == res)
        {
            const COggCodecInfo* pCodecInfo = NULL;
            
            res = m_streamInfoStrategy.GetCodecInfo(serialNum, pCodecInfo);
            
            if (HXR_OK == res)
            {
                if ((pCodecInfo->Type() == ctVorbis) ||
                    (pCodecInfo->Type() == ctSpeex))
                {
                    uNumAudioStreams++;
                }
                else if (pCodecInfo->Type() == ctTheora)
                {
                    uNumVideoStreams++;
                }
            }
        }
    }

    return res;
}


HX_RESULT 
COggFileFormat::setupStreamHandlerFromGroupInfo(UINT32 uGroupIndex)
{
    const COggGroupInfo* pGroupInfo = NULL;
    
    HX_RESULT res = m_fileInfo.GetGroupInfo(uGroupIndex, pGroupInfo);

    if (HXR_OK == res)
    {
        for (UINT32 i = 0; ((HXR_OK == res) &&
                            (i < pGroupInfo->StreamCount())); i++)
        {
            int serialNum;
            const COggCodecInfo* pCodecInfo = NULL;
            
            res = pGroupInfo->GetStreamSerialNum(i, serialNum);
            
            if (HXR_OK == res)
            {
                res = pGroupInfo->GetCodecInfo(serialNum, pCodecInfo);
            }
            
            if (HXR_OK == res)
            {
                res = m_streamHdlr.AddCodecInfo(serialNum, pCodecInfo);
            }
        }

        if (HXR_OK == res)
        {
            m_uCurrentGroup = uGroupIndex;
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::setupStreamHandlerFromStreamInfo()
{
    HX_RESULT res = HXR_OK;

    for (UINT32 i = 0; ((HXR_OK == res) &&
                        (i < m_streamInfoStrategy.StreamCount())); i++)
    {
        int serialNum;
        const COggCodecInfo* pCodecInfo = NULL;
        
        res = m_streamInfoStrategy.GetStreamSerialNum(i, serialNum);
            
        if (HXR_OK == res)
        {
            res = m_streamInfoStrategy.GetCodecInfo(serialNum, pCodecInfo);
        }
            
        if (HXR_OK == res)
        {
            COggCodecInfo* pCodecInfo2 = pCodecInfo->Clone();

            if (pCodecInfo2)
            {
                res = pCodecInfo2->SetStartTimestamp(m_liveNextGroupStartTime);
                
                if (HXR_OK == res)
                {
                    res = m_streamHdlr.AddCodecInfo(serialNum, pCodecInfo2);
                }
            }

            HX_DELETE(pCodecInfo2);
        }
    }

    return res;
}

HX_RESULT 
COggFileFormat::handleFileGroupChange(ogg_page* pPage)
{
    HX_RESULT res = HXR_OK;

    UINT32 uNextGroup = m_uCurrentGroup + 1;
    if (uNextGroup < m_fileInfo.GroupCount())
    {
        // The current group was not the last group.

        const COggGroupInfo* pGroupInfo = NULL;
        
        // Get the group info for the next group
        res = m_fileInfo.GetGroupInfo(uNextGroup, pGroupInfo);
        
        if (HXR_OK == res)
        {
            const COggCodecInfo* pCodecInfo = NULL;
            
            // See if there is any codec info for the serial number on
            // this page.
            res = pGroupInfo->GetCodecInfo(ogg_page_serialno(pPage),
                                           pCodecInfo);
            
            if (HXR_OK == res)
            {
                // This page is part of the next group. 
                
                // Tell the stream handler that the current
                // group has ended.
                res = m_streamHdlr.OnEndOfGroup();
            }
            
            if (HXR_OK == res)
            {
                // setup the stream handler with the info for 
                // the next group
                res = setupStreamHandlerFromGroupInfo(uNextGroup);
            }
            
            if (HXR_OK == res)
            {
                // Send this page to the appropriate stream
                
                COggStream* pStream = 
                    m_streamHdlr.GetStream(ogg_page_serialno(pPage));
                
                if (pStream)
                {
                    res = pStream->OnPage(pPage);
                }
                else
                {
                    res = HXR_UNEXPECTED;
                }
            }
        }
    }
    else
    {
        // The current group is the last group in the file
        HX_ASSERT(FALSE);
    }

    return res;
}

HX_RESULT 
COggFileFormat::createAndSendFileHeader()
{
    
    HX_RESULT res = HXR_OK;

    HX_VECTOR_DELETE(m_pGetPacketPending);
    m_pGetPacketPending = new BOOL[m_streamHdlr.StreamCount()];
    
    if (m_pGetPacketPending)
    {
        memset(m_pGetPacketPending, 0, 
               sizeof(BOOL) * m_streamHdlr.StreamCount());
    }
    else
    {
        res = HXR_OUTOFMEMORY;
    }

    if (HXR_OK == res)
    {
        IHXValues* pFileHdr = NULL;

        res = m_streamHdlr.CreateFileHeader(pFileHdr);

        if (HXR_OK == res)
        {
            if (m_bIsLive)
            {
                pFileHdr->SetPropertyULONG32("LiveStream", 1);
            }

            changeState(Ready);

            m_pResponse->FileHeaderReady(HX_STATUS_OK, pFileHdr);
        }

        HX_RELEASE(pFileHdr);
    }

    return res;
}

HX_RESULT 
COggFileFormat::dispatchPendingRequests()
{
    HX_RESULT res = HXR_OK;

    DPRINTF(D_OGG_FF, 
            ("COFF::dPR()\n"));

    if (m_pResponse && m_pGetPacketPending)
    {
        if (!m_bInDispatchPendingRequest)
        {
            BOOL bDone = FALSE;

            m_bInDispatchPendingRequest = TRUE;

            while (!bDone)
            {
                UINT16 uNextStreamID;

                // Assume this will be the last loop
                bDone = TRUE;

                res = m_streamHdlr.NextPacketStreamID(uNextStreamID);

                if ((HXR_OK == res) && m_pGetPacketPending[uNextStreamID])
                {
                    // We got the streamID for the next packet
                    // and we have a GetPacket() request pending.

                    IHXPacket* pPkt = NULL;
                    
                    // Get the next packet
                    res = m_streamHdlr.GetNextPacket(pPkt);
                    
                    if (HXR_OK == res)
                    {
                        m_pGetPacketPending[uNextStreamID] = FALSE;
                        
                        DPRINTF(D_OGG_FF, 
                                ("COFF::dPR() : PacketReady(%u, %u)\n",
                                 pPkt->GetStreamNumber(),
                                 pPkt->GetTime()));
                        m_pResponse->PacketReady(HXR_OK, pPkt);
                        HX_RELEASE(pPkt);
                        
                        // We dispatched a packet so
                        // we should loop again
                        bDone = FALSE;
                    }
                }
            }
        
            if (HXR_STREAM_DONE == res)
            {
                UINT32 uStreamCount = m_streamHdlr.StreamCount();

                // All the streams are done.
                // Handle any pending GetPacket() requests
                for (UINT32 i = 0; i < uStreamCount; i++)
                {
                    if (m_pGetPacketPending[i])
                    {
                        m_pGetPacketPending[i] = FALSE;

                        DPRINTF(D_OGG_FF, 
                                ("COFF::dPR() : StreamDone(%u)\n",
                                 i));
                        m_pResponse->StreamDone(i);
                    }
                }
                
                res = HXR_OK;
            }

            m_bInDispatchPendingRequest = FALSE;
        }
        else
        {
            // We are already in a dispatchPendingRequests() call
            // so we don't want to do anything here
            DPRINTF(D_OGG_FF, 
                    ("COFF::dPR() : deferred\n", res));
        }
    }
    else
    {
        res = HXR_FAILED;
    }

    DPRINTF(D_OGG_FF, 
            ("COFF::dPR() : res %08x\n", res));
    return res;
}

HX_RESULT 
COggFileFormat::cachePage(ogg_page* pPage)
{
    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (pPage)
    {
        ogg_page* pNewPage = OggUtil::CopyPage(pPage);
        
        if (pNewPage && m_pageCache.AddTail(pNewPage))
        {
            res = HXR_OK;
        }
        else
        {
            OggUtil::DestroyPage(pNewPage);

            res = HXR_OUTOFMEMORY;
        }
    }

    return res;
}

void COggFileFormat::flushPageCache()
{
    while(!m_pageCache.IsEmpty())
    {
        ogg_page* pPage = (ogg_page*)m_pageCache.RemoveHead();

        OggUtil::DestroyPage(pPage);
    }
}

HX_RESULT 
COggFileFormat::sendCachedPagesToStreamHandler()
{
    HX_RESULT res = HXR_OK;

    while(!m_pageCache.IsEmpty() && (HXR_OK == res))
    {
        ogg_page* pPage = (ogg_page*)m_pageCache.RemoveHead();

        if (pPage)
        {
            int serialNum = ogg_page_serialno(pPage);
            COggStream* pStream = m_streamHdlr.GetStream(serialNum);

            if (pStream)
            {
                res = pStream->OnPage(pPage);
            }
            else
            {
                res = HXR_UNEXPECTED;
            }

            OggUtil::DestroyPage(pPage);
        }
        else
        {
            res = HXR_UNEXPECTED;
        }
    }

    return res;
}
