#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <Util/XPlatform.h>
#include <Util/Time.h>
#include <Util/CmdLineParser.h>
#include <Util/Half/half.h>
#include <LaplacianMatrix/SocketedMultigrid/MultigridServer.h>
#ifndef _WIN32
#include <sys/resource.h>
#endif // !_WIN32

#define DEFAULT_BUFLEN 1024
#define USE_SERVER 0	// [Question] Why does the server no longer work?

typedef uint16_t LType;

cmdLineInt Port( "port" , 0 );
cmdLineString Address( "address" ) , Prefix( "prefix" ) , TempDir( "temp" );
cmdLineString Storage( "storage" );
cmdLineReadable HDR( "hdr" );

#include "LaplacianMatrix/SocketedMultigrid/MultigridProcess.h"
#include "LaplacianMatrix/SocketedMultigrid/MultigridServer.h"
#include "LaplacianMatrix/SocketedMultigrid/SocketData.h"
#include "Util/ImageStream.h"
cmdLineString LowPixels( "lowPixels" ) , Pixels( "pixels" ) , Labels( "labels" ) , Out( "out" );
cmdLineInt Threads( "threads" , 1 ) , Priority( "priority" , 0 );
cmdLineReadable InCore( "inCore" );
cmdLineInt Index( "index" , 0 );
cmdLineReadable* params[]=
{
	&HDR ,
	&LowPixels , &Pixels , &Labels , &Out , &Port , &Address , &Prefix , &Index , &TempDir , &Storage , &Priority ,
	&Threads , &InCore ,
};
void ShowUsage( char* ex )
{
	printf( "Usage %s:\n",ex) , fflush( stdout );
	printf( "\t --%s <low frequency image pixels>\n" , LowPixels.name ) , fflush( stdout );
	printf( "\t --%s <composited image pixels>\n" , Pixels.name ) , fflush( stdout );
	printf( "\t --%s <image labels>\n" , Labels.name ) , fflush( stdout );
	printf( "\t --%s <output image>\n" , Out.name ) , fflush( stdout );
	printf( "\t --%s <server connection address>\n" , Address.name ) , fflush( stdout );
	printf( "\t --%s <server port>\n" , Port.name ) , fflush( stdout );
	printf( "\t[--%s <preferred address prefix>]\n" , Prefix.name ) , fflush( stdout );
	printf( "\t[--%s <client index>=%d]\n" , Index.name , Index.value ) , fflush( stdout );
	printf( "\t[--%s <scratch directory>]\n",TempDir.name ) , fflush( stdout );
	printf( "\t[--%s <temporary storage type>]\n" , Storage.name ) , fflush( stdout );
	printf( "\t[--%s <number of computation threads>=%d]\n" , Threads.name , Threads.value ) , fflush( stdout );
	printf( "\t[--%s]\n" , InCore.name ) , fflush( stdout );
	printf( "\t[--%s <thread priority>=%d]\n" , Priority.name , Priority.value ) , fflush( stdout );
}

template< int PixelChannels , class StorageType >
int _Execute( SocketedSuperMultigridClient< StorageType , half , LType >& client , bool getGradientAverage , int w1 , int h1 , MultiStreamIOServer* ioServerPtr )
{
	int w2 , h2;
	std::vector< std::pair< LabelData< LType , 3 > , GradientAverage< PixelChannels > > > averageMap;
	if( getGradientAverage )
	{
		printf( "Computing gradient average(s)\n" );
		StreamingGrid *lowPixels = NULL , *pixels = NULL , *_lowPixels , *_pixels = NULL , *labels = NULL;
		if( LowPixels.set )
		{
			lowPixels = GetReadStream< half >( LowPixels.value , w2 , h2 , false , client.gammaCorrection() , false , ioServerPtr , Threads.value , Priority.value );
			if( PixelChannels==1 ) _lowPixels = lowPixels , lowPixels = new RGrayStream< half , 3 >( _lowPixels , w2 , h2 );
		}
		if( Labels.set ) labels = GetReadStream< LType >( Labels.value , w2 , h2 , false , false , false , ioServerPtr , Threads.value , Priority.value );

		SetGradientAverageMap< PixelChannels , 3 , half , LType >( pixels , labels , averageMap , client.periodicType()==SPHERICAL_PERIODIC , client.showProgress() );

		if( lowPixels ) delete lowPixels;
		if( pixels ) delete pixels;
		if( labels ) delete labels;
		if( _lowPixels ) delete _lowPixels;
		if( _pixels ) delete _pixels;
	}
	StreamingGrid *lowPixels = NULL , *_lowPixels = NULL , *pixels = NULL , *_pixels = NULL , *labels = NULL , *out = NULL , *_out = NULL;
	if( !debugFlags.noInput ) pixels = GetReadStream< half >( Pixels.value , w1 , h1 , false , client.gammaCorrection() , false , ioServerPtr , Threads.value , Priority.value );
	else					  pixels = new NULLStreamingGrid( w1 * 3  * sizeof( half ) , h1 );
	if( PixelChannels==1 ) _pixels = pixels , pixels = new RGrayStream< half , 3 >( _pixels , w1 , h1 );
	if( LowPixels.set )
	{
		if( !debugFlags.noInput ) lowPixels = GetReadStream< half >( LowPixels.value , w2 , h2 , false , client.gammaCorrection() , false , ioServerPtr , Threads.value , Priority.value );
		else
		{
			GetReadSize< half >( LowPixels.value , w2 , h2 );
			lowPixels = new NULLStreamingGrid( sizeof( half ) * w2 * 3 , h2 );
		}
		if( w1!=w2 || h1!=h2 )
		{
			fprintf(stderr,"Image and low-image dimensions differ: %d x %d != %d x %d\n",w1,h1,w2,h2);
			return EXIT_FAILURE;
		}
		if( PixelChannels==1 ) _lowPixels = lowPixels , lowPixels = new RGrayStream< half , 3 >( _lowPixels , w1 , h1 );
	}
	if( Labels.set )
	{
		if( !debugFlags.noInput ) labels = GetReadStream< LType >( Labels.value , w2 , h2 , false , false , false , ioServerPtr , Threads.value , Priority.value );
		else
		{
			GetReadSize< half >( Labels.value , w2 , h2 );
			labels = new NULLStreamingGrid( sizeof( LType ) * w2 * 3 , h2 );
		}
		if( w1!=w2 || h1!=h2 )
		{
			fprintf(stderr,"Image and label dimensions differ: %d x %d != %d x %d\n",w1,h1,w2,h2);
			return EXIT_FAILURE;
		}
	}
	DefaultOutputTileWidth = client.tileWidth( );
	DefaultOutputTileHeight = client.tileHeight( );
	if( client.tileExtension() ) DefaultOutputTileExtension = client.tileExtension( );

	if( client.shortSync( ) )
		if( !debugFlags.noOutput )
//			out = new WImageClipper< half >( Out.value , client.size() , client.height() , true , 0 , 0 , client.cSize() , client.cHeight() , client.gammaCorrection() , client.quality() , ioServerPtr , HDR.set , Threads.value , Priority.value );
			out = new WImageClipper< half >( Out.value , client.size() , client.height() , true , client.gammaCorrection() ,  0 , 0 , client.cSize() , client.cHeight() , client.quality() , ioServerPtr , HDR.set , Threads.value , Priority.value );
		else
			out = new NULLStreamingGrid( client.cSize() * sizeof( half ) * 3 , client.cHeight() );
	else
		if( !debugFlags.noOutput )
//			out = new WImageClipper<float>( Out.value , client.size() , client.height() , true , 0 , 0 , client.cSize() , client.cHeight() , client.gammaCorrection() , client.quality() , ioServerPtr , HDR.set , Threads.value , Priority.value );
			out = new WImageClipper<float>( Out.value , client.size() , client.height() , true , client.gammaCorrection() , 0 , 0 , client.cSize() , client.cHeight() , client.quality() , ioServerPtr , HDR.set , Threads.value , Priority.value );
		else
			out = new NULLStreamingGrid( client.cSize() * sizeof( float ) * 3 , client.cHeight() );
	if( PixelChannels==1 )
	{
		_out = out;
		if( client.shortSync() ) out = new WGrayStream< half  , 3 >( out , client.size() , client.height() );
		else                     out = new WGrayStream< float , 3 >( out , client.size() , client.height() );
	}

	char tmpDir[1024];
	if( TempDir.set )
	{
		sprintf( tmpDir , "TMP=%s" , TempDir.value );
		size_t l = strlen(tmpDir);
		if( tmpDir[l-1]!=FileSeparator ) tmpDir[l] = FileSeparator , tmpDir[l+1] = 0;
	}
	else sprintf( tmpDir , "TMP=" );
#ifdef _WIN32
	_putenv( tmpDir );
#else // !_WIN32
	putenv( tmpDir );
#endif // _WIN32
	double t=Time();
	client.template Run< PixelChannels , 3 >( lowPixels , pixels , labels , out , averageMap );
	size_t current , peak;
	WorkingSetInfo( current , peak );
	printf( "Running Time: %f\n" , Time()-t ) , fflush( stdout );
	printf( "Peak working set: %llu MB\n" , (unsigned long long)(peak>>20) ) , fflush( stdout );

	if( lowPixels ) delete lowPixels;
	if( pixels ) delete pixels;
	if( labels ) delete labels;
	if( out ) delete out;
	if( _lowPixels ) delete _lowPixels;
	if( _pixels ) delete _pixels;
	if( _out ) delete _out;
	IOServer::UnLoad();
#if FULL_ASSERT_ARRAY_ACCESS
	PrintMemoryInfo( );
#endif // FULL_ASSERT_ARRAY_ACCESS
	return EXIT_SUCCESS;
}
template< class StorageType >
int Execute( void )
{
#if USE_SERVER
	MultiStreamIOServer ioServer;
	MultiStreamIOServer* ioServerPtr = &ioServer;
#else // !USE_SERVER
	MultiStreamIOServer* ioServerPtr = NULL;
#endif // !USE_SERVER

	int w1 , h1;
	GetReadSize< half >( Pixels.value , w1 , h1 );
	printf( "Input image size: %d x %d\n" , w1 , h1 );
	SocketedSuperMultigridClient< StorageType , half , LType > client;
	bool getGradientAverage;
	bool useGrayImage;
	if( !client.SetUp( Address.value , Prefix.value , Port.value , Index.value , w1 , h1 , Threads.value , InCore.set , ioServerPtr , getGradientAverage , useGrayImage ) )
	{
		fprintf( stderr , "SocketedMultigridClient::SetUp failed\n" );
		return EXIT_FAILURE;
	}
	if( useGrayImage ) return _Execute< 1 , StorageType >( client , getGradientAverage , w1 , h1 , ioServerPtr );
	else               return _Execute< 3 , StorageType >( client , getGradientAverage , w1 , h1 , ioServerPtr );
}
int main( int argc , char* argv[])
{
#ifdef _WIN32
	_setmaxstdio( 1024 );
#else // !_WIN32
	struct rlimit rl;
	getrlimit( RLIMIT_NOFILE , &rl ); 
	rl.rlim_cur = 1024+3; 
	setrlimit( RLIMIT_NOFILE , &rl );
#endif // _WIN32
	IOServer::Load();
	int paramNum = sizeof( params ) / sizeof( cmdLineReadable* );
	if( !cmdLineParse( argc-1 , &argv[1] , paramNum , params , true ) ) return EXIT_FAILURE;

	if( !Pixels.set || !Out.set || !Address.set )
	{
		ShowUsage(argv[0]);
		return EXIT_FAILURE;
	}
	if( !Port.set )
	{
		int a1 , a2 , a3 , a4;
		if( sscanf( Address.value , " %d.%d.%d.%d:%d " , &a1 , &a2 , &a3 , &a4 , &Port.value ) != 5 )
		{
			ShowUsage(argv[0]);
			return EXIT_FAILURE;
		}
		sprintf( Address.value, "%d.%d.%d.%d" , a1 , a2 , a3 , a4 );
	}

	if( Storage.set )
		if		( !strcasecmp( Storage.value , "float" ) )	return Execute< half >	( );
		else if	( !strcasecmp( Storage.value , "double" ) )	return Execute< double >( );
		else if	( !strcasecmp( Storage.value , "half" ) )	return Execute< half >	( );
		else
		{
			fprintf( stderr , "Unrecognized storage type: %s\n" ,  Storage.value );
			return EXIT_FAILURE;
		}
	else return Execute< half >( );
}
