#include <iostream>
#include <fstream>
#include <getopt.h>
#include <time.h>
#include <ini/iniparser.h>

#include "toolbox/finalpreview.h"
#include "scheduler.h"

using std::cout;
using std::endl;
using std::cerr;

//----------------------------------------------------------------------------------
// a simple reporting function
//----------------------------------------------------------------------------------
void makeReport(const IniHandler &configIni)
{
	REPORT("\nenabled features:"
//computation/processing features
#ifdef GTGEN_DEBUG
		<< " + extended comments (DEBUG)"
#endif
#ifdef GTGEN_WITH_MULTITHREADING
		<< " + parallel convolutions"
#endif

//simulation features
		<< "\n                 "
#ifdef GTGEN_WITH_RIGIDMOTION
	#ifdef GTGEN_WITH_JUST2DTRANSLATIONS
		<< " + 2D"
	#else
		<< " + 3D"
	#endif
		<< " rigid motion"
#endif
#ifdef GTGEN_WITH_NONRIGIDMOTION
		<< " + non-rigid deformation"
#endif
#ifdef GTGEN_WITH_STRONGSYNCHRONIZATION
		<< " + strong synchronization"
#endif

//output features
		<< "\n                 "
		<< " + phantoms" //that's always produced
#ifdef GTGEN_WITH_PHOTOBLEACHING
		<< " with photobleaching"
#endif
#ifdef GTGEN_WITH_CHRCENTRESINPHANTHOMS
		<< " with chromocentres"
#endif
#ifdef GTGEN_WITH_TERRITORIES
		<< " + AF territories"
#endif
		<< "\n                 "
		<< " + cell masks" //that's always produced
#ifdef GTGEN_WITH_NUCLEIMASKS
		<< " + nuclei masks"
#endif
#ifdef GTGEN_WITH_FLOWFIELDS
		<< " + flow fields"
#endif
		<< "\n                 "
#ifdef GTGEN_WITH_FINALIMAGES
#ifdef GTGEN_WITH_REAL_PSF
		<< " + final images using real PSF"
#else
#ifdef GTGEN_WITH_HIGHSNR
		<< " + high SNR"
#else
		<< " + low SNR"
#endif
		<< " final previews"
#endif
#ifdef GTGEN_WITH_ANISOTROPIC_GT
		<< " with anisotropic GTs"
#endif
#endif

#ifdef GTGEN_WITH_TIFFS
		<< " + TIFF images"
#else
		<< " + ICS images"
#endif
		<< " + ST images at z=" << 
		(int) configIni["scheduler"]["slice of interest"]
#ifdef GTGEN_WITH_SOFA
		<< "\n                 "
		<< " + SOFA interconnection!"
#endif
		<< ( !configIni["testing"].empty() ?
			"\n                  + testing mode: watch out for TESTING MODE messages"
			:
			"")
	);

	REPORT("\nconstants used: "
		<< "cellDiameter=" << (float) configIni["cell"]["cell diameter"] << " um"
		<< ", nucleolusDiameter=" << (float) configIni["cell"]["nucleolus diameter"] << " um"
		<< ", coverage=" << (float) configIni["cell"]["coverage"] << " %"
		<< "\n                "
		<< "cellNonRigidDeformation=" << (float) configIni["cell"]["cell nonrigid deformation"] << " um"
		<< ", cellNonRigidStrongDeformation=" << (float) configIni["cell"]["cell nonrigid strong deformation"] << " um"
		<< "\n                "
		<< "(mean) noFramesPerCellCycle=" << (int) configIni["cell"]["number of frames per cell cycle"] << " frames"
		<< ", cellSparseNonRigidDeformations=" << (const char*) configIni["cell"]["cell sparse nonrigid deformations"]
		<< "\n                "
		<< "cellMagnitudeOfMovementPerFrame=" << (float) configIni["cell"]["cell magnitude of movement per frame"] << " um"
		<< ", cellLookDistance=" << (float) configIni["cell"]["cell look distance"] << " um"
		<< "\n                "
		<< "cometCellMagnitudeOfMovementPerFrame=" << 
		(float) configIni["cell"]["comet cell magnitude of movement per frame"] << " um"
		);

#ifdef GTGEN_WITH_SOFA
	REPORT("\nSOFA config params: "
		<< "exportMode=" << (int)configIni["sofa"]["export mode"] << " (1=on, otherwise off)"
		<< "\n                    "
		<< "frameSkipStep=" << (int)configIni["sofa"]["frame skip step"]
		);
#endif
}

//----------------------------------------------------------------------------------
// the function describing the use of this console application
// it is called whenever the program is called without parameters
// or with bad combination of parameters
//----------------------------------------------------------------------------------
void usage(const char *name)
{
	 cerr << "APPLICATION NAME" << endl <<
				"\t" << name << " ... ground truth generator of next generation :-)  " <<
				endl << endl;
	 cerr << "SYNTAX" << endl << "\t" << name << " <options> " << endl << endl;
	 cerr << "OPTIONS" << endl <<
		"\t-c <filename> ... ini file containing the description\n" <<
      "\t                  of the simulation (mandatory option)\n" << 
		"\t-g <filename> ... image with pregenerated cell population\n" <<
		"\t                  (must be compatible with the ini file)\n" <<
		"\t-p <filename> ... already created phantom image\n" <<
		"\t-m <filename> ... already created mask image\n" << 
		"\t-f <filename> ... where to save final image\n" << 
		"\t-s <value>    ... required stretch factor of the intensities (optional)\n" <<
		"\t-h            ... this help" << endl << endl;

	 cerr << "SOME EXAMPLES:" << endl << endl;

	 cerr << "** " << "Basic generation without use of any pregenerated data:" << 
				endl <<
				"\t" << name << " -c config.ini" << endl << endl;
	 cerr << "** " << "Generation based on already pregenerated cell population: " <<
				endl <<
				"\t" << name << 
				" -c config.ini -g images/initial/ID2_C8_SceneSizeS_ready.ics" <<
				endl << endl;
	 cerr << "** " << "Skip the phantom creation, simulate only" <<
				" OptiGen and AcquiGen: " << endl <<
				"\t" << name << 
				" -c config.ini -p phantom.ics -m mask.ics -f final.ics" <<
				endl << endl;
	 cerr << "** " << "Skip the phantom creation, simulate only" <<
				" OptiGen and AcquiGeni and stretch the sequence: " << endl <<
				"\t" << name << 
				" -c config.ini -p phantom.ics -m mask.ics -f final.ics -s 3.0" <<
				endl << endl;
}

//----------------------------------------------------------------------------------
/**
 * Entry point of the program, the main() function.
 *
 * It merely only parses arguments to find out what particular objects are asked
 * to be simulated. It then loops Scheduler::Run() until desired number
 * of frames is generated.
 */
//----------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
	 Scheduler<i3d::GRAY16, i3d::GRAY16> *controller = NULL;

	 std::string 
				configFilename(""),
				pregeneratedImageFilename(""),
				maskImageFilename(""),
				phantomImageFilename(""),
				finalImageFilename("");

	 int c; // control variable of getopt functions

	 bool flagPregenerated = false,
			flagConfig = false,
			flagMask = false,
			flagPhantom = false,
			flagFinal = false;

#ifdef GTGEN_WITH_REAL_PSF
	 float scale = 1.0f;
#endif

	 // read the command line options
	 while (optind < argc)
	 {
		  if ((c = getopt(argc, argv, "c:g:m:p:f:h:s:")) != -1)
		  {
				switch (c)
				{
					 case 'c':
								configFilename = optarg;
								flagConfig = true;
								break;
					 case 'g':
								pregeneratedImageFilename = optarg;
								flagPregenerated = true;
								break;
					 case 'm':
								maskImageFilename = optarg;
								flagMask = true;
								break;
					 case 'p':
								phantomImageFilename = optarg;
								flagPhantom = true;
								break;
					 case 'f':
								finalImageFilename = optarg;
								flagFinal = true;
								break;
					 case 's':
#ifdef GTGEN_WITH_REAL_PSF
								scale = atof(optarg);
#endif
								break;
					 default:
					 			std::cout << std::endl;
					 case 'h': // users asks for help
								usage(argv[0]);
								exit(-1);
				}
		  }
		  else
		  {
				optind++;
		  }
	 }

	 // Is the config-file defined? If not, give help and exit.
	 if (!flagConfig)
	 {
		  usage(argv[0]);
		  exit(-1);
	 }

	 // check the starting time of the process
	 char time_and_date_string[50];
	 time_t time_and_date_secs=time(NULL);
	 strftime(time_and_date_string,50,"%a %F, %T",localtime(&time_and_date_secs));	
	 REPORT("starting time: " << time_and_date_string);

	 try {

	 	 // Load the parameters of simulation from the given config file
	 	 REPORT("supplied configuration file: " << configFilename);
	 	 IniHandler configuration(configFilename.c_str());

	 	 // initialize random seed generator
	 	 unsigned int RandSeed=time(NULL);
	 	 REPORT("stdlib generator seeded with " << RandSeed); 
	 	 srand(RandSeed);

		 // start generation only if none of these is specified
		 // to make sure user knows what is she doing ;-)
		 if (!flagMask && !flagPhantom && !flagFinal)
		 {
			  // call reporting function
			  makeReport(configuration);

			  // ---- init for the simulation ----

			  // shall the initial population of the cell be generated or
			  // has it already been prepared?
			  if (flagPregenerated)
			  {
					REPORT("using the pregenerated image " << pregeneratedImageFilename);

					//scheduler's constructor will check compatibility itself
					controller = new Scheduler<>(configuration,
														  pregeneratedImageFilename.c_str());
			  }
			  else
			  {
					REPORT("starting from scratch, consider using -g possibly");
					controller = new Scheduler<>(configuration);
			  }
			  
			  REPORT("going to work with scene of size " << 
						controller->GetSceneSizePX() << " in pixels"); 
			  
			  // check if the sliceOfInterest is within the range 
			  size_t sliceOfInterest = (int) configuration["scheduler"]["slice of interest"];
			  if (sliceOfInterest >= controller->GetSceneSizePX().z)
					throw ERROR_REPORT("Adjust the constant 'sliceOfInterest'"
											 " to stay within the z-size of generated images."); 
			  
			  // init the 'power noise' array so that cells can make use of it
			  char fileName[]="pnArray.cache";
			  std::ifstream FLE(fileName); 
			  
			  if (FLE.is_open()) 
			  { 
					// file exists, use it
					FLE.close();
					controller->PnInitArray(fileName);
			  } 
			  else 
			  {
					// file is probably not existing, create it
					//default setting
					float xyz_factor=3.5f;
				   float cellDiameter = configuration["cell"]["cell diameter"];
					size_t frames=controller->GetInterphaseBudget()/6 +10; //plus 10 for reserve...

					if (configuration["testing"].present("pnArray xyz factor"))
					{
						REPORT("TESTING MODE: overriding default pnArray stretch factor with "
							<< (float)configuration["testing"]["pnArray xyz factor"]);
						xyz_factor=(float)configuration["testing"]["pnArray xyz factor"];
					}
					if (configuration["testing"].present("pnArray z length"))
					{
						REPORT("TESTING MODE: overriding default pnArray length with "
							<< (int)configuration["testing"]["pnArray z length"]);
						frames=(int)configuration["testing"]["pnArray z length"];
					}

					controller->PnInitArray(i3d::Vector3d<float>(
						xyz_factor*cellDiameter*controller->GetSceneRes().x,
						xyz_factor*cellDiameter*controller->GetSceneRes().y,
						xyz_factor*cellDiameter*controller->GetSceneRes().z),
													frames,fileName);
			  } 

#ifdef GTGEN_WITH_SOFA
			  // if SOFA in the export mode, then:
			  if ((int)configuration["sofa"]["export mode"] == 1)
			  {
				   //export indeed...
			  		controller->SOFA_InitialExport();

					//flush all remaining frames, i.e., the scene*[0] only in this moment
					controller->ReleaseOldScenes(true);

					//clean up for "the quit"
			  		delete controller;
					controller=NULL;

					//"the quit"
					throw ERROR_REPORT("Quiting as SOFA exports are done.");
			  }
#endif
			  // print overview of phases
			  controller->PrintPhaseBudgets();

			  // ---- do the simulation ----

			  if (! configuration["testing"].present("single run length"))
			  {
				  size_t noFramesPerCellCycle =
				  			(int) configuration["cell"]["number of frames per cell cycle"];
				  // simulate the next 3 times noFramesPerCellCycle (3 generation of cells)
				  controller->Run(noFramesPerCellCycle-10);
				  controller->Run(noFramesPerCellCycle+10);
				  //controller->Run(noFramesPerCellCycle-10);
			  }
			  else
			  {
				  REPORT("TESTING MODE: doing just single Run() of lenght (at least) "
				  		<< (int)configuration["testing"]["single run length"] << " frames.");
				  controller->Run( (int)configuration["testing"]["single run length"] );
			  }

			  // ---- clean up after the simulation ----

			  //flush all remaining frames
			  controller->ReleaseOldScenes(true);

			  //clean up and indicate nice end of simulation
			  delete controller;
			  controller=NULL;
			  REPORT("Happy end! :-)");
			  REPORT("\n\n");
			  REPORT("Now, inspect the results. If you are happy with them, please run:");
			  REPORT("$> ./scripts/finalize.sh " << configFilename);
			  REPORT("\n\n");
		 }
		 else if (!flagPregenerated) // perform only optigen and acquigen phases
		 {
			  DEBUG_REPORT("going only to turn a phantom (with its mask) into a final image");
			  if (!flagMask) throw ERROR_REPORT("-m must be supplied");
			  if (!flagPhantom) throw ERROR_REPORT("-p must be supplied");
			  if (!flagFinal) throw ERROR_REPORT("-f must be supplied");

			  // requires phantom (-p), mask (-m), and output final (-f)
			  i3d::Image3d<i3d::GRAY16> phantom(phantomImageFilename.c_str()); //was Img
			  i3d::Image3d<i3d::GRAY16> mask(maskImageFilename.c_str()); //was Mask 
			  i3d::Image3d<i3d::GRAY16> final; //final image

#ifdef GTGEN_WITH_REAL_PSF
			  // release version (real PSF)
			  PrepareFinalImage(phantom, mask, final, configuration, scale); 
#else
			  // simple but fast version (PSF as a Gaussian)
			  PrepareFinalPreviewImage(phantom, mask, final, configuration);
#endif
			  // report a SNR of the prepared image
			  ComputeSNR(final, mask);
			  
			  // save the final image
			  final.SaveImage(finalImageFilename.c_str());

#ifdef GTGEN_WITH_ANISOTROPIC_GT
			  //if non-ISO images are requested, the PrepareFinalImage() and PrepareFinalPreviewImage()
			  //changed both phantom and mask images, we're gonna save them under slightly
			  //modified names (added _nonISO suffix before the last dot '.')
			  std::string::size_type pos=phantomImageFilename.rfind('.');
			  if (pos == std::string::npos)
			  		throw ERROR_REPORT("Trying to rename phantom filename but couldn't locate '.'");
			  phantomImageFilename.insert(pos,"_nonISO");
			  REPORT("saving also resampled phantom image under a new name " << phantomImageFilename);
			  phantom.SaveImage(phantomImageFilename.c_str());

			  pos=maskImageFilename.rfind('.');
			  if (pos == std::string::npos)
			  		throw ERROR_REPORT("Trying to rename mask filename but couldn't locate '.'");
			  maskImageFilename.insert(pos,"_nonISO");
			  REPORT("saving also resampled mask image under a new name " << maskImageFilename);
			  mask.SaveImage(maskImageFilename.c_str());
#endif
				
		 }
		 else // wrong combination of input parameters - give help and exit
		 {
			  REPORT("Can't request -g simultaneously with -p, -m, and -f.\n");
			  usage(argv[0]);
			  exit(-1);
		 }
	 }
	 catch (const char *e)
	 {
		cout << e << endl;
	 }
	 catch (std::string &e)
	 {
		cout << e << endl;
	 }
	 catch (i3d::IOException& e)
	 {
		cout << e << endl;
	 }
	 catch (i3d::InternalException& e)
	 {
		cout << e << endl;
	 }
	 catch (std::bad_alloc&)
	 {
		cout << "Not enough memory." << endl;
	 }
	 catch (...)
	 {
		cout << "System exception." << endl;
	 }

 	 // if the controller still exists (something is wrong), 
	 // try to save remaining scenes.
	 if (controller) 
	 {
		  REPORT("Detected incorrect stop of simulation,"
					" trying to flush remaining scenes.\n");
		  
		  try 
		  {
				//clean up and indicate nice end of simulation
				controller->ReleaseOldScenes(true);
				delete controller;
				REPORT("just clean end");
		  }
		  catch (std::string &e)
		  {
				cout << e << endl;
		  }
		  catch (i3d::IOException& e)
		  {
				cout << e << endl;
		  }
		  catch (i3d::InternalException& e)
		  {
				cout << e << endl;
		  }
		  catch (std::bad_alloc&)
		  {
				cout << "Not enough memory." << endl;
		  }
		  catch (...)
		  {
				cout << "System exception (2)." << endl;
		  } 
		 
		  // check the final time of the process
		  time_and_date_secs=time(NULL);
		  strftime(time_and_date_string,50,"%a %F, %T",localtime(&time_and_date_secs));
		  REPORT("stopping time: " << time_and_date_string);
		  return 1;
	 }

    // check the final time of the process
	 time_and_date_secs=time(NULL);
	 strftime(time_and_date_string,50,"%a %F, %T",localtime(&time_and_date_secs));
	 REPORT("stopping time: " << time_and_date_string);
	 return 0;
}
