// ContinuousCaptureAllDevicesLIN.cpp
// Capture continuously from all found MATRIX VISION devices
//
// *** LINUX VERSION ***
//
// No display. Uses Linux threads.
//
#ifndef linux
  #error This file is only for Linux
#endif

#include <unistd.h>
#include <conio.h>
#include <iostream>
#include <errno.h>

#include <mvIMPACT_CPP/mvIMPACT_acquire.h>

using namespace std;
using namespace mvIMPACT::acquire;

#define PRESS_A_KEY				\
		getchar();
typedef void* HDISP;

const unsigned int MAX_REQUESTS = 3;

//-----------------------------------------------------------------------------
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;
}

//-----------------------------------------------------------------------------
static unsigned int thread_func( void* pData )
{
	ThreadParameter* pThreadParameter = (ThreadParameter*)pData;
	unsigned int cnt = 0;
	
	try
	{
		pThreadParameter->device()->open();
	}
	catch( ImpactAcquireException& e )
	{
		// 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;
		PRESS_A_KEY
		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; i++ )
	{
		fi.imageRequestSingle();
	}

	// run thread loop
	const Request* pRequest = 0;
	const unsigned int timeout_ms = 5000 ;
	int requestNr = -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 )
				{
					cout << "Info from " << pThreadParameter->device()->serial.read() 
						<< ": " << statistics.framesPerSecond << ", "
						<< statistics.errorCount << ", " << statistics.captureTime_s 
						<< ", " << statistics.frameCount << endl;
				}
			}
			else
			{
				cout << "Error: " << pRequest->getResult().result << endl;
			}
			// this image has been displayed thus the buffer is no longer needed...
			fi.imageRequestUnlock( requestNr );
			// send a new image request into the capture queue
			fi.imageRequestSingle();
		}
		else
		{
			cout << "Error: There has been no request in the queue for device " << pThreadParameter->device()->serial.read() << "." << endl;
		}
	}

	// empty request queue
	fi.imageRequestReset( 0, 0 );
	return 0;
}


//-----------------------------------------------------------------------------
static void* liveThread( void* pData )
{
	static unsigned int ret = thread_func( pData );
	cout << ">>>> THREAD ENDING..!" << endl;
	pthread_exit (&ret);
	return  NULL;
}

//-----------------------------------------------------------------------------
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;
	}
	pthread_t* pHandles = new pthread_t[devCnt];
	pthread_attr_t* pAttrs = new pthread_attr_t[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++ )
	{
		HDISP hDisp = NULL;
		threadParams.push_back( new ThreadParameter( devMgr[i], hDisp ) );
		cout << *(devMgr[i]) << endl;
	}

	// start live threads
	for( unsigned int j=0; j<devCnt; j++ )
	{
		pthread_attr_init( &pAttrs[j] );
//		you can set the stack size like this: pthread_attr_setstacksize (&pAttrs[j], 1024*1024);
		pthread_create( &pHandles[j], &pAttrs[j], liveThread, (void *) threadParams[j] );
	}
	
	// now all threads will start running...
	cout << "Press return to end the acquisition( the initialisation of the devices might take some time )" << endl;
	PRESS_A_KEY

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

	// wait until each live thread has terminated.
	for( unsigned int j=0; j<devCnt; j++ )
	{
		cout << "Waiting for thread " << j << " to terminate." << endl;
		pthread_join (pHandles[j], NULL);
	}
	cout << "All capture threads terminated." << endl;

	// free resources
	for( unsigned int l=0; l<vSize; l++ )
	{	
		delete threadParams[l];
		cout << "thread parameter " << l << " removed." << endl;
	}
	delete [] pHandles;
	cout << "All thread handles removed." << endl;

	delete [] pAttrs;
	cout << "All thread atributes removed." << endl;
	return 0;
}
