//------------------------------------------------------------------------------
// File: XMLTlTst.cpp
//
// Desc: DirectShow sample code - test utility for building timelines from
//       .XTL files.
//
// Copyright (c) 1999 - 2000, Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

//
// This sample demonstrates the following:
// 
// - Using IXml2Dex to load timelines from and save to .XTL files 
// - Adding Windows Media (ASF) support to Editing applications
// - Using IAMErrorLog to display error messages
// - Preview of DES timelines (IAMTimeLine, IRenderEngine)
// - Traversing the timeline nodes
//

#include <windows.h>
#include <streams.h>
#include <stdio.h>
#include <atlbase.h>
#include <qedit.h>

extern CComModule _Module;
#include <atlimpl.cpp>

//
// NOTE:
//
// In order to write ASF files using this program, you need to obtain
// a Windows Media Format SDK Certificate (WMStub.lib), link to it, 
// and define USE_WMF_CERT below. 
//
// See the Windows Media Format SDK documentation for more information.
//
// The SDK download page is located at 
// http://msdn.microsoft.com/workshop/imedia/windowsmedia/sdk/wmsdk.asp, 
// with links to the SDK itself and information for obtaining a certificate.
//
// #define USE_WMF_CERT
// 

#ifdef USE_WMF_CERT

#include <wmsdk.h>
#include <dshowasf.h>


// Note: this object is a SEMI-COM object, and can only be created statically.
class CKeyProvider : public IServiceProvider {
public:
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
    STDMETHODIMP_(ULONG) AddRef() { return 2; }
    STDMETHODIMP_(ULONG) Release() { return 1; }

    // IServiceProvider
    STDMETHODIMP QueryService(REFIID siid, REFIID riid, void **ppv);
    
};

CKeyProvider g_prov; // note, must stay in scope while graph is alive

#endif // USE_WMF_CERT


class CErrorReporter : public IAMErrorLog
{
    // IAMErrorLog
    STDMETHODIMP LogError( long Severity, BSTR ErrorString, LONG ErrorCode, 
                           HRESULT hresult, VARIANT * pExtraInfo );

public:

    // SEMI COM
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
    {
        if (riid == IID_IAMErrorLog || riid == IID_IUnknown) {
            *ppv = (void *) static_cast<IAMErrorLog *>(this);
            return NOERROR;
        }    
        return E_NOINTERFACE;
    }
    STDMETHODIMP_(ULONG) AddRef() { return 2; }
    STDMETHODIMP_(ULONG) Release() { return 1; }
};


//
// Declarations for helper functions defined below
//
IPin * GetPin( IBaseFilter * pFilter, int PinNum, PIN_DIRECTION pd );

void TurnOffPreviewMode( IAMTimeline * pTimeline );

HRESULT GetFirstSourceOnTimeline(
    IAMTimeline *pTimeline, GUID MajorType,
    IAMTimelineGroup ** ppGroup, IAMTimelineSrc ** ppSource );
    
HRESULT GetSourceVideoType(WCHAR *wszFilename, AM_MEDIA_TYPE *pmt);

HRESULT GetDestinationASFFormat(
    AM_MEDIA_TYPE **ppmt,
    int iProfile );

#ifdef USE_WMF_CERT
HRESULT MapProfileIdToProfile(
    int iProfile, IWMProfile **ppProfile);
#endif

void ListWMSDKProfiles();
BOOL IsAsfExtension( WCHAR * Filename );

HRESULT ConnectOutputFile(
    IRenderEngine * pEngine, WCHAR * Filename
#ifdef USE_WMF_CERT
    , int iProfile
#endif
    );


//
// Main program code
//
int __cdecl main(int argc, char *argv[])
{
    CErrorReporter pLog; // note, must stay in scope while graph is alive
    HRESULT hr = 0;

    ++argv; // skip the app name
    --argc;

    int Ret = 0;

    char **&ppArg = argv;

    int i = 1;
    BOOL fNoRender = FALSE;
    BOOL fDynamic = FALSE;
    BOOL fRecomp = FALSE;
    BOOL fWriteIt = FALSE;
    int nWriteArg = -1;
    BOOL fWriteGrf = FALSE;
    int nWriteGrfArg = -1;
    int nInputFileArg = -1;
    BOOL fWriteXtl = FALSE;
    int nWriteXtlArg = -1;
    double RenderStart = -1.0;
    double RenderStop = -1.0;
    BOOL fNoClock = FALSE;
#ifdef USE_WMF_CERT
    int iASFProfile = -1;
#endif

    // find which switches we've set. Every time we process a switch,
    // we pull it off the stack
    //
    for( int arg = 0 ; arg < argc ; arg++ )
    {
        if( !ppArg[arg] ) continue;

        // found the input .xtl file specified
        //
        if( ppArg[arg][0] != '/' )
        {
            nInputFileArg = arg;
            continue;
        }

        if (ppArg[arg][1] == 'N' || ppArg[arg][1] == 'n') {
            fNoRender = TRUE;
            continue;
        }

        if (ppArg[arg][1] == 'D' || ppArg[arg][1] == 'd') {
            fDynamic = TRUE;
            continue;
        }

        if (ppArg[arg][1] == 'C' || ppArg[arg][1] == 'c') {
            fNoClock = TRUE;
            continue;
        }

        if (ppArg[arg][1] == 'R' || ppArg[arg][1] == 'r') {
            fRecomp = TRUE;
            continue;
        }
        if (ppArg[arg][1] == 'W' || ppArg[arg][1] == 'w') {
            fWriteIt = TRUE;
            nWriteArg = arg + 1;
            arg++; // advance beyond the extra arg
            continue;
        }
        if (ppArg[arg][1] == 'G' || ppArg[arg][1] == 'g') {
            fWriteGrf = TRUE;
            nWriteGrfArg = arg + 1;
            arg++; // advance beyond the extra arg
            continue;
        }
        if (ppArg[arg][1] == 'X' || ppArg[arg][1] == 'x') {
            fWriteXtl = TRUE;
            nWriteXtlArg = arg + 1;
            arg++; // advance beyond the extra arg
            continue;
        }

#ifdef USE_WMF_CERT        
        if (ppArg[arg][1] == 'P' || ppArg[arg][1] == 'p') {
            arg++;
            if (arg >= argc || (ppArg[arg][0] < '0' || ppArg[arg][0] > '9')) {
                ListWMSDKProfiles();
                return -1;
            }

            iASFProfile = atoiA(ppArg[arg]);
            printf("Using WMSDK profile #%d\r\n", iASFProfile);
            continue;
        }
#endif // USE_WMF_CERT
        
        if( ppArg[arg][1] == '[' ) {
            // need to pull doubles out of the string
            //
            char * p = &ppArg[arg][2];
            for( unsigned long k = 0 ; k < strlen( p ) ; k++ )
            {
                if( p[k] == '-' )
                {
                    break;
                }
            }
            if( p[k] != '-' )
            {
                printf( "forgot the '-' between doubles\r\n" );
                return -1;
            }
            for( unsigned long j = 0 ; j < strlen( p ) ; j++ )
            {
                if( p[j] == ']' )
                {
                    break;
                }
            }
            if( p[j] != ']' )
            {
                printf( "forgot the ']' \r\n" );
                return -1;
            }
            
            // set it to zero so atof will work
            //
            p[k] = 0;
            p[j] = 0;

            // get the float
            //
            RenderStart = atof( p );

            // get the next float
            //
            p = &p[k+1];

            RenderStop = atof( p );
            continue;
        }


        printf("unrecognized switch: %s\n\n", ppArg[arg]);
        nInputFileArg = -1;
        break;
    }
    
    long argucount = 0;

    if( argc < 1 || nInputFileArg == -1 ) {
        printf("Usage:     [various switches] input.xtl\r\n");
        printf("           /N  - No preview, just connect it up\r\n");
        printf("           /G output.grf - Output a GRF file\r\n");
        printf("           /X output.xtl - Output an XTL file\r\n");
        printf("           /R  - Connect the graph with smart recompression turned on\r\n");
        printf("           /W <filename> - Render the clip to file. (It may take a while)\r\n" );
        printf("           /D  - Dynamic connections on\r\n");
        printf("           /[double-double] - set the render range start/stop times\r\n" );
        printf("           /C  - No clock. Run as fast as you can\r\n" );
#ifdef USE_WMF_CERT        
        printf("           /P [number] - Choose an ASF compression profile\r\n" );
        printf("           /P without a number - list available profiles\r\n" );
#endif // USE_WMF_CERT
        return -1;
    }

    USES_CONVERSION;
    WCHAR *wszFileInput = A2W(ppArg[nInputFileArg]);

    CComPtr< IRenderEngine > pRenderEngine;
    CComPtr< IAMTimeline > pTimeline;
    CComPtr< IXml2Dex > pXml;


    do
    {
        hr = CoInitialize( NULL );

        hr = CoCreateInstance( 
            __uuidof(AMTimeline), 
            NULL, 
            CLSCTX_INPROC_SERVER, 
            __uuidof(IAMTimeline), 
            (void**) &pTimeline
            );

        if (FAILED(hr)) {
            printf("Failed to create timeline, error = %x\r\n", hr);
            Ret = -1;
            break;
        }

        CComQIPtr< IAMSetErrorLog, &IID_IAMSetErrorLog > pTimelineLog( pTimeline );
        if( pTimelineLog )
        {
            pTimelineLog->put_ErrorLog( &pLog );
        }

        hr = CoCreateInstance( 
            __uuidof(Xml2Dex), 
            NULL, 
            CLSCTX_INPROC_SERVER, 
            __uuidof(IXml2Dex), 
            (void**) &pXml
            );
    
        if (FAILED(hr)) {
            printf("QEDIT not registered properly\r\n");
            Ret = -1;
            break;
        }

        
        hr = pXml->ReadXMLFile(pTimeline, wszFileInput);
        printf("ReadXMLFile('%ls') returned %x\r\n", wszFileInput, hr);
        if (FAILED(hr)) {
            Ret = -1;
            break;
        }


        // validate sources
        //
        HANDLE WaitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

        long Flags =
            SFN_VALIDATEF_CHECK |
            SFN_VALIDATEF_POPUP | 
            // SFN_VALIDATEF_TELLME |
            SFN_VALIDATEF_REPLACE |
            SFN_VALIDATEF_USELOCAL |
            0;
        
        hr = pTimeline->ValidateSourceNames( Flags, NULL, (LONG_PTR) WaitEvent );
        if( hr == NOERROR )
        {
            WaitForSingleObject( WaitEvent, INFINITE );
        }
        CloseHandle( WaitEvent );

        // create a render engine
        //
        if( fRecomp )
        {
            hr = CoCreateInstance( 
                __uuidof(SmartRenderEngine),
                NULL,
                CLSCTX_INPROC_SERVER,
                __uuidof(IRenderEngine),
                (void**) &pRenderEngine );

            TurnOffPreviewMode( pTimeline );
        
#ifdef USE_WMF_CERT
            // if we're doing smart recompression then the AMTimeline object will 
            // need a way to get our key, so we SetSite on the AMTimeline object here.
            // Note that the pRenderEngine gets the key a little later than this.
            if( SUCCEEDED( hr ) )
            {
                CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pTimeline );
                ASSERT( pOWS );
                if( pOWS )
                {            
                    pOWS->SetSite((IUnknown *) (IServiceProvider *) &g_prov);
                }                
            }

#endif // USE_WMF_CERT

            SCompFmt0 scompFmt;
            ZeroMemory(&scompFmt, sizeof(scompFmt));
            // scompFmt.nFormatId initialized to 0;
            
            // use the compression type of the first input video in
            // the project (note this can change the height/width of
            // the group)

            CComPtr<IAMTimelineGroup> pGroup;
            CComPtr<IAMTimelineSrc> pSource;
            hr = GetFirstSourceOnTimeline( pTimeline, MEDIATYPE_Video, &pGroup, &pSource);


#ifdef USE_WMF_CERT

            // smart recompression works differently for ASF than it
            // does for AVI. ASF compression needs to be configured by
            // profile id rather than compressor and format block. We
            // cannot recover the profile used to author an ASF file,
            // so the caller is required to provide us the right
            // profile id. We ask the WMSDK for the media type
            // corresponding to the profile.
            
            WCHAR *wszFileTmp = A2W(ppArg[nWriteArg]);
            if( IsAsfExtension( wszFileTmp ) )
            {
                AM_MEDIA_TYPE *pmt;
                hr = GetDestinationASFFormat(
                    &pmt, 
                    iASFProfile);
                if(SUCCEEDED(hr)) {
                    CopyMediaType(&scompFmt.MediaType, pmt);
                    DeleteMediaType(pmt);
                }
            }
            else
#endif // USE_WMF_CERT
            {
                CComBSTR Filename;
                if(SUCCEEDED(hr)) {
                    hr = pSource->GetMediaName(&Filename);
                }
                if(SUCCEEDED(hr)) {
                    hr = GetSourceVideoType(Filename, &scompFmt.MediaType);
                }
            }
            
            if(SUCCEEDED(hr)) {
                hr = pGroup->SetSmartRecompressFormat( (long*) &scompFmt );
                FreeMediaType(scompFmt.MediaType);
            }
        }
        else
        {
            hr = CoCreateInstance( 
                __uuidof(RenderEngine),
                NULL,
                CLSCTX_INPROC_SERVER,
                __uuidof(IRenderEngine),
                (void**) &pRenderEngine );

        }
        if (FAILED(hr)) {
            printf("Failed to create render engine, error = %x\r\n", hr);
            Ret = -1;
            break;
        }

#ifdef USE_WMF_CERT

        //
        // give the RenderEngine a key to support ASF
        //
        {
            CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pRenderEngine );
            ASSERT( pOWS );
            if( pOWS )
            {        
                pOWS->SetSite((IUnknown *) (IServiceProvider *) &g_prov);
            }            
        }
#endif // USE_WMF_CERT

        long gRenderMedLocFlags = 0 |
            //        SFN_VALIDATEF_POPUP | 
            //        SFN_VALIDATEF_TELLME |
            //        SFN_VALIDATEF_VERBOSE | 
            //        SFN_VALIDATEF_USELOCAL |
            0;
        
        pRenderEngine->SetSourceNameValidation( NULL, NULL, gRenderMedLocFlags );

        // set it's timeline
        //
        hr = pRenderEngine->SetTimelineObject( pTimeline );
        if (FAILED(hr)) {
            printf("Failed to set timeline object, error = %x\r\n", hr);
            Ret = -1;
            break;
        }

        // set dynamicness
        //
        if (fDynamic) {
            pRenderEngine->SetDynamicReconnectLevel(CONNECTF_DYNAMIC_SOURCES);
            printf("DYNAMIC ON\r\n");
        } else {
            pRenderEngine->SetDynamicReconnectLevel(CONNECTF_DYNAMIC_NONE);
            printf("DYNAMIC OFF\r\n");
        }

        // set the renderrange, if there is one
        //
        if( RenderStart != -1.0 && RenderStop != -1.0 )
        {
            pRenderEngine->SetRenderRange2( RenderStart, RenderStop );
        }

        
       
        // connect up first half
        //
        hr = pRenderEngine->ConnectFrontEnd( );

        if( FAILED( hr ) )
        {
            printf("ConnectFrontEnd returned bomb code of %x\r\n", hr);
            Ret = -1;
            break;
        }



        // get this now, because other calls will need it
        //
        CComPtr< IGraphBuilder > pGraph;
        hr = pRenderEngine->GetFilterGraph( &pGraph );

        if (FAILED(hr)) {
            printf("Couldn't get graph!  hr = %x", hr);
            break;
        }

        // if we didn't want to write the file to disk, may as well render it
        //
        if( !fWriteIt )
        {
            hr = pRenderEngine->RenderOutputPins( );


            if (FAILED(hr)) 
            {
                printf("RenderOutputPins returned bomb code of %x\r\n", hr);
                Ret = -1;
                break;
            }

            if( fNoClock )
            {
                CComQIPtr< IMediaFilter, &IID_IMediaFilter > pMFGraph( pGraph );
                if( pMFGraph )
                {
                    pMFGraph->SetSyncSource( NULL );
                }
            }
        }
        else
        {
            TurnOffPreviewMode( pTimeline );
            WCHAR *wszFileTmp = A2W(ppArg[nWriteArg]);

            hr = ConnectOutputFile(pRenderEngine, wszFileTmp
#ifdef USE_WMF_CERT
                                   , iASFProfile
#endif
                                   );
                                   
            printf( "ConnectOutputFile returned %x\r\n", hr );
            if( FAILED( hr ) )
            {
                fNoRender = TRUE;
            }
        }

        // write out GRF files
        //
        if( fWriteGrf )
        {
            WCHAR *wszFileTmp = A2W(ppArg[nWriteGrfArg]);
            hr = pXml->WriteGrfFile(pGraph, wszFileTmp);
            printf("WriteGrfFile('%ls') returned %x\r\n", wszFileTmp, hr);
        }
        if( fWriteXtl )
        {
            WCHAR *wszFileTmp = A2W(ppArg[nWriteXtlArg]);
            hr = pXml->WriteXMLFile(pTimeline, wszFileTmp);
            printf("WriteXtlFile('%ls') returned %x\r\n", wszFileTmp, hr);
        }

        if (!fNoRender) {
            CComQIPtr< IMediaEvent, &IID_IMediaEvent > pEvent( pGraph );
            CComQIPtr< IMediaControl, &IID_IMediaControl > pControl( pGraph );
            hr = pControl->Run( );
            if (FAILED(hr)) {
                printf("Failed to run the graph, error = %x\r\n", hr);
                Ret = -1;
                break;
            }


            HANDLE hEvent;

            if( !pEvent ) {
                // unexpected error
                Ret = -1;
            }

            hr = pEvent->GetEventHandle((OAEVENT *)&hEvent);
            if(FAILED(hr)) {
                // unexpected error
                Ret = -1;
            }

            // wait for completion and dispatch messages for any windows
            // created on our thread.
            for(;;)
            {
                while(MsgWaitForMultipleObjects(
                    1,
                    &hEvent,
                    FALSE,
                    INFINITE,
                    QS_ALLINPUT) != WAIT_OBJECT_0)
                {
                    MSG Message;

                    while (PeekMessage(&Message, NULL, 0, 0, TRUE))
                    {
                        TranslateMessage(&Message);
                        DispatchMessage(&Message);
                    }
                }

                // event signaled. see if we're done.

                LONG levCode;
                LONG_PTR lp1, lp2;

                if(pEvent->GetEvent(&levCode, &lp1, &lp2, 0) == S_OK)
                {
                    EXECUTE_ASSERT(SUCCEEDED(
                        pEvent->FreeEventParams(levCode, lp1, lp2)));
                
                    if(levCode == EC_COMPLETE || levCode == EC_ERRORABORT ||
                       levCode == EC_USERABORT)
                    {
                        break;
                    }
                }
            }
            
            hr = pControl->Stop( );

            
        }
    } while(0); 

    pRenderEngine.Release( );
    pXml.Release( );
    if (pTimeline)
    {
        hr = pTimeline->ClearAllGroups();
        pTimeline.Release( );
    }

    CoUninitialize( );

    
    return Ret;
}

//
// helper function to traverse a timline and return the first source
// 

HRESULT GetFirstSourceOnTimeline(
    IAMTimeline *pTimeline, GUID MajorType,
    IAMTimelineGroup ** ppGroup, IAMTimelineSrc ** ppSource )
{
    *ppGroup = 0;
    *ppSource = 0;
    
    for( long g = 0 ;; g++ )
    {
        // locate group of right media type (audio / video)
        CComPtr< IAMTimelineObj > pGroupObj;
        HRESULT hr = pTimeline->GetGroup( &pGroupObj, g );
        if(FAILED(hr)) {
            break;
        }
        CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pGroup( pGroupObj );

        CMediaType GroupType;
        pGroup->GetMediaType( &GroupType );
        if( GroupType.majortype != MajorType ) {
            continue;
        }

        // found the right group, go through each track in it

        long TrackCount = 0;
        long Layers = 0;
        pTimeline->GetCountOfType( g, &TrackCount, &Layers, TIMELINE_MAJOR_TYPE_TRACK );
    
        if( TrackCount < 1 )
        {
            continue;
        }

        CComQIPtr< IAMTimelineComp, &IID_IAMTimelineComp > pGroupComp( pGroupObj );

        for( int CurrentLayer = 0 ; CurrentLayer < Layers ; CurrentLayer++ )
        {
            // get the layer itself
            //
            CComPtr< IAMTimelineObj > pLayer;
            hr = pGroupComp->GetRecursiveLayerOfType( &pLayer, CurrentLayer, TIMELINE_MAJOR_TYPE_TRACK );

            // ask if the layer is muted
            //
            BOOL LayerMuted = FALSE;
            pLayer->GetMuted( &LayerMuted );
            if( LayerMuted )
            {
                // don't look at this layer
                //
                continue; // skip this layer, don't worry about grid
            }

            CComQIPtr< IAMTimelineTrack, &IID_IAMTimelineTrack > pTrack( pLayer );
            if( !pTrack )
            {
                continue;
            }

            REFERENCE_TIME InOut = 0;
            while( 1 )
            {
                // get the next source on this layer, given a time.
                //
                CComPtr< IAMTimelineObj > pSourceObj;
                hr = pTrack->GetNextSrc( &pSourceObj, &InOut );
                ASSERT( !FAILED( hr ) );
                if( hr != NOERROR )
                {
                    // all done with sources
                    //
                    break;
                }

                CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource( pSourceObj );
                ASSERT( pSource );
                if( !pSource )
                {
                    // this one failed, so look at the next
                    //
                    continue; // sources
                }

                // ask if the source is muted
                //
                BOOL SourceMuted = FALSE;
                pSourceObj->GetMuted( &SourceMuted );
                if( SourceMuted )
                {
                    // don't look at this source
                    //
                    continue; // sources
                }

                *ppSource = pSource;
                pSource.p->AddRef();
                *ppGroup = pGroup;
                pGroup.p->AddRef();

                return S_OK;

            } // while sources


        } // while currentlayer

    } // for all groups

    return VFW_E_NOT_FOUND;
}

void TurnOffPreviewMode( IAMTimeline * pTimeline )
{
    long Groups = 0;
    pTimeline->GetGroupCount( &Groups );
    for( int i = 0 ; i < Groups ; i++ )
    {
        CComPtr< IAMTimelineObj > pGroupObj;
        pTimeline->GetGroup( &pGroupObj, i );
        CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pGroup( pGroupObj );
        pGroup->SetPreviewMode( FALSE );
    }
}

//
// uses the media detector to retrieve mediatype of a clip
// 
HRESULT GetSourceVideoType(WCHAR *wszFilename, AM_MEDIA_TYPE *pmt)
{
    CComPtr< IMediaDet > pDet;
    HRESULT hr = CoCreateInstance( CLSID_MediaDet,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_IMediaDet,
                           (void**) &pDet );
    if( FAILED( hr ) ) {
        return hr;
    }

#ifdef USE_WMF_CERT

    //
    // set the site provider on the MediaDet object to allowed keyed apps
    // to use ASF decoder
    //
    CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pDet );
    if( pOWS )
    {
        pOWS->SetSite( (IServiceProvider *) &g_prov );
    }

#endif // USE_WMF_CERT

    hr = pDet->put_Filename( wszFilename );
    if( FAILED( hr ) )
    {
        return hr;
    }

    // go through and find the video stream type
    //
    long Streams = 0;
    long VideoStream = -1;
    hr = pDet->get_OutputStreams( &Streams );
    for( int i = 0 ; i < Streams ; i++ )
    {
        pDet->put_CurrentStream( i );
        GUID Major = GUID_NULL;
        pDet->get_StreamType( &Major );
        if( Major == MEDIATYPE_Video )
        {
            VideoStream = i;
            break;
        }
    }
    if( VideoStream == -1 )
    {
        return VFW_E_INVALIDMEDIATYPE;
    }

    hr = pDet->get_StreamMediaType( pmt );
    return hr;
}


#ifdef USE_WMF_CERT

// ------------------------------------------------------------------------
// CKeyProvider methods
// 

HRESULT CKeyProvider::QueryInterface(REFIID riid, void ** ppv)
{
    if (riid == IID_IServiceProvider || riid == IID_IUnknown) {
        *ppv = (void *) static_cast<IServiceProvider *>(this);
        return NOERROR;
    }    
    return E_NOINTERFACE;
}


STDMETHODIMP CKeyProvider::QueryService(REFIID siid, REFIID riid, void **ppv)
{
    if (siid == __uuidof(IWMReader) && riid == IID_IUnknown) {
    
        IUnknown *punkCert;

        HRESULT hr = WMCreateCertificate( &punkCert );
        if (SUCCEEDED(hr)) {
            *ppv = (void *) punkCert;
        }
        return hr;
    }
    return E_NOINTERFACE;
}

#endif // USE_WMF_CERT

// ------------------------------------------------------------------------
// CErrorReporter methods
// 

STDMETHODIMP CErrorReporter::LogError( long Severity, BSTR ErrorString, LONG ErrorCode, HRESULT hresult, VARIANT * pExtraInfo )
{
    USES_CONVERSION;
    printf( "Error %d (HRESULT %x)\r\n", ErrorCode, hresult);
    char * t = W2A( ErrorString );
    printf( t );
    printf( "\r\n" );

#ifdef DEBUG
    OutputDebugStringA( t );
    OutputDebugString( TEXT("\r\n") );
#endif // DEBUG

    // look at variant
    //
    if( pExtraInfo )
    {
        if( pExtraInfo->vt == VT_BSTR )
        {
            printf( "Extra Info:%ls\r\n", pExtraInfo->bstrVal);
        } else if( pExtraInfo->vt == VT_I4 ) {
            printf( "Extra Info: %d\r\n", (int)pExtraInfo->lVal );
        } else if( pExtraInfo->vt == VT_R8 ) {
            printf( "Extra Info: %d/100\r\n",
                    (int)(V_R8(pExtraInfo) * 100));
        }
    }
    return hresult;
}

#ifdef USE_WMF_CERT

// list formats Windows Media Format SDK supports.
// 
void ListWMSDKProfiles()
{
    USES_CONVERSION;
    
    int wextent = 0 ;
    int Loop = 0 ;
    SIZE extent ;
    DWORD cProfiles = 0 ;

    printf("Standard system profiles:\n");
    
    CComPtr <IWMProfileManager> pIWMProfileManager;

    HRESULT hr = WMCreateProfileManager( &pIWMProfileManager );
    if( FAILED( hr ) ) {   
        return; // error
    }        

    IWMProfileManager2* pIPM2;
    hr = pIWMProfileManager->QueryInterface( IID_IWMProfileManager2,
                                             ( void ** )&pIPM2 );
    if(FAILED(hr)) {
        return;
    }
    // we only use 7_0 profiles
    hr = pIPM2->SetSystemProfileVersion( WMT_VER_7_0 );
    pIPM2->Release();
    
    if(FAILED(hr)) {
        return;
    }
        
    hr = pIWMProfileManager->GetSystemProfileCount(  &cProfiles );
    if( FAILED( hr ) )
    {
        return;
    }
        
    //    
    // now load the profile strings
    //    
    LRESULT ix;
    DWORD cchName, cchDescription;
    for (int i = 0; i < (int)cProfiles; ++i) {
        CComPtr <IWMProfile> pIWMProfile;
        
        hr = pIWMProfileManager->LoadSystemProfile( i, &pIWMProfile );
        if( FAILED( hr ) )
            return;
            
        hr = pIWMProfile->GetName( NULL, &cchName );
        if( FAILED( hr ) )
            return;
            
        WCHAR *wszProfile = new WCHAR[ cchName ];
        if( NULL == wszProfile )
            return;
            
        hr = pIWMProfile->GetName( wszProfile, &cchName );
        if( FAILED( hr ) )
            return;
        
        hr = pIWMProfile->GetDescription( NULL, &cchDescription );
        if( FAILED( hr ) )
            return;
            
        WCHAR *wszDescription = new WCHAR[ cchDescription ];
        if( NULL == wszDescription )
            return;
            
        
        hr = pIWMProfile->GetDescription( wszDescription, &cchDescription );
        if( FAILED( hr ) )
            return;
        

        printf("  %3d:  %ls\n", i, wszProfile);
        
        delete[] wszProfile;
        delete[] wszDescription;
    }

    printf("\r\n Use /p [#] to choose the profile you want\r\n"); 
}
#endif // USE_WMF_CERT

// 
// helper function to return Nth input pin or output pin on a
// filter. pin is addref'd
// 
IPin * GetPin( IBaseFilter * pFilter, int PinNum, PIN_DIRECTION pd )
{
    CComPtr<IEnumPins> pEnum;
    HRESULT hr = pFilter->EnumPins( &pEnum );
    if(SUCCEEDED(hr))
    {
        ULONG cFetched;
        IPin * pPin;
        while(pEnum->Next( 1, &pPin, &cFetched) == S_OK)
        {
            PIN_DIRECTION pd2;
            pPin->QueryDirection( &pd2 );
            if( pd2 == pd )
            {
                if( PinNum == 0 )
                {
                    // return addref'd pin
                    return pPin;
                }
                PinNum--;
            }

            pPin->Release();
        }
    }

    return NULL;
}

//
// Configure the graph to write to an AVI file (or ASF or .WAV)
//

HRESULT ConnectOutputFile(
    IRenderEngine * pEngine, WCHAR * Filename
#ifdef USE_WMF_CERT
    , int iProfile
#endif
    )
{
    CComPtr< ICaptureGraphBuilder2 > pBuilder;
    HRESULT hr = CoCreateInstance(
        CLSID_CaptureGraphBuilder2,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_ICaptureGraphBuilder2,
        (void**) &pBuilder );
    if( FAILED( hr ) )
    {
        return hr;
    }

    CComPtr< IAMTimeline > pTimeline;
    hr = pEngine->GetTimelineObject( &pTimeline );
    if( FAILED( hr ) )
    {
        return hr;
    }

    CComPtr< IGraphBuilder > pGraph;
    hr = pEngine->GetFilterGraph( &pGraph );
    if( FAILED( hr ) )
    {
        return hr;
    }

    long Groups = 0;
    pTimeline->GetGroupCount( &Groups );
    if( !Groups )
    {
        return E_INVALIDARG;
    }

    CComPtr< IBaseFilter > pMux;
    CComPtr< IFileSinkFilter > pWriter;

    hr = pBuilder->SetFiltergraph( pGraph );
    if( FAILED( hr ) )
    {
        return hr;
    }


    bool fConnectManually = false;
    GUID guid = MEDIASUBTYPE_Avi;

    // determine which writer to use based on file extension
    if (lstrlenW(Filename) > 4)
    {
        WCHAR *pExt = Filename + lstrlenW(Filename) - 3;

#ifdef USE_WMF_CERT
        if(IsAsfExtension(pExt))
        {
            guid =  CLSID_WMAsfWriter;
        }
#endif // USE_WMF_CERT
        if (lstrcmpiW(pExt, L"wav") == 0)
        {
            fConnectManually = true;
            
            // creation of the wav writer filter SDK sample
            //
            // .wav files need to be special-cased here because the
            // capture graph builder doesn't know about the wav writer
            // filter.
            // 
            // {3C78B8E2-6C4D-11d1-ADE2-0000F8754B99}
            const CLSID CLSID_WavDest = {
                0x3C78B8E2,0x6C4D,0x11D1,{0xAD,0xE2,0x00,0x00,0xF8,0x75,0x4B,0x99}
            };

            hr = CoCreateInstance(
                CLSID_WavDest,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IBaseFilter,
                (void**) &pMux );
            if( SUCCEEDED( hr ) )
            {
                hr = pGraph->AddFilter( pMux, L"Wave Mux" );
            }

            if( SUCCEEDED( hr ) )
            {
                hr = CoCreateInstance(
                    CLSID_FileWriter,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_IFileSinkFilter,
                    (void**) &pWriter );
            }
            if( SUCCEEDED( hr ) )
            {
                hr = pWriter->SetFileName( Filename, NULL );
            }
            if( SUCCEEDED( hr ) )
            {
                CComQIPtr< IBaseFilter, &IID_IBaseFilter > pWriterBase( pWriter );
                hr = pGraph->AddFilter( pWriterBase, L"Writer" );
            }
        }
    }

    if(FAILED(hr)) {
        return hr;
    }

    if(!fConnectManually)
    {
        hr = pBuilder->SetOutputFileName(
            &guid, Filename, &pMux, &pWriter );
    }
    if( FAILED( hr ) )
    {
        return hr;
    }

    CComQIPtr<IConfigInterleaving, &IID_IConfigInterleaving> pConfigInterleaving(pMux);
    if(pConfigInterleaving) {
        pConfigInterleaving->put_Mode(INTERLEAVE_FULL);
    }
    CComQIPtr<IFileSinkFilter2, &IID_IFileSinkFilter2> pCfgFw(pWriter);
    if(pCfgFw) {
        pCfgFw->SetMode(AM_FILE_OVERWRITE);
    }
     
#ifdef USE_WMF_CERT
    CComQIPtr<IConfigAsfWriter, &IID_IConfigAsfWriter> pConfigAsfWriter(pMux);
    if(pConfigAsfWriter && iProfile >= 0)
    {
        CComPtr<IWMProfile> pProfile;
        hr = MapProfileIdToProfile(iProfile, &pProfile);
        if(FAILED(hr)) {
            return hr;
        }

        // note that the ASF writer will not run if the number of streams
        // does not match the profile.
        hr = pConfigAsfWriter->ConfigureFilterUsingProfile(pProfile);
        if(FAILED(hr)) {
            return hr;
        }
    }
#endif

    for( int g = 0 ; g < Groups ; g++ )
    {
        CComPtr< IPin > pPin;
        hr = pEngine->GetGroupOutputPin( g, &pPin );
        if( FAILED( hr ) )
        {
            return hr;
        }

        // connect pin to the mux
        //
        hr = pBuilder->RenderStream( NULL, NULL, pPin, NULL, pMux );
        if( FAILED( hr ) )
        {
            return hr;
        }
    }

    if( fConnectManually )
    {
        // connect the mux to the writer. the wav mux refuses to
        // connect its output pin until its input pin is
        // connected. Otherwise this could be done earlier.
        //
        CComQIPtr< IBaseFilter, &IID_IBaseFilter > pWriterBase( pWriter );
        CComPtr<IPin> pMuxOut, pWriterIn;
        pMuxOut.p = GetPin( pMux, 0, PINDIR_OUTPUT );
        pWriterIn.p = GetPin( pWriterBase, 0, PINDIR_INPUT );
        if(!pMuxOut || !pWriterIn) {
            return E_UNEXPECTED;
        }
        hr = pGraph->Connect( pMuxOut, pWriterIn );
    }

    return hr;
}

BOOL IsAsfExtension( WCHAR *Filename )
{
    if (lstrlenW(Filename) >= 3)
    {
        WCHAR *pExt = Filename + lstrlenW(Filename) - 3;

        if(lstrcmpiW(pExt, L"asf") == 0 ||
           lstrcmpiW(pExt, L"wma") == 0 ||
           lstrcmpiW(pExt, L"wmv") == 0)
        {
            return TRUE;
        }
    }
    return FALSE;
}

//
// Get the mediatype for a particular ASF profile. it does this using
// the DirectShow components that use the WMSDK rather than through
// the WMSDK directly.
//

#ifdef USE_WMF_CERT

HRESULT GetDestinationASFFormat(
    AM_MEDIA_TYPE **ppmt,
    int iProfile
    )
{
    *ppmt = 0;
    
    CComPtr< IConfigAsfWriter > pWriter;
    HRESULT hr = CoCreateInstance(
        CLSID_WMAsfWriter,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_IConfigAsfWriter,
        (void **)&pWriter);
    if(FAILED(hr)) {
        return hr;
    }

    CComQIPtr<IBaseFilter,&IID_IBaseFilter> pwf(pWriter);
    if(!pWriter) {
        return E_UNEXPECTED;
    }

    // we have to add the ASF writer to a dummy graph in order for the
    // calls to work.
    CComPtr< IGraphBuilder > pBuilder;
    hr = CoCreateInstance(
        CLSID_FilterGraph,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_IGraphBuilder,
        (void**) &pBuilder );
    if( FAILED( hr ) )
    {
        return hr;
    }

    //
    // give the graph a key to support ASF
    //
    {
        CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pBuilder );
        ASSERT( pOWS );
        if( pOWS )
        {        
            pOWS->SetSite((IUnknown *) (IServiceProvider *) &g_prov);
        }            
    }

    hr = pBuilder->AddFilter(pwf, 0);
    if(FAILED(hr)) {
        return hr;
    }

    // we use profile id -1 to indicate no profile was set.
    if (iProfile >= 0)
    {
        CComPtr<IWMProfile> pProfile;
        hr = MapProfileIdToProfile(iProfile, &pProfile);
        if(FAILED(hr)) {
            return hr;
        }
        hr = pWriter->ConfigureFilterUsingProfile(pProfile);
        if(FAILED(hr)) {
            return hr;
        }
    }

    CComPtr<IEnumPins> pEnumPins;
    hr = pwf->EnumPins( &pEnumPins );
    if(FAILED(hr)) {
        return hr;
    }
    
    // get destination video format from ASF writer using IAMStreamConfig::GetFormat on
    // its input pins
    int iPins = 0;
    ULONG ul = 0;
    CComPtr < IPin > pWriterInputPin; // we only expect to find input pins
    while( S_OK == pEnumPins->Next( 1, &pWriterInputPin, &ul ) )
    {
#ifdef DEBUG
        {
            PIN_DIRECTION pd;
            pWriterInputPin->QueryDirection( &pd );
            ASSERT( PINDIR_OUTPUT != pd );
        }
#endif
        CComQIPtr <IAMStreamConfig, &IID_IAMStreamConfig> pStreamConfig( pWriterInputPin);
        if( !pStreamConfig ) {
            return E_UNEXPECTED;
        }
        pWriterInputPin.Release();

        AM_MEDIA_TYPE *pmt2;
        hr = pStreamConfig->GetFormat( &pmt2 );
        if( SUCCEEDED( hr ) )
        {
            if(pmt2->majortype != MEDIATYPE_Video) {
                DeleteMediaType(pmt2);
                continue;
            }
            *ppmt = pmt2; // caller needs to delete
            return S_OK;
        }
    }

    return E_FAIL;
}


HRESULT MapProfileIdToProfile(
    int iProfile, IWMProfile **ppProfile)
{
    *ppProfile = 0;
    
    CComPtr <IWMProfileManager> pIWMProfileManager;
    HRESULT hr = WMCreateProfileManager( &pIWMProfileManager );
    if(FAILED(hr)) {
        return hr;
    }

    // we only use 7_0 profiles
    CComQIPtr<IWMProfileManager2, &IID_IWMProfileManager2> pIPM2(pIWMProfileManager);
    if(!pIWMProfileManager) {
        return E_UNEXPECTED;
    }
    hr = pIPM2->SetSystemProfileVersion( WMT_VER_7_0 );
    if(FAILED(hr)) {
        return hr;
    }

    DWORD cProfiles;
    hr = pIWMProfileManager->GetSystemProfileCount(  &cProfiles );
    if(FAILED(hr)) {
        return hr;
    }
    if( (DWORD)iProfile >= cProfiles ) {
        printf("Invalid profile: %d\n", iProfile);
        return E_INVALIDARG;
    }

    return pIWMProfileManager->LoadSystemProfile( iProfile, ppProfile );
}

#endif // USE_WMF_CERT
