//---------------------------------------------------------------------------------------
//
// Simple Windows DirectShow Example Code
//
// Jim Shaw 12/3/2004
//
//---------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------
// Always include windows.h for windows programs
//---------------------------------------------------------------------------------------
#define _WIN32_DCOM
#include <windows.h>

#ifdef __WATCOMC__
#define _WINGDI_
#define _IAMFilterGraphCallback_
#endif

#include <dshow.h>

#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <assert.h>

//---------------------------------------------------------------------------------------
// Screen dimensions
//---------------------------------------------------------------------------------------
#define SCREEN_X   640
#define SCREEN_Y   480
#define IN_OWN_WINDOW 1

//---------------------------------------------------------------------------------------
// Variable to hold the address of the Windows window
//---------------------------------------------------------------------------------------
HWND game_window;

//---------------------------------------------------------------------------------------
// Name of our window class.  every class has a name.  every window is one of a class.
// this can be pretty much anything
//---------------------------------------------------------------------------------------
char game_class[]="DSCPPWindow";
static int quit = 0;

//---------------------------------------------------------------------------------------
// Function prototypes
//---------------------------------------------------------------------------------------
void draw(void);
void flip(void);
int init_dshow(wchar_t *, int);
void shutdown_dshow(void);
int handle_dshow_event(void);

//---------------------------------------------------------------------------------------
// DirectShow Variables
//---------------------------------------------------------------------------------------
IGraphBuilder *pGB=NULL;
IMediaControl *pMC=NULL;
IMediaEventEx *pME=NULL;
IMediaSeeking *pMS=NULL;
IVMRWindowlessControl *pWC=NULL;

#define WM_GRAPHNOTIFY0  (WM_USER+13)

//---------------------------------------------------------------------------------------
// Every window has a WindowProc which handles messages Windows sends to them.  These
// are things like key presses, mouse clicks, and thousands of other things you might
// be interested in.
//---------------------------------------------------------------------------------------
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		//this message gets sent to the window when you click the close box on it
		case WM_DESTROY:
			//tell Windows to close the app
			PostQuitMessage(0);
			quit = 1;
			break;

		//this message gets sent when you press a key
		case WM_KEYDOWN:
			switch (wParam)
			{
				//pressed Escape key?
				case VK_ESCAPE:
					//tell Windows to close the app
					PostQuitMessage(0);
					quit = 1;
					break;
			}
			break;

		case WM_GRAPHNOTIFY0:
			if (handle_dshow_event())
			{
				PostQuitMessage(0);
				quit = 1;
			}
			break;

		//any message you don't process gets passed to this function inside Windows.
		default:
			return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
	//any message you do process, return 0 (unless the message documentation says otherwise)
return 0;
}

//---------------------------------------------------------------------------------------
// Convert char string to wchar_t string
//---------------------------------------------------------------------------------------
static wchar_t c[_MAX_PATH];
static wchar_t *ctowc(char *p)
{
#ifdef __WATCOMC__
	swprintf(c, _MAX_PATH, L"%hs", p);
#else
	swprintf(c, L"%S", p);
#endif
	return c;
}

//---------------------------------------------------------------------------------------
// WinMain
//---------------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASSEX clas;
	MSG msg;
	int style;
	RECT rect;

	//need COM for DirectShow
	CoInitializeEx(NULL, COINIT_MULTITHREADED); 

	//Here we create the Class we named above
	clas.cbSize = sizeof(WNDCLASSEX);
	clas.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	clas.lpfnWndProc = WindowProc;//<- tell it where the WindowProc is
	clas.cbClsExtra = 0;
	clas.cbWndExtra = 0;
	clas.hInstance = hInstance;
	clas.hIcon = NULL;
	clas.hCursor = NULL;
	clas.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);//<- background colour of window
	clas.lpszMenuName = NULL;
	clas.lpszClassName = game_class;//<- the class name
	clas.hIconSm = 0;
	//do it!
	RegisterClassEx(&clas);

	//style of the window - what boxes do we need (close minimised etc)
	style = WS_CAPTION|WS_SYSMENU|WS_MAXIMIZEBOX|WS_MINIMIZEBOX;
	//create the window
	game_window = CreateWindowEx(0, game_class, "DirectShow", style, CW_USEDEFAULT, CW_USEDEFAULT, 1,1, NULL, NULL, hInstance, 0);

	//adjust the window size so that a SCREEN_X x SCREEN_Y window will fit inside its frame
	rect.left = rect.top = 0;
	rect.right = SCREEN_X;
	rect.bottom = SCREEN_Y;
	AdjustWindowRectEx(&rect, style , FALSE, 0);
	SetWindowPos(game_window, NULL, 0,0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE|SWP_NOZORDER);

	//show the window on the desktop
	ShowWindow(game_window, nCmdShow);

	char filename[_MAX_PATH];
	OPENFILENAME ofn;
	BOOL res;

	filename[0] = '\0';
	ofn.lStructSize = sizeof(OPENFILENAME); 
	ofn.hwndOwner = NULL; 
	ofn.hInstance = NULL;
	ofn.lpstrFilter = "AVI Files\0*.avi\0"; 
	ofn.lpstrCustomFilter = NULL; 
	ofn.nMaxCustFilter = 0; 
	ofn.nFilterIndex = 0; 
	ofn.lpstrFile = filename; 
	ofn.nMaxFile = _MAX_PATH; 
	ofn.lpstrFileTitle = NULL; 
	ofn.nMaxFileTitle = 0; 
	ofn.lpstrInitialDir = NULL; 
	ofn.lpstrTitle = "Select Video File"; 
	ofn.Flags = OFN_FILEMUSTEXIST|OFN_EXPLORER; 
	ofn.nFileOffset=0; 
	ofn.nFileExtension=0; 
	ofn.lpstrDefExt="bin"; 
	ofn.lCustData=0; 
	ofn.lpfnHook=NULL; 
	ofn.lpTemplateName=""; 

	res = GetOpenFileName(&ofn);
	if (res == 0)
		return 0;

	if (init_dshow(ctowc(filename), IN_OWN_WINDOW)==0)
	{
		MessageBox(NULL, "A problem occurred creating the DirectShow FilterGraph.\nCheck your video CODECs and "
							"DirectX version.\nThis demo requires DirectX9.0 or better.", "DirectX Initialisation Error", MB_ICONWARNING|MB_OK);
		shutdown_dshow();
		return 0;
	}

	//message processing loop
	//all Windows programs have one of these.  It receives the messages Windows sends to the program and
	//passes them to the WindowProc in the Class we registered for the window.
	quit = 0;
	do
	{
		//Are there any messages waiting?
		while (PeekMessage(&msg, game_window, 0, 0, PM_NOREMOVE))
		{
			//yes!  read it.
			if (GetMessage(&msg, game_window, 0,0) < 0)
				break;

			//pass the message to WindowProc
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

	} while (!quit);

	shutdown_dshow();

	CoUninitialize();

	return 0;
}

//---------------------------------------------------------------------------------------
// Initialise all the DirectShow structures we need
//---------------------------------------------------------------------------------------
int init_dshow(wchar_t *FileName, int render_in_own_window)
{
	HRESULT err;
	FILTER_STATE state = State_Stopped;

	// Create a new graph
    err = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGB);
	if (err != S_OK)
		return 0;

	// Get access to the video controls
	err = pGB->QueryInterface(IID_IMediaControl, (void **)&pMC);
	if (err != S_OK)
		return 0;

	err = pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS);
	if (err != S_OK)
		return 0;

	err = pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME);
	if (err != S_OK)
		return 0;

	if (render_in_own_window)
	{
		IBaseFilter *pVMR;
		IVMRFilterConfig *pFC; 
		long lWidth, lHeight; 
		RECT rcSrc, rcDest;

		err = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVMR); 
		if (err != S_OK)
			return 0;

		// Add the VMR to the filter graph.
		err = pGB->AddFilter(pVMR, L"VMR");
		if (err != S_OK)
			return 0;

		// Set the rendering mode.  
		err = pVMR->QueryInterface(IID_IVMRFilterConfig, (void**)&pFC); 
		if (err != S_OK)
			return 0;

		err = pFC->SetRenderingMode(VMRMode_Windowless); 
		if (err != S_OK)
			return 0;

		pFC->Release();

		// Set the window. 
		err = pVMR->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWC);
		if (err != S_OK)
			return 0;

		err = pWC->SetVideoClippingWindow(game_window); 
		if (err != S_OK)
			return 0;

		pVMR->Release();

		// Find the native video size.
		err = pWC->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL); 
		// Set the source rectangle.
		SetRect(&rcSrc, 0, 0, lWidth/2, lHeight/2); 
    
		// Get the window client area.
		GetClientRect(game_window, &rcDest); 
		// Set the destination rectangle.
		SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom); 
   
			// Set the video position.
		err = pWC->SetVideoPosition(&rcSrc, &rcDest); 
	}

	// Add the source file
	err = pGB->RenderFile(FileName, NULL);
	if (err != S_OK)
		return 0;

	// Have the graph signal event via window callbacks for performance
	err = pME->SetNotifyWindow((OAHWND)game_window, WM_GRAPHNOTIFY0, 0);
	if (err != S_OK)
		return 0;

	err = pMS->SetTimeFormat(&TIME_FORMAT_FRAME);
	if(err != S_OK)
	{
		err = pMS->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
	}

	err = pMC->Run();
	do
	{
		err = pMC->GetState(0, (long *)&state);
	} while (state != State_Running);

	return 1;
}

//---------------------------------------------------------------------------------------
// Shutdown DirectShow
//---------------------------------------------------------------------------------------
void shutdown_dshow(void)
{
	if (pWC) pWC->Release();
	if (pMS) pMS->Release();
	if (pME) pME->Release();
	if (pMC) pMC->Release();
	if (pGB) pGB->Release();
}

//---------------------------------------------------------------------------------------
// Handle DirectShow Event
//---------------------------------------------------------------------------------------
int handle_dshow_event(void)
{
    LONG evCode, evParam1, evParam2;
	int stopping = 0;

    // Make sure that we don't access the MediaEventEx interface after it has already been released.
    if (!pME)
	    return S_OK;

    // Process all queued events
    while (pME->GetEvent(&evCode, &evParam1, &evParam2, 0) == S_OK)
    {
        switch (evCode)
        {
			case EC_COMPLETE:
				stopping = 1;
				break;

			default:
				break;
       }

        // Free memory associated with callback, since we're not using it
        pME->FreeEventParams(evCode, evParam1, evParam2);
    }

	return stopping;
}
