#include <windows.h>
#include <process.h>
#include <conio.h>
#include <iostream>
#include <mvDisplay/Include/mvDisplayWindow.h>
#include <mvIMPACT_CPP/mvIMPACT_acquire.h>

using namespace std;
using namespace mvIMPACT::acquire;

const unsigned int MAX_REQUESTS = 4;

//-----------------------------------------------------------------------------
/// to get thread safe access to the std out and the display module
class CriticalSection
//-----------------------------------------------------------------------------
{
	CRITICAL_SECTION m_criticalSection;
public:
	CriticalSection() { InitializeCriticalSection( &m_criticalSection ); }
	~CriticalSection() { DeleteCriticalSection( &m_criticalSection ); }
	void lock( void ) { EnterCriticalSection( &m_criticalSection ); }
	void unlock( void ) { LeaveCriticalSection( &m_criticalSection ); }
} g_critSect;


//-----------------------------------------------------------------------------
class ThreadParameter
//-----------------------------------------------------------------------------
{
	Device*			m_pDev;
	HDISP			m_hDisp;
	volatile bool	m_boTerminateThread;
public:
	ThreadParameter( Device* pDev, HDISP hDisp ) : m_pDev(pDev), m_hDisp(hDisp), m_boTerminateThread(false) {}
	Device*	device( void ) const { return m_pDev; }
	HDISP	hDisp( void ) const { return m_hDisp; }
	bool	terminated( void ) const { return m_boTerminateThread; }
	void	terminateThread( void ) { m_boTerminateThread = true; }
};

//-----------------------------------------------------------------------------
ostream& operator<<( std::ostream& out, const Property& prop )
//-----------------------------------------------------------------------------
{
	out << prop.name() << ": " << prop.readS();
	return out;
}

//-----------------------------------------------------------------------------
unsigned int __stdcall liveThread( void* pData )
//-----------------------------------------------------------------------------
{
	ThreadParameter* pThreadParameter = (ThreadParameter*)pData;
	TDisp* pDisp = mvGetDisplayStructure( pThreadParameter->hDisp() );

	unsigned int cnt = 0;
	
	try
	{
		pThreadParameter->device()->open();
	}
	catch( ImpactAcquireException& e )
	{
		g_critSect.lock();
		// this e.g. might happen if the same device is already opened in another process...
		cout << "An error occurred while opening the device " << pThreadParameter->device()->serial.read() 
		     << "(error code: " << e.errCode() << "). Terminating thread." << endl 
		     << "Press any key to end the application..."
		     << endl;
		g_critSect.unlock();
		_getch();
		return 0;
	}

	// establish access to the statistic properties
	Statistics statistics( pThreadParameter->device() );
	// create an interface to the device found
	FunctionInterface fi( pThreadParameter->device() );

	// make sure enough requests are avaiable:
	fi.ensureRequests(MAX_REQUESTS);
	// now we can work with at least MAX_REQUESTS requests

	// prefill the capture queue. There can be more then 1 queue for some device, but for this sample
	// we will work with the default capture queue. If a device supports more then one capture or result
	// queue, this will be stated in the manual. If nothing is set about it, the device supports one
	// queue only.
	for( unsigned int i=0; i<MAX_REQUESTS-1; i++ )
	{
		fi.imageRequestSingle();
	}

	// run thread loop
	const Request* pRequest = 0;
	const unsigned int timeout_ms = 500;
	int requestNr = -1;
	// we always have to keep at least 2 images as the display module might want to repaint the image, thus we
	// can free it unless we have a assigned the display to a new buffer.
	int lastRequestNr = -1;
	while( !pThreadParameter->terminated() )
	{
		// wait for results from the default capture queue
		requestNr = fi.imageRequestWaitFor( timeout_ms );
		if( fi.isRequestNrValid( requestNr ) )
		{
			pRequest = fi.getRequest(requestNr);
			if( fi.isRequestOK( pRequest ) )
			{
				++cnt;
				// here we can display some statistical information every 100th image
				if( cnt%100 == 0 )
				{
					g_critSect.lock();
					cout << "Info from " << pThreadParameter->device()->serial.read() 
					     << ": " << statistics.framesPerSecond << ", "
					     << statistics.errorCount << ", " << statistics.captureTime_s 
					     << ", " << statistics.frameCount << endl;
					g_critSect.unlock();
				}
				const ImageBuffer& ib = pRequest->getImageBuffer();
				g_critSect.lock();
				mvDispSetImage( pDisp, ib.vpData, ib.iWidth, ib.iHeight, ib.pChannels[0].iPixelPitch*8, ib.pChannels[0].iLinePitch );
				mvDispUpdate( pDisp );
				g_critSect.unlock();
			}
			else
			{
				g_critSect.lock();
				cout << "Error: " << pRequest->getParamS( irpResult ) << endl;
				g_critSect.unlock();
			}
			if( lastRequestNr >= 0 )
			{
				// this image has been displayed thus the buffer is no longer needed...
				fi.imageRequestUnlock( lastRequestNr );
			}
			lastRequestNr = requestNr;
			// send a new image request into the capture queue
			fi.imageRequestSingle();
		}
		else
		{
			g_critSect.lock();
			// If the error code is -2119(DEV_WAIT_FOR_REQUEST_FAILED), the documentation will provide 
			// additional information under TDMR_ERROR in the interface reference (
			cout << "imageRequestWaitFor failed (" << requestNr << ", device " << pThreadParameter->device()->serial.read() << ")"
			     << ", timeout value too small?" << endl;
			g_critSect.unlock();
		}
	}

	// stop the display from showing freed memory
	mvDispSetImage( pDisp, 0, 0, 0, 0, 0 );
	// free the last potential locked request
	if( fi.isRequestNrValid( requestNr ) )
	{
		fi.imageRequestUnlock( requestNr );
	}
	// clear the request queue
	fi.imageRequestReset( 0, 0 );
	return 0;
}

//-----------------------------------------------------------------------------
int main(int argc, char* argv[])
//-----------------------------------------------------------------------------
{
	DeviceManager devMgr;
	unsigned int devCnt = devMgr.deviceCount();
	if( devCnt == 0 )
	{
		cout << "No MATRIX VISION device found! Unable to continue!" << endl;
		return 0;
	}

	HANDLE* pHandles = new HANDLE[devCnt];
	// store all device infos in a vector
	// and start the execution of a 'live' thread for each device.
	std::vector<ThreadParameter*> threadParams;
	for( unsigned int i=0; i<devCnt; i++ )
	{
		// initialise display window
		// IMPORTANT: It's NOT save to create multiple display windows in multiple threads!!!
		// Therefore this must be done before starting the threads
		string windowtitle("mvIMPACT_acquire sample, Device " + devMgr[i]->serial.read() );
		HDISP hDisp = mvInitDisplayWindow( windowtitle.c_str() );
		mvShowDisplayWindow( hDisp );
		// store thread parameter for this device in the vector
		threadParams.push_back( new ThreadParameter( devMgr[i], hDisp ) );
		cout << *(devMgr[i]) << endl;
	}

	// start live threads
	for( unsigned int j=0; j<devCnt; j++ )
	{
		unsigned int dwThreadID;
		pHandles[j] = (HANDLE)_beginthreadex( 0, 0, liveThread, (LPVOID)threadParams[j], 0, &dwThreadID );
	}
	
	// now all threads will start running...
    g_critSect.lock();
	cout << "Press any key to end the acquisition( the initialisation of the devices might take some time )" << endl;
    g_critSect.unlock();
	_getch();

	// stop all threads again
    g_critSect.lock();
	cout << "Terminating live threads..." << endl;
    g_critSect.unlock();
	size_t vSize = threadParams.size();
	for( unsigned int k=0; k<vSize; k++ )
	{
		threadParams[k]->terminateThread();
	}

	// wait until each live thread has terminated.
	WaitForMultipleObjects( devCnt, pHandles, true, INFINITE );

	// free resources
	for( unsigned int l=0; l<vSize; l++ )
	{
		mvDestroyImageWindow( threadParams[l]->hDisp() );
		delete threadParams[l];
	}
	delete [] pHandles;

	return 0;
}
