//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef linux
    #error This sample is only for LINUX!
#endif

//#define HAVE_GL 1

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Overlay_Window.H>
#ifdef HAVE_GL
 #include <FL/Fl_Gl_Window.H>
 #include <FL/gl.h>
#endif
#include <FL/fl_draw.H>
#include <iostream>
#include <mvIMPACT_CPP/mvIMPACT_acquire.h>

#ifdef MALLOC_TRACE
	#include <mcheck.h>
#endif	// MALLOC_TRACE


#define AOI_WIDTH 640
#define AOI_HEIGHT 480

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
using namespace std;
using namespace mvIMPACT::acquire;

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
static bool g_boTerminated = false;
static bool g_boLoadSettings = false;
const unsigned int MAX_REQUESTS = 3;

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
unsigned WaitForInput( int maxwait_sec, int fd )
// returns 0 if timeout, else 1
//-----------------------------------------------------------------------------
{
    fd_set rfds;
    struct timeval tv;

    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

    tv.tv_sec = maxwait_sec ;
    tv.tv_usec = 0;

    return select(fd+1, &rfds, NULL, NULL, &tv);
}

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

#if HAVE_GL
	//-----------------------------------------------------------------------------
	class MyWindow : public Fl_Gl_Window
	//-----------------------------------------------------------------------------
	{
		const Request* pRequest_;
		string msg_;
		virtual void draw()
		{
			if( !valid() )
			{
				valid( 1 );
				glLoadIdentity();
				glViewport( -w(), -h(), 2*w(), 2*h() );
			}
	
			if( pRequest_ )
			{
				
				gl_draw_image((unsigned char *) pRequest_->imageData.read(), 0, 0,
						pRequest_->imageWidth.read(), pRequest_->imageHeight.read(),
						pRequest_->imageBytesPerPixel.read(),
						pRequest_->imageLinePitch.read() );
			}
		}
		virtual void draw_overlay()
		{
			if (!valid())
			{
				valid( 1 );
					glLoadIdentity();
					glViewport( -w(), -h(), 2*w(), 2*h() );
			}
			gl_color( FL_RED );
			gl_draw( msg_.c_str(), 8, h() - 20 );
		}
	public:
		MyWindow( int W, int H ) : Fl_Gl_Window(W, H), pRequest_(0), msg_("") {}
		void NewRequest( const Request* pRequest) { pRequest_ = pRequest; }
		void setOverlayString( const string& msg ) { msg_ = msg; redraw_overlay(); }
	};

	//-----------------------------------------------------------------------------
	MyWindow* createWindow( int width, int height )
	//-----------------------------------------------------------------------------
	{
		MyWindow* pWindow = new MyWindow(width, height);
		if( pWindow->can_do() == 0 )
		{
			cout << "OpenGL not possible!!" << endl;
		}
		else
		{
			cout << "Using OpenGL." << endl;
			if( pWindow->can_do_overlay() == 0 )
			{
				cout << "OpenGL Overlays not possible!!" << endl;
			}
			else
			{
				cout << "Using OpenGL Overlays." << endl;
			}
	
			//window->clear_border();
			gl_font( FL_TIMES, 12 );
			Fl::gl_visual( FL_RGB );
			pWindow->end();
			pWindow->show();
		}
		return pWindow;
	}
#else
	//-----------------------------------------------------------------------------
	class MyWindow : public Fl_Overlay_Window
	//-----------------------------------------------------------------------------
	{
		const Request* pRequest_;
		string msg_;
		virtual void draw()
		{
			if( pRequest_ )
			{
				fl_draw_image((unsigned char *) pRequest_->imageData.read(), 0, 0,
						pRequest_->imageWidth.read(), pRequest_->imageHeight.read(),
						pRequest_->imageBytesPerPixel.read(),
						pRequest_->imageLinePitch.read() );
			}
		}
		virtual void draw_overlay()
		{
			fl_color( FL_RED );
			fl_draw( msg_.c_str(), 8, h() - 20 );
		}
	public:
		MyWindow( int W, int H ) : Fl_Overlay_Window( W, H ), pRequest_(0), msg_("") {}
		void NewRequest( const Request* pRequest) { pRequest_ = pRequest; }
		void setOverlayString( const string& msg ) { msg_ = msg; redraw_overlay(); }
	};

	//-----------------------------------------------------------------------------
	MyWindow* createWindow( int width, int height )
	//-----------------------------------------------------------------------------
	{
		cout << "Not using OpenGL." << endl;
		MyWindow* pWindow = new MyWindow(width, height);
		//pWindow->clear_border();
		fl_font( FL_TIMES, 12);
		pWindow->end();
		Fl::visual( FL_RGB|FL_DOUBLE|FL_INDEX );
		return pWindow;
	}

#endif

//-----------------------------------------------------------------------------
void windowCallback( Fl_Widget*, void* )
//-----------------------------------------------------------------------------
{
	printf( "Window was closed\n" );
	g_boTerminated = true;
}

//-----------------------------------------------------------------------------
unsigned int liveLoop( Device* pDev )
//-----------------------------------------------------------------------------
{
	unsigned int cnt = 0;

	cout << " == " << __FUNCTION__ << " - Initialising the device. This might take some time...." << endl;
	try
	{
		pDev->open();
	}
	catch( const ImpactAcquireException& e )
	{
		// this e.g. might happen if the same device is already opened in another process...
		cout << "*** " << __FUNCTION__ << " - An error occurred while opening the device " << pDev->serial.read()
			 << "(error code: " << e.getErrorCode() << "). Press RETURN to end the application..." << endl;
		getchar();
		return 0;
	}

	cout << " == " << __FUNCTION__ << " - establish access to the statistic properties...." << endl;
	// establish access to the statistic properties
	Statistics statistics( pDev );
	cout << " == " << __FUNCTION__ << " - create an interface to the device found...." << endl;
	// create an interface to the device found
	FunctionInterface fi( pDev );

	string sFamily = pDev->family.read();
	if( sFamily == "mvBlueCOUGAR" ) {
		CameraSettingsBlueCOUGAR cBCCamSet( pDev );
		cBCCamSet.frameRate_Hz.write( 10 );
	}
	if(( sFamily != "mvVirtualDevice" ) && ( sFamily != "mvV4L" )) {
		InfoBlueDevice ibD( pDev ); // not implemented for VirtualDevice : an exception is thrown in ComponentLocatorBase::bindSearchBase
		if( ibD.sensorColorMode.read() == iscmBayer ) {
			printf( "This is a bayer sensor; colors will be wrong!\n");
		}
	}
	if( sFamily != "mvV4L" ) {
		CameraSettingsBlueDevice cCamSet( pDev );
		cCamSet.aoiWidth.write( AOI_WIDTH );
		cCamSet.aoiHeight.write( AOI_HEIGHT );
	}

	if( g_boLoadSettings )
	{
		try
		{
			cout << " == " << __FUNCTION__ << " - loadSetting...." << endl;
			fi.loadSetting( pDev->serial.read() + ".xml" );
		}
		catch( const ImpactAcquireException& e )
		{
			cout << "***" << __FUNCTION__ << " " << e.getErrorString().c_str() <<"("<< e.getErrorCode() <<")"<< endl;
		}
	}

	// make sure enough requests are available:
	SystemSettings ss( pDev );
	ss.requestCount.write( MAX_REQUESTS );
	// now we can work with at least MAX_REQUESTS requests

	// prefill the capture queue. There can be more then 1 queue, but for this sample
	// we will work with the default capture queue
	for( unsigned int i = 0; i < MAX_REQUESTS -1; i++ )
	{
		try
		{
			fi.imageRequestSingle( );
		}
		catch( const ImpactAcquireException& e )
		{
			cout << "***" << __FUNCTION__ << " " << e.getErrorString().c_str() <<"("<< e.getErrorCode() <<")"<< endl;
		}
	}

	cout << "Press <<ENTER>> to end the application!!" << endl;
	MyWindow* pWindow = createWindow( AOI_WIDTH, AOI_HEIGHT );
	pWindow->show();
	pWindow->callback(windowCallback);
	cout << "********* GO ***************" << endl;
	// run thread loop
	const Request* pRequest = 0;
	const unsigned int timeout_ms = 2000;
	int requestNr = -1;
	while( !g_boTerminated )
	{
		// send a new image request into the capture queue
		try
		{
			fi.imageRequestSingle( );
		}
		catch( const ImpactAcquireException& e )
		{
			cout << "***" << __FUNCTION__ << " " << e.getErrorString().c_str() <<"("<< e.getErrorCode() <<")"<< endl;
		}

		// 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 10th image
				if( cnt%20 == 0 )
				{
					ostringstream out;
					out << pDev->serial.read() << ": " << statistics.framesPerSecond;
					pWindow->setOverlayString( out.str() );

					cout << cnt << ": " << pDev->serial.read()
						<< ": " << statistics.framesPerSecond << ", "
						<< statistics.errorCount << ", " << statistics.captureTime_s << endl;
				}

				// everything went well. Display the result
				pWindow->NewRequest( pRequest );
				pWindow->redraw();
				Fl::check();

			}
			else
			{
				cout << "*** Error: " << pRequest->requestResult.read() << endl;
			}

			// this image has been displayed thus the buffer is no longer needed...
			fi.imageRequestUnlock( requestNr );
		}
		else
		{
			// 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 "
				<< pDev->serial.read() << ")"
				<< ", timeout value too small?" << endl;
		}

		if( WaitForInput( 0, STDOUT_FILENO ) )
		{
			g_boTerminated = true; // break by STDIN
		}
		if( g_boTerminated )
		{	// finishing...
			do
			{	// wait for outstanding results from the default capture queue and discrad them all
				requestNr = fi.imageRequestWaitFor( timeout_ms );
			} while(  fi.isRequestNrValid( requestNr ) );
		}
	}

	cout << " == " << __FUNCTION__ << " - free resources...." << endl;
	fi.imageRequestReset( 0, 0 );

	cout << " == " << __FUNCTION__ << " - close device...." << endl;
	pDev->close( );
	return 0;
}

//-----------------------------------------------------------------------------
int main( int argc, char* argv[] )
//-----------------------------------------------------------------------------
{
	const char * DevSerial = argc > 1 ? argv[1] : "VD*";

#ifdef MALLOC_TRACE
	mtrace();
#endif	// MALLOC_TRACE

	if( argc > 2 )
		g_boLoadSettings = atoi( argv[2] );

	cout << "Device serial: "<< DevSerial << endl;
	unsigned int uiDevCount = 0;
	DeviceManager devMgr;
	if(( uiDevCount = devMgr.deviceCount()) == 0 )
	{
		cout << "No MATRIX VISION device found! Unable to continue!" << endl;
		return 0;
	}

	cout << "Have found " << uiDevCount << " devices on this platform!" << endl;

	for( unsigned i = 0; i < uiDevCount; i++ )
	{
        Device* pDevTmp = devMgr.getDevice( i );
		cout << " " << i << " Serial: " << pDevTmp->serial.read() << "(" << pDevTmp->family.read() << "," << pDevTmp->product.read() << ")" << endl;
	}

	cout << "Initialising the device: "<< DevSerial << ". This might take some time..." << endl;
	// create an interface to the first MATRIX VISION device with the serial number pDevSerial
	Device* pDev = devMgr.getDeviceBySerial( DevSerial );

	if( pDev )
	{
		// initialise display window
		// IMPORTANT: It's NOT safe to create multiple display windows in multiple threads!!!
		string windowTitle( "mvIMPACT_acquire sample, Device " + pDev->serial.read() );
		cout << "<<" << windowTitle << ">>" << endl;

		// start the execution of the 'live' loop.
		liveLoop( pDev );
		cout << " -- end application...." << endl;
	}
	else
		cout << "The device: "<< DevSerial << " does not exist." << endl;

	return 0;
}
