// Copyright (c) 1997  Microsoft Corporation.  All Rights Reserved.
#include <windows.h>
#include <stdio.h>
#include "strmif.h"
#include "uuids.h"
#include "ddraw.h"
#include "mmstream.h"
#include "amstream.h"
#include "ddstream.h"

typedef HRESULT (STDAPICALLTYPE * PFNSAMPLECALLBACK) (IStreamSample *pSource,
                                                      IStreamSample *pDest,
                                                      void * pvContext);

#define RELEASE(x) if (x) { (x)->Release(); (x) = NULL; };
#define CHECK_ERROR(x)     \
   if (FAILED(hr = (x))) { \
       printf(#x "  failed with HRESULT(0x%8.8X)\n", hr); \
       goto Exit;          \
   }


#define MAX_COPY_STREAMS 5

class CopyPair {
public:
    IStreamSample *pSource;
    IStreamSample *pDest;
    PFNSAMPLECALLBACK pCallback;
    void * pCallbackContext;
    HRESULT hrLastStatus;
    bool    bReading;
};



class CCopyEngine
{
public:
    CCopyEngine() : m_cNumPairs(0) {};
    ~CCopyEngine();

    HRESULT CopyMediaStream(IMultiMediaStream *pSourceStream,
                            IMultiMediaStream *pDestStream,
                            REFMSPID PurposeId,
                            PFNSAMPLECALLBACK pCallback = NULL,
                            void * pContext = NULL);

    HRESULT AddCopyPair(IStreamSample *pSource, IStreamSample *pDest,
                        PFNSAMPLECALLBACK pCallback = NULL, void * pvContext = NULL);
    HRESULT CopyStreamData();

private:
    CopyPair        m_aPair[MAX_COPY_STREAMS];
    HANDLE          m_aEvent[MAX_COPY_STREAMS];
    int             m_cNumPairs;
};

HRESULT CCopyEngine::AddCopyPair(IStreamSample *pSource, IStreamSample *pDest,
                                 PFNSAMPLECALLBACK pCallback, void * pContext)
{
    if (m_cNumPairs >= MAX_COPY_STREAMS) {
        return E_FAIL;
    }
    HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!hEvent) {
        return E_OUTOFMEMORY;
    }
    pSource->AddRef();
    pDest->AddRef();
    m_aEvent[m_cNumPairs] = hEvent;
    m_aPair[m_cNumPairs].pSource = pSource;
    m_aPair[m_cNumPairs].pDest = pDest;
    m_aPair[m_cNumPairs].pCallback = pCallback;
    m_aPair[m_cNumPairs].pCallbackContext = pContext;
    m_cNumPairs++;
    return NOERROR;
}


HRESULT CCopyEngine::CopyMediaStream(IMultiMediaStream *pSourceMMStream,
                                     IMultiMediaStream *pDestMMStream,
                                     REFMSPID PurposeId,
                                     PFNSAMPLECALLBACK pCallback, void * pContext)
{
    HRESULT hr = E_FAIL;    // Assume it won't work.
    IMediaStream *pSource;
    if (pSourceMMStream->GetMediaStream(PurposeId, &pSource) == NOERROR) {
        IMediaStream *pDest;
        if (pDestMMStream->GetMediaStream(PurposeId, &pDest) == NOERROR) {
            IStreamSample *pSourceSample;
            hr = pSource->AllocateSample(0, &pSourceSample);
            if (SUCCEEDED(hr)) {
                IStreamSample *pDestSample;
                hr = pDest->CreateSharedSample(pSourceSample, 0, &pDestSample);
                if (SUCCEEDED(hr)) {
                    hr = AddCopyPair(pSourceSample, pDestSample, pCallback, pContext);
                    pDestSample->Release();
                }
                pSourceSample->Release();                    
            }
            pDest->Release();
        }
        pSource->Release();
    }
    return hr;
}

HRESULT CCopyEngine::CopyStreamData()
{
    if (m_cNumPairs == 0) {
        return S_FALSE;
    }
    int i;
    for (i = 0; i < m_cNumPairs; i++) {
        m_aPair[i].hrLastStatus = NOERROR;
        m_aPair[i].bReading = true;
        m_aPair[i].pSource->Update(0, m_aEvent[i], NULL, 0);
    }
    int NumRunning = i;
    while (NumRunning > 0) {
        DWORD dwWaitRet = WaitForMultipleObjects(m_cNumPairs, m_aEvent, FALSE, INFINITE);
        if (dwWaitRet >= WAIT_OBJECT_0 && dwWaitRet < WAIT_OBJECT_0 + m_cNumPairs) {
            int iCompleted = dwWaitRet - WAIT_OBJECT_0;
            CopyPair *pPair = &m_aPair[iCompleted];
            IStreamSample *pDone = pPair->bReading ? pPair->pSource : pPair->pDest;
            pPair->hrLastStatus = pDone->CompletionStatus(0, 0);
            if (pPair->hrLastStatus == NOERROR) {
                if (pPair->bReading) {
                    STREAM_TIME stStart, stStop;
                    if (pPair->pCallback) {
                        pPair->pCallback(pPair->pSource, pPair->pDest, pPair->pCallbackContext);
                    }
                    pPair->pSource->GetSampleTimes(&stStart, &stStop, NULL);
                    pPair->pDest->SetSampleTimes(&stStart, &stStop);
                    pPair->pDest->Update(0, m_aEvent[iCompleted], NULL, 0);
                    pPair->bReading = false;
                } else {
                    pPair->pSource->Update(0, m_aEvent[iCompleted], NULL, 0);
                    pPair->bReading = true;
                }
            } else {
                if (pPair->bReading && pPair->hrLastStatus == MS_S_ENDOFSTREAM) {
                    IMediaStream *pStream;
                    pPair->pDest->GetMediaStream(&pStream);
                    pStream->SendEndOfStream(0);
                    pStream->Release();
                    ResetEvent(m_aEvent[iCompleted]);
                }
                NumRunning--;
            }
        }
    }
    return NOERROR;
}


CCopyEngine::~CCopyEngine()
{
    int i;
    for (i = 0; i < m_cNumPairs; i++) {
        CloseHandle(m_aEvent[i]);
        m_aPair[i].pSource->Release();
        m_aPair[i].pDest->Release();
    }
}





HRESULT STDAPICALLTYPE ArcEffect(IStreamSample *pSource, IStreamSample *pDest, void * pvPrimarySurface)
{
    static int iFrame = 0;
    IDirectDrawStreamSample *pSample;
    if (pSource->QueryInterface(IID_IDirectDrawStreamSample, (void **)&pSample) == NOERROR) {
        IDirectDrawSurface *pSurface;
        IDirectDrawSurface *pPrimarySurface = (IDirectDrawSurface *)pvPrimarySurface;
        RECT rect;
        if (SUCCEEDED(pSample->GetSurface(&pSurface, &rect))) {
            HDC hdc;
            if (SUCCEEDED(pSurface->GetDC(&hdc))) {
                Ellipse(hdc, 0, 0, (iFrame * 2) % rect.right, iFrame % rect.bottom);
                pSurface->ReleaseDC(hdc);
            }
            pPrimarySurface->Blt(&rect, pSurface, &rect, DDBLT_WAIT, NULL);
            pSurface->Release();
        }
        pSample->Release();
    }
    iFrame ++;
    return NOERROR;
}


/*

HRESULT PolylineEffectToSample(IDirectDrawStreamSample *pSample)
{
    IDirectDrawSurface *pSurface = NULL;
    RECT rect;
    HRESULT hr;
    POINT pt[2] = {0};
    HDC hdc;
    
    CHECK_ERROR(pSample->GetSurface(&pSurface, &rect));
    pt[1].x = iFrame % rect.right;
    pt[1].y = rect.bottom;
    CHECK_ERROR(pSurface->GetDC(&hdc));
    Polyline(hdc, pt, 2);
    pSurface->ReleaseDC(hdc);
    iFrame ++;

Exit:
    RELEASE(pSurface);
    return hr;
}

*/

HRESULT FindCompressor(REFCLSID rcidCategory,
                       int Index,
                       IBaseFilter **ppFilter)
{
    *ppFilter = NULL;
    ICreateDevEnum *pCreateDevEnum = NULL;
    IEnumMoniker *pEm = NULL;
    IMoniker *pMoniker = NULL;
    ULONG cFetched;
    HRESULT hr;

    CHECK_ERROR(CoCreateInstance(
			  CLSID_SystemDeviceEnum,
			  NULL,
			  CLSCTX_INPROC_SERVER,
			  IID_ICreateDevEnum,
			  (void**)&pCreateDevEnum));
    CHECK_ERROR(pCreateDevEnum->CreateClassEnumerator(rcidCategory, &pEm, 0));
    if (Index) {
        pEm->Skip(Index);
    }
    CHECK_ERROR(pEm->Next(1, &pMoniker, &cFetched));
    if (cFetched == 1) {
        hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void **)ppFilter);
    }
Exit:
    RELEASE(pMoniker);
    RELEASE(pCreateDevEnum);
    RELEASE(pEm);
    return hr;
}

HRESULT CreateStreamWithSameFormat(IAMMultiMediaStream *pAMStream,
                                   IMultiMediaStream *pSourceMMStream,
                                   REFMSPID PurposeId,
                                   IMediaStream **ppNewMediaStream)
{
    IMediaStream *pSource;
    HRESULT hr = pSourceMMStream->GetMediaStream(PurposeId, &pSource);
    if (SUCCEEDED(hr)) {
        hr = pAMStream->AddMediaStream(pSource, &PurposeId, AMMSF_CREATEPEER, ppNewMediaStream);
        pSource->Release();
    }
    return hr;
}

HRESULT CreateWriterStream(const char * pszOutputFileName,
                           IMultiMediaStream *pSourceMMStream,
                           IDirectDraw *pDD,
                           IMultiMediaStream **ppMMStream)
{
	static LPWSTR szVideoRender = L"@device:sw:CLSID\\{083863F1-70DE-11D0-BD40-00A0C911CE86}\\Instance\\{70E102B0-5556-11CE-97C0-00AA0055595A}";
    *ppMMStream = NULL;
    IAMMultiMediaStream *pAMStream = NULL;
    IMediaStream *pVideoStream = NULL;
    IMediaStream *pAudioStream = NULL;
    ICaptureGraphBuilder *pBuilder = NULL;
    IGraphBuilder *pFilterGraph = NULL;
    IFileSinkFilter *pFileSinkWriter = NULL;
    IBaseFilter *pVideoCompressFilter = NULL;
    IBaseFilter *pMuxFilter = NULL;
    
    HRESULT hr;
    WCHAR       wPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, pszOutputFileName, -1, wPath, sizeof(wPath)/sizeof(wPath[0]));

    CHECK_ERROR(CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
				 IID_IAMMultiMediaStream, (void **)&pAMStream));
    CHECK_ERROR(pAMStream->Initialize(STREAMTYPE_WRITE, 0, NULL));

    CHECK_ERROR(CreateStreamWithSameFormat(pAMStream, pSourceMMStream, MSPID_PrimaryVideo, &pVideoStream));
    CHECK_ERROR(CreateStreamWithSameFormat(pAMStream, pSourceMMStream, MSPID_PrimaryAudio, &pAudioStream));

    CHECK_ERROR(pAMStream->GetFilterGraph(&pFilterGraph));

	// Create a new Bind Context (connects a file to an object type)
	LPBC lpBC;
	CreateBindCtx(0, &lpBC);

	ULONG cchEaten;
	IMoniker *pMoniker;
	hr = MkParseDisplayName(lpBC, szVideoRender, &cchEaten, &pMoniker);
	if( SUCCEEDED(hr) ) {
		IBaseFilter *pVideoRenderFilter = 0;
		hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pVideoRenderFilter);
		if( SUCCEEDED(hr) ) {
			pFilterGraph->AddFilter(pVideoRenderFilter, L"Video Renderer");
			pVideoRenderFilter->Release();
		}			
		pMoniker->Release();
	}

	pAMStream->Render(0);
	/*
    CHECK_ERROR(CoCreateInstance(CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC_SERVER,
                                 IID_ICaptureGraphBuilder, (void **)&pBuilder));

    CHECK_ERROR(pBuilder->SetFiltergraph(pFilterGraph));
    
    CHECK_ERROR(pBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, wPath, &pMuxFilter, &pFileSinkWriter));

    CHECK_ERROR(FindCompressor(CLSID_VideoCompressorCategory, 1, &pVideoCompressFilter));
    CHECK_ERROR(pFilterGraph->AddFilter(pVideoCompressFilter, L"Video Compression filter"))

    CHECK_ERROR(pBuilder->RenderCompressionStream(pVideoStream, pVideoCompressFilter, pMuxFilter));
    CHECK_ERROR(pBuilder->RenderCompressionStream(pAudioStream, NULL, pMuxFilter));
*/
    *ppMMStream = pAMStream;
    pAMStream->AddRef();

Exit:
    if (pAMStream == NULL) {
	printf("Could not create a CLSID_MultiMediaStream object\n"
	       "Check you have run regsvr32 amstream.dll\n");
    }
    RELEASE(pAMStream);
    RELEASE(pBuilder);
    RELEASE(pFilterGraph);
    RELEASE(pFileSinkWriter);
    RELEASE(pVideoCompressFilter);
    RELEASE(pMuxFilter);
    RELEASE(pVideoStream);
    RELEASE(pAudioStream);
    
    return hr;
}


HRESULT OpenReadMMStream(const char * pszFileName, IDirectDraw *pDD, IMultiMediaStream **ppMMStream)
{
    *ppMMStream = NULL;
    IAMMultiMediaStream *pAMStream;
    HRESULT hr;

    CHECK_ERROR(CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
				 IID_IAMMultiMediaStream, (void **)&pAMStream));
    CHECK_ERROR(pAMStream->Initialize(STREAMTYPE_READ, 0, NULL));
    CHECK_ERROR(pAMStream->AddMediaStream(pDD, &MSPID_PrimaryVideo, 0, NULL));
    CHECK_ERROR(pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, 0, NULL));

    WCHAR       wPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, pszFileName, -1, wPath, sizeof(wPath)/sizeof(wPath[0]));

    CHECK_ERROR(pAMStream->OpenFile(wPath, 0));

    *ppMMStream = pAMStream;
    pAMStream->AddRef();

Exit:
    if (pAMStream == NULL) {
	printf("Could not create a CLSID_MultiMediaStream object\n"
	       "Check you have run regsvr32 amstream.dll\n");
    }
    RELEASE(pAMStream);
    return hr;
}



HRESULT RenderStreamToSurface(IDirectDraw *pDD, IDirectDrawSurface *pPrimary,
			      IMultiMediaStream *pReadStream,
                              IMultiMediaStream *pWriteStream)
{
    HRESULT hr;
    CCopyEngine Engine;

    CHECK_ERROR(Engine.CopyMediaStream(pReadStream, pWriteStream, MSPID_PrimaryVideo, ArcEffect, pPrimary));
    CHECK_ERROR(Engine.CopyMediaStream(pReadStream, pWriteStream, MSPID_PrimaryAudio));

    CHECK_ERROR(pReadStream->SetState(STREAMSTATE_RUN));
    CHECK_ERROR(pWriteStream->SetState(STREAMSTATE_RUN));

    Engine.CopyStreamData();

    pReadStream->SetState(STREAMSTATE_STOP);
    pWriteStream->SetState(STREAMSTATE_STOP);

Exit:
    return hr;
}


int _CRTAPI1
main(
    int argc,
    char *argv[]
    )
{
    if (argc < 2) {
	printf("Usage : writer movie.ext\n");
	exit(0);
    }
    CoInitialize(NULL);
    IDirectDraw *pDD;

    HRESULT hr = DirectDrawCreate(NULL, &pDD, NULL);
    if (SUCCEEDED(hr)) {
	DDSURFACEDESC ddsd;
	IDirectDrawSurface *pPrimarySurface;

	pDD->SetCooperativeLevel(GetDesktopWindow(), DDSCL_NORMAL);

	ddsd.dwSize = sizeof(ddsd);
	ddsd.dwFlags = DDSD_CAPS;
	ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
//	ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
	hr = pDD->CreateSurface(&ddsd, &pPrimarySurface, NULL);
	if (SUCCEEDED(hr)) {
	    IMultiMediaStream *pReadStream;
	    hr = OpenReadMMStream(argv[1], pDD, &pReadStream);
	    if (SUCCEEDED(hr)) {
                IMultiMediaStream *pWriteStream;
                hr = CreateWriterStream("C:\\TEST.AVI", pReadStream, pDD, &pWriteStream);
                if (SUCCEEDED(hr)) {
		    RenderStreamToSurface(pDD, pPrimarySurface, pReadStream, pWriteStream);
                    pWriteStream->Release();
                }
                pReadStream->Release();
	    }
	    pPrimarySurface->Release();
	}
	pDD->Release();
    } else {
	printf("Could not open DirectDraw - check it is installed\n");
    }
    CoUninitialize();
    return 0;
}