//This software is based on Touchless, which is released under the Microsoft Public License (Ms-PL) 
#ifdef CINTERFACE
#undef CINTERFACE
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dshow.h>
#pragma comment(lib, "strmiids")
extern "C" {
#include "../../common/common.h"
#include "webcam.h"
#include "bmp2jpeg.h"
}

//Required interface stuff - bad hack for qedit.h not being present/compatible with later windows versions
interface ISampleGrabberCB : public IUnknown {
	virtual STDMETHODIMP SampleCB( double SampleTime, IMediaSample *pSample ) = 0;
	virtual STDMETHODIMP BufferCB( double SampleTime, BYTE *pBuffer, long BufferLen ) = 0;
};
static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
interface ISampleGrabber : public IUnknown {
	virtual HRESULT STDMETHODCALLTYPE SetOneShot( BOOL OneShot ) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetMediaType( const AM_MEDIA_TYPE *pType ) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType( AM_MEDIA_TYPE *pType ) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetBufferSamples( BOOL BufferThem ) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer( long *pBufferSize, long *pBuffer ) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetCurrentSample( IMediaSample **ppSample ) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetCallback( ISampleGrabberCB *pCallback, long WhichMethodToCallback ) = 0;
};
static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };

//Handle used for synchronization. Main thread waits for capture event to be signalled to clean up
HANDLE writeEvent;

//Store width/height of captured frame
int nWidth;
int nHeight;
bool running = false;

//Capture variables
#define MAX_CAMERAS		10
IGraphBuilder* g_pGraphBuilder = NULL;
IMediaControl* g_pMediaControl = NULL;
ICaptureGraphBuilder2* g_pCaptureGraphBuilder = NULL;
IBaseFilter* g_pIBaseFilterCam = NULL;
IBaseFilter* g_pIBaseFilterSampleGrabber = NULL;
IBaseFilter* g_pIBaseFilterNullRenderer = NULL;

PBYTE imgdata = NULL;
long imgsize = 0;
UINT bmpsize = 0;
PBYTE bmpdata = NULL;
DWORD jpgsize = 0;
PBYTE jpgarray = NULL; //shouldn't be bigger, right?

// SampleGrabber callback interface
class MySampleGrabberCB : public ISampleGrabberCB{
public:
	MySampleGrabberCB(){
		m_nRefCount = 0;
	}
	virtual HRESULT STDMETHODCALLTYPE SampleCB( 
            double SampleTime,
            IMediaSample *pSample){
		 return E_FAIL;
	 }
     virtual HRESULT STDMETHODCALLTYPE BufferCB( 
            double SampleTime,
            BYTE *pBuffer,
            long BufferLen) {
		if (imgdata == NULL || imgsize < BufferLen){
			imgsize = BufferLen;
			if(imgdata != NULL)
				free(imgdata);
			imgdata = (PBYTE)malloc(imgsize);
		}
		memcpy(imgdata,pBuffer,imgsize);
		SetEvent(writeEvent); //Notify of new frame
		return S_OK;
	 }
	virtual HRESULT STDMETHODCALLTYPE QueryInterface( 
            REFIID riid,
            void **ppvObject) {
		 return E_FAIL;  // Not a very accurate implementation
	 }
	virtual ULONG STDMETHODCALLTYPE AddRef(){
		return ++m_nRefCount;
	}
	virtual ULONG STDMETHODCALLTYPE Release(){
		int n = --m_nRefCount;
		if (n <= 0)
			delete this;
		return n;
	}
private:
	int m_nRefCount;
};

extern "C" {
// lists webcams
DWORD request_webcam_list(Remote *remote, Packet *packet){
	Packet *response = packet_create_response(packet);
	DWORD dwResult = ERROR_SUCCESS;

	do{
		IEnumMoniker* pclassEnum = NULL;
		ICreateDevEnum* pdevEnum = NULL;
		
		CoInitialize(NULL);
		HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, 
				NULL, 
				CLSCTX_INPROC, 
				IID_ICreateDevEnum, 
				(LPVOID*)&pdevEnum);

		if (SUCCEEDED(hr))
			hr = pdevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pclassEnum, 0);

		if (pdevEnum != NULL){
			pdevEnum->Release();
			pdevEnum = NULL;
		}
		int nCount = 0;
		IUnknown* pUnk = NULL;
		if (pclassEnum == NULL)
			break;// Error!

		IMoniker* apIMoniker[1];
		ULONG ulCount = 0;
		while (SUCCEEDED(hr) && nCount < MAX_CAMERAS && pclassEnum->Next(1, apIMoniker, &ulCount) == S_OK){
			IPropertyBag *pPropBag;
			hr = apIMoniker[0]->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
			if (SUCCEEDED(hr)) {
				// To retrieve the filter's friendly name, do the following:
				VARIANT varName;
				VariantInit(&varName);
				hr = pPropBag->Read(L"FriendlyName", &varName, 0);
				//get chars from wchars
				size_t converted;
				char charbuf[512];
				wcstombs_s(&converted, charbuf, sizeof(charbuf), varName.bstrVal, sizeof(charbuf));
				if (SUCCEEDED(hr) && varName.vt == VT_BSTR)
					packet_add_tlv_string(response, TLV_TYPE_WEBCAM_NAME, charbuf);
				VariantClear(&varName);
				pPropBag->Release();
			}
			nCount++;
		}
		pclassEnum->Release();
		if(pUnk == NULL)
			break;// No webcam!
	} while (0);

	dwResult = GetLastError();
	packet_transmit_response(dwResult, remote, response);
	return dwResult;
}

// Starts webcam
DWORD request_webcam_start(Remote *remote, Packet *packet){
	Packet *response = packet_create_response(packet);
	DWORD dwResult = ERROR_SUCCESS;
	UINT index = packet_get_tlv_value_uint(packet, TLV_TYPE_WEBCAM_INTERFACE_ID);

	do {
		if(running)
			BREAK_WITH_ERROR("Already running!", ERROR_SERVICE_ALREADY_RUNNING)
		IEnumMoniker* pclassEnum = NULL;
		ICreateDevEnum* pdevEnum = NULL;
		if(index < 1)
			BREAK_WITH_ERROR("No webcams found", ERROR_FILE_NOT_FOUND)
		CoInitialize(NULL);
		HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, 
				NULL, 
				CLSCTX_INPROC, 
				IID_ICreateDevEnum, 
				(LPVOID*)&pdevEnum);
		if (FAILED(hr))
			BREAK_WITH_ERROR("No webcams found", hr)

		hr = pdevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pclassEnum, 0);

		if (pdevEnum != NULL){
			pdevEnum->Release();
			pdevEnum = NULL;
		}
		UINT nCount = 0;
		IUnknown* pUnk = NULL;
		if (pclassEnum == NULL)
			BREAK_WITH_ERROR("No webcams found", ERROR_FILE_NOT_FOUND) // Error!
		IMoniker* apIMoniker[1];
		ULONG ulCount = 0;
		while (SUCCEEDED(hr) && nCount < index && pclassEnum->Next(1, apIMoniker, &ulCount) == S_OK){
			pUnk = apIMoniker[0];
			nCount++;
		}
		pclassEnum->Release();
		if(pUnk == NULL)
			BREAK_WITH_ERROR("No webcams found", ERROR_FILE_NOT_FOUND)
		IMoniker *pMoniker = NULL;

		// Grab the moniker interface
		hr = pUnk->QueryInterface(IID_IMoniker, (LPVOID*)&pMoniker);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Query interface failed", hr)

		// Build all the necessary interfaces to start the capture
		hr = CoCreateInstance(CLSID_FilterGraph, 
			NULL, 
			CLSCTX_INPROC, 
			IID_IGraphBuilder, 
			(LPVOID*)&g_pGraphBuilder);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Filter graph creation failed", hr)

		hr = g_pGraphBuilder->QueryInterface(IID_IMediaControl, (LPVOID*)&g_pMediaControl);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Query interface failed", hr)

		hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, 
			NULL, 
			CLSCTX_INPROC, 
			IID_ICaptureGraphBuilder2, 
			(LPVOID*)&g_pCaptureGraphBuilder);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Capture Graph Builder failed", hr)

		// Setup the filter graph
		hr = g_pCaptureGraphBuilder->SetFiltergraph(g_pGraphBuilder);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Set filter graph failed", hr)
		// Build the camera from the moniker
		hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (LPVOID*)&g_pIBaseFilterCam);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Bind to object failed", hr)
		// Add the camera to the filter graph
		hr = g_pGraphBuilder->AddFilter(g_pIBaseFilterCam, L"WebCam");
		if (FAILED(hr))
			BREAK_WITH_ERROR("Add filter failed", hr)
		// Create a SampleGrabber
		hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&g_pIBaseFilterSampleGrabber);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Create sample grabber failed", hr)
		// Configure the Sample Grabber
		ISampleGrabber *pGrabber = NULL;
		hr = g_pIBaseFilterSampleGrabber->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber);
		if (SUCCEEDED(hr)){
			AM_MEDIA_TYPE mt;
			ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
			mt.majortype = MEDIATYPE_Video;
			mt.subtype = MEDIASUBTYPE_RGB24;
			mt.formattype = FORMAT_VideoInfo;
			hr = pGrabber->SetMediaType(&mt);
		}
		if (SUCCEEDED(hr)){
			MySampleGrabberCB* msg = new MySampleGrabberCB();
			hr = pGrabber->SetCallback(msg, 1);
		}
		if (pGrabber != NULL){
			pGrabber->Release();
			pGrabber = NULL;
		}
		if (FAILED(hr))
			BREAK_WITH_ERROR("Sample grabber instantiation failed", hr)

		// Add Sample Grabber to the filter graph
		hr = g_pGraphBuilder->AddFilter(g_pIBaseFilterSampleGrabber, L"SampleGrabber");
		if (FAILED(hr))
			BREAK_WITH_ERROR("Add Sample Grabber to the filter graph failed", hr)
		// Create the NullRender
		hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&g_pIBaseFilterNullRenderer);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Create the NullRender failed", hr)
		// Add the Null Render to the filter graph
		hr = g_pGraphBuilder->AddFilter(g_pIBaseFilterNullRenderer, L"NullRenderer");
		if (FAILED(hr))
			BREAK_WITH_ERROR("Add the Null Render to the filter graph failed", hr)
		// Configure the render stream
		hr = g_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, g_pIBaseFilterCam,
					g_pIBaseFilterSampleGrabber, g_pIBaseFilterNullRenderer);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Configure the render stream failed", hr)
		// Grab the capture width and height
		hr = g_pIBaseFilterSampleGrabber->QueryInterface(IID_ISampleGrabber, (LPVOID*)&pGrabber);
		if (FAILED(hr))
			BREAK_WITH_ERROR("Querying interface failed", hr)
		AM_MEDIA_TYPE mt;
		hr = pGrabber->GetConnectedMediaType(&mt);
		if (FAILED(hr))
			BREAK_WITH_ERROR("GetConnectedMediaType failed", hr)
		VIDEOINFOHEADER *pVih;
		if ((mt.formattype == FORMAT_VideoInfo) && 
			(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
			(mt.pbFormat != NULL) ) {
			pVih = (VIDEOINFOHEADER*)mt.pbFormat;
			nWidth = pVih->bmiHeader.biWidth;
			nHeight = pVih->bmiHeader.biHeight;
		}else{
			BREAK_WITH_ERROR("Wrong format type", hr) // Wrong format
		}
		if (pGrabber != NULL){
			pGrabber->Release();
			pGrabber = NULL;
		}

		//Sync: set up semaphore
		writeEvent = CreateEvent( 
			NULL,               // default security attributes
			FALSE,               // auto-reset event
			FALSE,              // initial state is nonsignaled
			NULL);  // no object name

		// Start the capture
		if (FAILED(hr))
			BREAK_WITH_ERROR("CreateEvent failed", hr)
		hr = g_pMediaControl->Run();
		if (FAILED(hr))
			BREAK_WITH_ERROR("Running capture failed", hr)

		// Cleanup
		if (pMoniker != NULL){
			pMoniker->Release();
			pMoniker = NULL;
		}

		//Now we wait for first frame
		if(WaitForSingleObject (writeEvent, 30000) == WAIT_TIMEOUT)
			BREAK_WITH_ERROR("timeout!", WAIT_TIMEOUT);
		running = true;
		dwResult = GetLastError();
	} while (0);

	packet_transmit_response(dwResult, remote, response);
	return dwResult;
}

// Gets image from running webcam
DWORD request_webcam_get_frame(Remote *remote, Packet *packet){
	Packet *response = packet_create_response(packet);
	DWORD dwResult = ERROR_SUCCESS;
	UINT quality = packet_get_tlv_value_uint(packet, TLV_TYPE_WEBCAM_QUALITY);
	
	do{
		if(!running)
			BREAK_WITH_ERROR("Not running!", ERROR_INVALID_ACCESS)
		//Make bmp
		BITMAPFILEHEADER	bfh;
		bfh.bfType = 0x4d42;	// always "BM"
		bfh.bfSize = sizeof( BITMAPFILEHEADER );
		bfh.bfReserved1 = 0;
		bfh.bfReserved2 = 0;
		bfh.bfOffBits = (DWORD) (sizeof( bfh ) + sizeof(BITMAPINFOHEADER));

		BITMAPINFOHEADER bih;
		bih.biSize = sizeof(BITMAPINFOHEADER);
		bih.biWidth = nWidth;
		bih.biHeight = nHeight;
		bih.biPlanes = 1;
		bih.biBitCount = 24;
		bih.biCompression = BI_RGB;
		bih.biSizeImage = imgsize;
		bih.biXPelsPerMeter = 0;
		bih.biYPelsPerMeter = 0;
		bih.biClrUsed = 0;
		bih.biClrImportant = 0;

		UINT mybmpsize = imgsize + sizeof(bfh) + sizeof(bih);
		if(bmpsize < mybmpsize){
			bmpsize = mybmpsize;
			if(bmpdata != NULL)
				delete [] bmpdata;
			bmpdata = new BYTE[bmpsize];
		}

		// put headers together to make a .bmp in memory
		memcpy(bmpdata, &bfh, sizeof(bfh));
		memcpy(bmpdata + sizeof(bfh), &bih, sizeof(bih));
		memcpy(bmpdata + sizeof(bfh) + sizeof(bih), imgdata, imgsize);

		// Now convert to JPEG
		bmp2jpeg(bmpdata, quality, &jpgarray, &jpgsize );

		//And send
		packet_add_tlv_raw(response, TLV_TYPE_WEBCAM_IMAGE, jpgarray, jpgsize);
	}while(0);
	packet_transmit_response(dwResult, remote, response);

	PBYTE tmparray = jpgarray;
	jpgsize = 0;
	jpgarray = NULL;
	free(tmparray);
	return dwResult;
}

// Stops running webcam
DWORD request_webcam_stop(Remote *remote, Packet *packet){
	Packet *response = packet_create_response(packet);
	DWORD dwResult = ERROR_SUCCESS;

	running = false;
	if (g_pMediaControl != NULL){
		g_pMediaControl->Stop();
		g_pMediaControl->Release();
		g_pMediaControl = NULL;
	}
	if (g_pIBaseFilterNullRenderer != NULL){
		g_pIBaseFilterNullRenderer->Release();
		g_pIBaseFilterNullRenderer = NULL;
	}
	if (g_pIBaseFilterSampleGrabber != NULL){
		g_pIBaseFilterSampleGrabber->Release();
		g_pIBaseFilterSampleGrabber = NULL;
	}
	if (g_pIBaseFilterCam != NULL){
		g_pIBaseFilterCam->Release();
		g_pIBaseFilterCam = NULL;
	}
	if (g_pGraphBuilder != NULL){
		g_pGraphBuilder->Release();
		g_pGraphBuilder = NULL;
	}
	if (g_pCaptureGraphBuilder != NULL){
		g_pCaptureGraphBuilder->Release();
		g_pCaptureGraphBuilder = NULL;
	}

	packet_transmit_response(dwResult, remote, response);
	return dwResult;
}

}
