/*

Na zacatku tohoto souboru je nastaveni chovani bloku. Pote jsou zde funkce
nezvytne k vytvoreni instance COMu, a funkce, ktera vraci ukazatele na
rozhrani bloku. Prozatim se predpokladaji pouze 2 pridavna rozhrani k tomuto
objektu. 

-- IBlockConfig --
je urcene ke konfiguraci bloku. Je pouzito
v pripade, kdyz uzivatel klikne pravym tlacitkem mysi na blok a vybere
volbu "properties...". Melo by se otevrit okno s nastavenim, ktere musi
byt implementovano

-- IBlockAutoTrigger --
slouzi k automatickemu spousteni procesu. Vyuzije se napriklad pri realtime
zpracovani zvuku a videa, kdy potrebujeme pravidelne zpracovavat useky dat

*/


#include "stdafx.h"
#include "imp_block.h"
#include "imp_config.h"
#include "imp_autotrigger.h"
#include <deque>



// jmeno, popis a cesta k bloku
const LPWSTR g_pName = L"Load camera";
const LPWSTR g_pDesc = L"Can read image or video/audio stream from camera";
const LPWSTR g_pPath = L"General";
// jestli ma blok rohrani, odkomentujte prislusny radek
#define __BLOCKHAVECONFIGWINDOW
#define __BLOCKHAVEAUTOTRIGGER
// pocty vstupnich a vystupnich portu
const DWORD g_dwInPortCount = 1;
const DWORD g_dwOutPortCount = 0;
// nazvy vstupnich a vystupnich portu
const WCHAR *g_pInputNameArray[] = {L"<-TRG+"};
const WCHAR *g_pOutputNameArray[] = {NULL};

// Tyto funkce nemenit !!!
// funkce volana klientskym programem k vytvoreni instance tridy

HRESULT CreateBlockInstance(REFIID riid, void **ppv)
{
	return CImpBlock::CreateInstance(riid, ppv);
}
// staticka funkce, ktera vytvari instanci tridy
HRESULT CImpBlock::CreateInstance(const IID &riid, void **ppv)
{
	CImpBlock* pObj; 
	pObj = new CImpBlock();
	if (pObj == NULL) 
		return E_OUTOFMEMORY; 
	HRESULT hr; 
	if ( FAILED(hr = pObj->QueryInterface(riid, (void**)ppv))) 
		delete pObj ; 
	return hr; 
}
// funkce, ktera vraci adresu rozhrani
void* CImpBlock::GetInterface(REFIID riid)
{
#ifdef __BLOCKHAVECONFIGWINDOW
	if ( IID_IBlockConfig == riid )
		return m_pxConfig;
#endif
#ifdef __BLOCKHAVEAUTOTRIGGER
	if ( IID_IBlockAutoTrigger == riid )
		return m_pxAutoTrigger;
#endif
	return NULL;
}
// konstruktor
CImpBlock::CImpBlock() : m_pxConfig(NULL), m_pxAutoTrigger(NULL)
{
#ifdef __BLOCKHAVECONFIGWINDOW
	m_pxConfig = new CImpBlockConfig(this);
#endif
#ifdef __BLOCKHAVEAUTOTRIGGER
	m_pxAutoTrigger = new CImpBlockAutoTrigger(this);
#endif
	m_dwInPortCount = g_dwInPortCount;
	m_dwOutPortCount = g_dwOutPortCount;
	m_pName = g_pName;
	m_pDesc = g_pDesc;
	m_pPath = g_pPath;
	m_pInputNameArray = g_pInputNameArray;
	m_pOutputNameArray = g_pOutputNameArray;

	// inicializace promennych // IMPLEMENTOVAT

	m_dwRevSignalIdVideo = 1;
	m_dwRevSignalIdAudio = 2;
	
	m_nCameraIndex = 0;
	m_pBitmap = NULL;
	m_pRevDataBitmap = NULL;
	m_pRevDataWave = NULL;
	m_dwStatus = CAMSTATUS_STOPPED;
}
// destruktor
CImpBlock::~CImpBlock()
{
	if ( m_pxAutoTrigger )
		delete ( m_pxAutoTrigger );
	if ( m_pxConfig )
		delete ( m_pxConfig );
	if ( m_pBitmap )
		free(m_pBitmap);
}

///////////////////////////////
// TYTO FUNKCE IMPLEMENTOVAT //
///////////////////////////////

STDMETHODIMP CImpBlock::InitInstance()
{
	WCHAR szDeviceName[80];
	WCHAR szDeviceVersion[80];
	InitializeCriticalSection(&m_csCam);
	UINT wIndex = 0;

	while (capGetDriverDescription(
				wIndex, 
				szDeviceName, 
				sizeof (szDeviceName), 
				szDeviceVersion, 
				sizeof (szDeviceVersion)
			)) 
	{
		wIndex++;
		CString str;
		str.Format(L"%d: %s [%s]", wIndex, szDeviceName, szDeviceVersion);
		m_dqDriverNames.push_back(str);
	}


	m_hWndC = capCreateCaptureWindow(L"Capture Window", 0, 276, 40, 160, 120, NULL, 0);
	capSetUserData(m_hWndC, this);
	if ( m_nCameraIndex )
	{
		capDriverConnect(m_hWndC, wIndex - 1);
		SetDriverParams();
		EnterCriticalSection(&m_csCam);
		capGetVideoFormat(m_hWndC, &m_bitmapInfo, sizeof(BITMAPINFO));
		LeaveCriticalSection(&m_csCam);
	}



	

	m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	m_hEventQuit = CreateEvent(NULL, TRUE, FALSE, NULL);

	CBlock::InitInstance();



	if ( m_dwStatus == CAMSTATUS_RUNNING )
		capCaptureSequenceNoFile(m_hWndC);

	return S_OK;
}

STDMETHODIMP CImpBlock::ExitInstance()
{
	EnterCriticalSection(&m_csCam);
	capCaptureAbort(m_hWndC);
	LeaveCriticalSection(&m_csCam);
	capDriverDisconnect(m_hWndC);

	DestroyWindow(m_hWndC);

	CBlock::ExitInstance();
	
	CloseHandle(m_hEvent);
	CloseHandle(m_hEventQuit);
	DeleteCriticalSection(&m_csCam);
	return S_OK;
}

STDMETHODIMP CImpBlock::ProcessRev(const DWORD dwProcessId, REVDATAMAP &rRevDataMap)
{
	EnterCriticalSection(&m_csCam);
	REVDATA rd;
	rd.dwDataLen = 0;
	rd.pData = NULL;
	if ( m_pBitmap )
	{
		m_pRevDataBitmap = malloc(m_dwBitmapLen);
		memcpy(m_pRevDataBitmap, m_pBitmap, m_dwBitmapLen);
		rd.dwDataLen = m_dwBitmapLen;
	}
	LeaveCriticalSection(&m_csCam);
	rd.pData = m_pRevDataBitmap;
	rRevDataMap.insert(std::pair<DWORD, REVDATA>(m_dwRevSignalIdVideo, rd));
	return S_OK;
}

STDMETHODIMP CImpBlock::ClearRevMap(REVDATAMAP &rRevDataMapClean)
{
	REVDATAMAP::iterator it;
	it = rRevDataMapClean.find(m_dwRevSignalIdVideo);
	ASSERT (it != rRevDataMapClean.end());
	rRevDataMapClean.erase(it);
	return S_OK;
}

STDMETHODIMP CImpBlock::ProcessData(const LPBLOCKDATA pInputData)
{
	EnterCriticalSection(&m_csCam);
	if ( m_pRevDataBitmap )
		free(m_pRevDataBitmap);
	m_pRevDataBitmap = NULL;
	LeaveCriticalSection(&m_csCam);
	return S_OK;
}

STDMETHODIMP_(void) CImpBlock::Serialize(CArchive &ar)
{
	if ( ar.IsStoring() )
	{
		ar << m_dwRevSignalIdAudio << m_dwRevSignalIdVideo << m_nCameraIndex << m_dwStatus;
	}
	else
	{
		ar >> m_dwRevSignalIdAudio >> m_dwRevSignalIdVideo >> m_nCameraIndex >> m_dwStatus;
	}
}

BOOL CImpBlock::ConnectCamera(BOOL bDisconnect)
{
	EnterCriticalSection(&m_cs);
	if ( m_dwStatus == CAMSTATUS_RUNNING )
		capCaptureStop(m_hWndC);
	if ( bDisconnect )
		capDriverDisconnect(m_hWndC);
	if ( m_nCameraIndex )
	{
		capDriverConnect(m_hWndC, m_nCameraIndex - 1);
		SetDriverParams();
		if ( m_dwStatus == CAMSTATUS_RUNNING )
			capCaptureSequenceNoFile(m_hWndC);
	}
	LeaveCriticalSection(&m_cs);
	return TRUE;
}

BOOL CImpBlock::SetDriverParams()
{
	CAPTUREPARMS cp;
	capCaptureGetSetup(m_hWndC, &cp, sizeof(cp));
	cp.fYield = TRUE;
	cp.wPercentDropForError = 100;
	cp.fAbortLeftMouse = FALSE;
	cp.fAbortRightMouse = FALSE;
	capCaptureSetSetup(m_hWndC, &cp, sizeof(cp));

	capSetCallbackOnVideoStream(m_hWndC, fpProc);
	capSetCallbackOnStatus(m_hWndC, fpStatus);
	capSetCallbackOnError(m_hWndC, fpError);
	return TRUE;
}

LRESULT CImpBlock::fpProc(HWND hWnd, LPVIDEOHDR lpVHdr)
{
	CImpBlock* pThis = (CImpBlock*)capGetUserData(hWnd);

	EnterCriticalSection(&pThis->m_csCam);
	if ( pThis->m_pBitmap )
		free(pThis->m_pBitmap);

	pThis->m_pBitmap = malloc(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) + lpVHdr->dwBytesUsed);

	BITMAPFILEHEADER bfh;
	ZeroMemory(&bfh, sizeof(bfh));
	bfh.bfType = 0x4D42;
	bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) + lpVHdr->dwBytesUsed;
	bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO);
	memcpy(pThis->m_pBitmap, &bfh, sizeof(bfh));

	memcpy(&((BYTE*)pThis->m_pBitmap)[sizeof(BITMAPFILEHEADER)], &pThis->m_bitmapInfo, sizeof(BITMAPINFO));
	memcpy(&((BYTE*)pThis->m_pBitmap)[sizeof(BITMAPINFO) + sizeof(BITMAPFILEHEADER)], lpVHdr->lpData, lpVHdr->dwBytesUsed);
	pThis->m_dwBitmapLen = lpVHdr->dwBytesUsed + sizeof(BITMAPINFO) + sizeof(BITMAPFILEHEADER);
	LeaveCriticalSection(&pThis->m_csCam);

	SetEvent(pThis->m_hEvent);
	return 0;
}

LRESULT CImpBlock::fpStatus(HWND hWnd, int nID, LPCSTR lpsz)
{
	CImpBlock* pThis = (CImpBlock*)capGetUserData(hWnd);
	EnterCriticalSection(&pThis->m_csCam);

	switch ( nID )
	{
	case IDS_CAP_BEGIN:
		pThis->m_dwStatus = CAMSTATUS_RUNNING;
		break;
	case IDS_CAP_END:
		pThis->m_dwStatus = CAMSTATUS_STOPPED;
		break;
	}
	LeaveCriticalSection(&pThis->m_csCam);
	if ( pThis->m_pxConfig )
	{
		((CImpBlockConfig*)pThis->m_pxConfig)->RefreshButtons_Safe();
	}
	return 0;
}

LRESULT CImpBlock::fpError(HWND hWnd, int nID, LPCSTR lpsz)
{
	return 0;
}