#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <Util/XPlatform.h>
#include <Util/Time.h>
#include <Util/CmdLineParser.h>
#include <LaplacianMatrix/SocketedMultigrid/MultigridServer.h>
#include <LaplacianMatrix/SocketedMultigrid/SocketData.h>


// [WARNING] This definition needs to be synchronized with ClientSocket
typedef uint16_t LType;

#define DEFAULT_BUFLEN 1024

cmdLineInt Count("count"),Port("port",0),Iters("iters",5),InCoreRes("inCoreRes",1024),MinMGRes("minMGRes",64),VCycles("vCycles",1),Quality("quality",100),Lanes("lanes",1);
cmdLineInt MinBandSize( "minBandSize" , 0 ) , TileWidth( "tileWidth" , 8192 ) , TileHeight( "tileHeight" , 8192 ) , UnknownType( "unknownType" , UNKNOWN_BLACK );
cmdLineReadable Verbose("verbose") , Spherical( "spherical" ) , Cylindrical( "cylindrical" ) , Progress("progress") , GammaCorrection( "gCorrection" );
cmdLineReadable NoConjugateGradient("noCG");
cmdLineReadable NoDisk( "noDisk" ) , NoNetwork( "noNetwork" ) , ShortSync( "shortSync" );
cmdLineString Prefix( "prefix" ) , TileExtension( "tileExt" );
cmdLineFloat IWeight("iWeight",0) , GScale("gScale",1);
#if DEBUG_FLAGS
cmdLineReadable NoGradient("noGradient") , NoDivergence("noDivergence") , NoResidual("noResidual") , NoRestriction("noRestriction");
cmdLineReadable NoProlongation("noProlongation") , NoSolver("noSolver") , NoInput("noInput") , NoOutput("noOutput") , NoTempIO("noTempIO");
cmdLineReadable NoLeftRight( "noLeftRight" ) , NoTopBottom( "noTopBottom" ) , EmptyPackets( "emptyPackets" );
#endif // DEBUG_FLAGS
cmdLineReadable Deramp( "deramp" );
cmdLineReadable GrayImage( "gray" );
cmdLineReadable* params[]=
{
	&Port,&Count,&Iters,&InCoreRes,&MinMGRes,&VCycles,&Verbose,&Spherical , &Cylindrical , &Progress,&Quality,&Lanes,&NoConjugateGradient , &MinBandSize , &Prefix , &UnknownType , &GammaCorrection ,
#if DEBUG_FLAGS
	&NoGradient , &NoDivergence , &NoResidual , &NoRestriction , &NoProlongation , &NoSolver , &NoInput , &NoOutput , &NoTempIO ,
	&NoLeftRight , &NoTopBottom , &EmptyPackets , 
#endif // DEBUG_FLAGS
	&IWeight,&GScale ,
	&NoDisk , &NoNetwork , &ShortSync , &TileWidth , &TileHeight , &TileExtension , &Deramp ,
	&GrayImage ,
};

void ShowUsage( char* ex )
{
	printf("Usage %s:\n",ex) , fflush( stdout );
	printf("\t--%s <client count>\n",Count.name) , fflush( stdout );
	printf( "\t[--%s <preferred address prefix>]\n" , Prefix.name ) , fflush( stdout );
	printf("\t[--%s <listen port>=%d]\n",Port.name,Port.value) , fflush( stdout );
	printf("\t[--%s <minimum multigrid resolution>=%d]\n",MinMGRes.name,MinMGRes.value) , fflush( stdout );
	printf("\t[--%s <Gauss-Seidel iterations>=%d]\n",Iters.name,Iters.value) , fflush( stdout );
	printf("\t[--%s <in core resolution>=%d]\n",InCoreRes.name,InCoreRes.value) , fflush( stdout );
	printf("\t[--%s <v-cycles>=%d]\n",VCycles.name,VCycles.value) , fflush( stdout );
	printf("\t[--%s <image quality>=%d]\n",Quality.name,Quality.value) , fflush( stdout );

	printf( "\t[--%s <default output tile width>=%d]\n" , TileWidth.name , TileWidth.value ) , fflush( stdout );
	printf( "\t[--%s <default output tile height>=%d]\n" , TileHeight.name , TileHeight.value ) , fflush( stdout );
	printf( "\t[--%s <default output file extension>]\n" , TileExtension.name ) , fflush( stdout );

	printf("\t[--%s <solver lanes>=%d]\n",Lanes.name,Lanes.value) , fflush( stdout );

	printf( "\t[--%s <unknown label behavior>=%d]\n" , UnknownType.name , UnknownType.value );
	printf( "\t\t%d] Do nothing\n" , UNKNOWN_NONE );
	printf( "\t\t%d] Fill with black\n" , UNKNOWN_BLACK );
	printf( "\t\t%d] Fill with harmonic\n" , UNKNOWN_HARMONIC );

	printf("\t[--%s]\n",Spherical.name) , fflush( stdout );
	printf("\t[--%s]\n" , Cylindrical.name ) , fflush( stdout );
	printf("\t[--%s]\n",Verbose.name) , fflush( stdout );
	printf("\t[--%s]\n",Progress.name) , fflush( stdout );
	printf( "\t[--%s]\n" , NoDisk.name ) , fflush( stdout );
	printf( "\t[--%s]\n" , NoNetwork.name ) , fflush( stdout );
	printf("\t[--%s <sharpening interpolation weight>=%f]\n",IWeight.name,IWeight.value) , fflush( stdout );
	printf("\t[--%s <sharpening gradient weight>=%f]\n",GScale.name,GScale.value) , fflush( stdout );
	printf( "\t[--%s]\n" , MinBandSize.name ) , fflush( stdout );
	printf( "\t[--%s]\n" , GammaCorrection.name ) , fflush( stdout );
	printf( "\t[--%s]\n" , Deramp.name ) , fflush( stdout );
	printf( "\t[--%s]\n" , GrayImage.name ) , fflush( stdout );
#if DEBUG_FLAGS
	printf("\t[--%s]\n",NoGradient.name) , fflush( stdout );
	printf("\t[--%s]\n",NoDivergence.name) , fflush( stdout );
	printf("\t[--%s]\n",NoResidual.name) , fflush( stdout );
	printf("\t[--%s]\n",NoRestriction.name) , fflush( stdout );
	printf("\t[--%s]\n",NoProlongation.name) , fflush( stdout );
	printf("\t[--%s]\n",NoSolver.name) , fflush( stdout );
	printf("\t[--%s]\n",NoInput.name) , fflush( stdout );
	printf("\t[--%s]\n",NoOutput.name) , fflush( stdout );
	printf("\t[--%s]\n",NoTempIO.name) , fflush( stdout );
	printf( "\t[--%s]\n" , NoLeftRight.name ) , fflush( stdout );
	printf( "\t[--%s]\n" , NoTopBottom.name ) , fflush( stdout );
	printf( "\t[--%s]\n" , EmptyPackets.name ) , fflush( stdout );
#endif // DEBUG_FLAGS
}
template< int PixelChannels >
int Execute( void )
{
	IOServer::Load();
	PrintHostAddresses();

#if DEBUG_FLAGS
	debugFlags.noGradient		= NoGradient.set;
	debugFlags.noDivergence		= NoDivergence.set;
	debugFlags.noResidual		= NoResidual.set;
	debugFlags.noRestriction	= NoRestriction.set;
	debugFlags.noProlongation	= NoProlongation.set;
	debugFlags.noSolver			= NoSolver.set;
	debugFlags.noInput			= NoInput.set;
	debugFlags.noOutput			= NoOutput.set;
	debugFlags.noTempIO			= NoTempIO.set;
	debugFlags.noLeftRight		= NoLeftRight.set;
	debugFlags.noTopBottom		= NoTopBottom.set;
	debugFlags.emptyPackets		= EmptyPackets.set;
#endif // DEBUG_FLAGS

	SocketedSuperMultigridServer server;
	if( !TileExtension.set )
	{
		if( !server.SetUp( Prefix.value , Port.value , Count.value , Iters.value , InCoreRes.value , MinMGRes.value , VCycles.value , MinBandSize.value , TileWidth.value , TileHeight.value , NULL , GammaCorrection.set , Quality.value , Lanes.value , Verbose.set , Spherical.set ? SPHERICAL_PERIODIC : (Cylindrical.set ? CYLINDRICAL_PERIODIC : NO_PERIODIC ) , IWeight.value , GScale.value ,
			Deramp.set , UnknownType.value , Progress.set ,
#if DEBUG_FLAGS
			debugFlags ,
#endif // DEBUG_FLAGS
			NoConjugateGradient.set , ShortSync.set
			) )
			fprintf( stderr , "SocketedSuperMultigridServer::SetUp failed\n" );
	}
	else
	{
		if( !server.SetUp( Prefix.value , Port.value , Count.value , Iters.value , InCoreRes.value , MinMGRes.value , VCycles.value , MinBandSize.value , TileWidth.value , TileHeight.value , TileExtension.value , GammaCorrection.set , Quality.value , Lanes.value , Verbose.set , Spherical.set ? SPHERICAL_PERIODIC : (Cylindrical.set ? CYLINDRICAL_PERIODIC : NO_PERIODIC ) , IWeight.value , GScale.value ,
			Deramp.set , UnknownType.value , Progress.set ,
#if DEBUG_FLAGS
			debugFlags ,
#endif // DEBUG_FLAGS
			NoConjugateGradient.set , ShortSync.set
			) )
			fprintf( stderr , "SocketedSuperMultigridServer::SetUp failed\n" );
	}

	double t=Time();
	if( ShortSync.set ) server.Run< PixelChannels , 3 , half  , LType >();
	else                server.Run< PixelChannels , 3 , float , LType >();
	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 );

	IOServer::UnLoad();

#if FULL_ASSERT_ARRAY_ACCESS
	PrintMemoryInfo( );
#endif // FULL_ASSERT_ARRAY_ACCESS
	return EXIT_SUCCESS;
}
int main( int argc , char* argv[] )
{
	int paramNum = sizeof(params)/sizeof(cmdLineReadable*);
	if( !cmdLineParse( argc-1 , &argv[1] , paramNum , params , true ) ) return EXIT_FAILURE;
	if( !Count.set )
	{
		ShowUsage( argv[0] );
		return EXIT_FAILURE;
	}
	if( GrayImage.set ) return Execute< 1 >( );
	else                return Execute< 3 >( );
}
