/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: ogg_payload.cpp,v 1.2.4.2 2005/01/21 19:30:13 nhart 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 ***** */

#include "ogg_payload.h"
#include "codec_info.h"

#include "debug.h"

#define D_OGG_PAYLOAD 0 //0x40000

COggPayload::COggPayload() :
    m_pCCF(NULL),
    m_pCodecInfo(NULL),
    m_bEndOfFile(FALSE),
    m_bSeenDataPacket(FALSE)
{
#ifdef _DEBUG
    debug_level() |= D_OGG_PAYLOAD;
#endif // _DEBUG

    ogg_stream_init(&m_os, 0);
}

COggPayload::~COggPayload()
{
    HX_RELEASE(m_pCCF);
    HX_DELETE(m_pCodecInfo);

    flushPktList();
    ogg_stream_clear(&m_os);
}

HX_RESULT 
COggPayload::Init(UINT16 uStreamNum, IUnknown* pContext)
{
    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (pContext)
    {
        m_uStreamNum = uStreamNum;

        res = pContext->QueryInterface(IID_IHXCommonClassFactory, 
                                       (void**)&m_pCCF);
    }

    return res;
}

HX_RESULT 
COggPayload::SetCodecInfo(int serialNum, 
                          const COggCodecInfo* pCodecInfo)
{
    DPRINTF(D_OGG_PAYLOAD, 
                    ("COggPayload::SCI : %u\n",
                     m_uStreamNum));

    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (pCodecInfo)
    {
        HX_ASSERT(m_pCodecInfo == NULL);
        
        m_pCodecInfo = pCodecInfo->Clone();
        if (m_pCodecInfo)
        {
            ogg_stream_clear(&m_os);
            ogg_stream_init(&m_os, serialNum);
            res = HXR_OK;
        }
        else
        {
            res = HXR_OUTOFMEMORY;
        }
    }

    return res;
}

HX_RESULT 
COggPayload::OnPacket(ogg_packet* pPkt)
{
    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (pPkt)
    {
        if (!m_bSeenDataPacket &&
            !m_pCodecInfo->IsHeader(pPkt))
        {
            // This is the first data packet after the
            // headers. We should flush the streams so the
            // headers are in their own page
            ogg_page og;
            int flushErr = ogg_stream_flush(&m_os, &og);
            m_bSeenDataPacket = TRUE;

            if (flushErr > 0)
            {
                res = packetizePage(&og); 
            }
        }

        int piErr = ogg_stream_packetin(&m_os, pPkt);
        
        if (piErr == 0)
        {
            BOOL bDone = FALSE;
            ogg_page og;
            
            res = HXR_OK;
            while(!bDone && (HXR_OK == res))
            {
                int poErr = ogg_stream_pageout(&m_os, &og);

                if (poErr > 0)
                {
                    res = packetizePage(&og); 
                }
                else if (poErr == 0)
                {
                    bDone = TRUE;
                }
                else
                {
                    res = HXR_UNEXPECTED;
                }
            }
        }
        else
        {
            res = HXR_UNEXPECTED;
        }
    }

    return res;
}

HX_RESULT 
COggPayload::OnEndOfGroup()
{
    DPRINTF(D_OGG_PAYLOAD, 
                    ("COggPayload::OEOG : %u\n",
                     m_uStreamNum));

    HX_RESULT res = packetizePendingData();

    if (HXR_OK == res)
    {
        HX_DELETE(m_pCodecInfo);
        m_bSeenDataPacket = FALSE;
    }

    return res;
}

HX_RESULT 
COggPayload::OnEndOfFile()
{
    DPRINTF(D_OGG_PAYLOAD, 
                    ("COggPayload::OEOF : %u\n",
                     m_uStreamNum));

    HX_RESULT res = packetizePendingData();

    m_bEndOfFile = TRUE;

    return res;
}

HX_RESULT 
COggPayload::OnSeek(UINT32 uRequestedTime)
{
    DPRINTF(D_OGG_PAYLOAD, 
                    ("COggPayload::OS : %u\n",
                     uRequestedTime));

    ogg_stream_reset(&m_os);
    flushPktList();
    HX_DELETE(m_pCodecInfo);

    m_bEndOfFile = FALSE;
    m_bSeenDataPacket = FALSE;

    return HXR_OK;
}

HX_RESULT 
COggPayload::SetStartTimestamp(const COggTimestamp& timestamp)
{
    COggTimestamp tmp = timestamp;
    tmp.SetSampleRate(1000);
    DPRINTF(D_OGG_PAYLOAD, 
            ("COggPayload::SST : %u\n",
             tmp.Samples()));
    m_startTS = timestamp;
    return HXR_OK;
}

HX_RESULT 
COggPayload::NextPacketTimestamp(REF(UINT32) uTimestamp) const
{
    HX_RESULT res = (m_bEndOfFile) ? HXR_STREAM_DONE : HXR_NO_DATA;

    if (!m_pktList.IsEmpty())
    {
        IHXPacket* pPkt = (IHXPacket*)m_pktList.GetHead();

        uTimestamp = pPkt->GetTime();
        res = HXR_OK;
    }

    return res;
}

HX_RESULT 
COggPayload::GetNextPacket(REF(IHXPacket*) pPkt)
{
    HX_RESULT res = (m_bEndOfFile) ? HXR_STREAM_DONE : HXR_NO_DATA;

    if (!m_pktList.IsEmpty())
    {
        pPkt = (IHXPacket*)m_pktList.RemoveHead();
        res = HXR_OK;
    }

    return res;
}

void COggPayload::flushPktList()
{
    while(!m_pktList.IsEmpty())
    {
        IHXPacket* pPkt = (IHXPacket*)m_pktList.RemoveHead();

        HX_RELEASE(pPkt);
    }
}

HX_RESULT 
COggPayload::packetizePendingData()
{
    HX_RESULT res = HXR_OK;

    BOOL bDone = FALSE;

    while(!bDone && (HXR_OK == res))
    {
        ogg_page og;
        int flushErr = ogg_stream_flush(&m_os, &og);

        if (flushErr > 0)
        {
            res = packetizePage(&og);
        }
        else if (flushErr == 0)
        {
            bDone = TRUE;
        }
        else
        {
            res = HXR_UNEXPECTED;
        }
    }

    return res;
}

HX_RESULT 
COggPayload::packetizePage(ogg_page* pPage)
{
    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (pPage)
    {
        IHXPacket* pPkt = NULL;
        IHXBuffer* pBuf = NULL;

        res = m_pCCF->CreateInstance(CLSID_IHXPacket, (void**)&pPkt);

        if (HXR_OK == res)
        {
            res = m_pCCF->CreateInstance(CLSID_IHXBuffer, (void**)&pBuf);
        }

        if (HXR_OK == res)
        {
            res = pBuf->SetSize(pPage->header_len + pPage->body_len);
        }

        if (HXR_OK == res)
        {
            UCHAR* pPayload = pBuf->GetBuffer();
            memcpy(pPayload, pPage->header, pPage->header_len);
            memcpy(pPayload + pPage->header_len, 
                   pPage->body, pPage->body_len);
        }

        if (HXR_OK == res)
        {
            COggTimestamp tmp = m_startTS; 

            // convert to milliseconds
            tmp.SetSampleRate(1000);

            DPRINTF(D_OGG_PAYLOAD, 
                    ("COggPayload::packetizePage : new pkt %u %u %u\n",
                     m_uStreamNum, tmp.Samples(), pBuf->GetSize()));

            res = pPkt->Set(pBuf,
                            tmp.Samples(),
                            m_uStreamNum,
                            0,
                            0);
        }
        
        if (HXR_OK == res)
        {
            ogg_int64_t granulepos = ogg_page_granulepos(pPage);

            if (granulepos > 0)
            {
                COggTimestamp endTS;
                res = m_pCodecInfo->GetTimestamp(granulepos, endTS);

                m_startTS = endTS;
            }
        }

        if (HXR_OK == res)
        {
            if (m_pktList.AddTail(pPkt))
            {
                pPkt->AddRef();
            }
            else
            {
                res = HXR_OUTOFMEMORY;
            }
        }

        HX_RELEASE(pPkt);
        HX_RELEASE(pBuf);
    }

    return res;
}
