#include <iostream>
#include <fstream>
#include <sstream>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <fftw3.h>
#include <values.h>		//defines FLT_MAX and FLT_MIN
#include <unistd.h>		//for getpid() in constructors
#include <fcntl.h>		//for creat() in constructors
#include <time.h>			//for timestamps in Scheduler::Run()

#include <i3d/toolbox.h>	//defines GetNumberOfProcessors()
#include <i3d/transform.h>

#include <cytopacq/cytogen.h>
#include <cytopacq/acquigen.h>
#include <ini/iniparser.h>

#include "toolbox/finalpreview.h"
#include "toolbox/shape_rendering.h"
#include "toolbox/rnd_generators.h"
#include "toolbox/user_abort.h"
#include "scheduler.h"

//-----------------------------------------------------------------------------
/// filename for storing the tracking info
//-----------------------------------------------------------------------------
const char *fnameOfTracksTxt = "tracks.txt";

//-----------------------------------------------------------------------------
template <class MV, class PV> 
Scheduler<MV, PV>::Scheduler(const IniHandler &_configIni,
									  const char *fname) :
		  configIni(_configIni),
		  sceneSize(0.0f,0.0f,0.0f),
		  sceneRes(0.0f,0.0f,0.0f),
		  sceneOffset(0.0f,0.0f,0.0f),
		  sceneMasks(0, NULL),
#ifdef GTGEN_WITH_NUCLEIMASKS
		  sceneNuclei(0, NULL),
#endif
		  scenePhantoms(0, NULL),
#ifdef GTGEN_WITH_TERRITORIES
		  sceneTerritories(0, NULL),
#endif
		  timeStamp(0),
		  oldestScene(0),
		  newestScene(0),
		  cells(0, NULL),
		  stMasks(0),
		  stPhantoms(0),
#ifdef GTGEN_WITH_TERRITORIES
		  stTerritories(0),
#endif
		  pnArray(NULL),
		  pnRealWidth(0)
{
	 // fill variables from ini structure
	 framesPerCycle = (int) configIni["cell"]["number of frames per cell cycle"];
	 sliceOfInterest = (int) configIni["scheduler"]["slice of interest"];

	 // prepare tracker strucutre
	 tracker.clear();

	 // read the mask from the image file
	 // and store the image properties
	 i3d::Image3d<MV> *userProvidedMasks = new i3d::Image3d<MV>(fname);

	 //before we proceed with it, we make sure it is isotropic
	 //and compatible with the supplied ini file
	 //
	 // create initial reference image, just like when no -g parameter was supplied
	 i3d::Image3d<MV> *refImg=new i3d::Image3d<MV>;
	 // this function initiates *refImg with ISOtropic resolution,
	 CreateEmptyImage(configIni, *refImg);

	 //check everything relevant
	 // if (refImg->GetResolution().GetRes() != userProvidedMasks->GetResolution().GetRes()) 
	 // was too much sensitive (ICS fileformat, for instance, rounds res. info to 6 digits),
	 // hence resDiff is used instead (with little tolerance)
	 const i3d::Vector3d<float> resDiff=
	 		userProvidedMasks->GetResolution().GetRes()
			- refImg->GetResolution().GetRes();
	 DEBUG_REPORT("mask res. minus ini file res.=" << resDiff
	 		<< ", L2=" << i3d::Norm2(resDiff));

	 if ( (i3d::Norm2(resDiff) > 0.001*0.001)
	 	|| (refImg->GetSize() != userProvidedMasks->GetSize())
		|| (refImg->GetOffset() != userProvidedMasks->GetOffset()) ) {

			std::ostringstream err;
			err << "Initial mask image (-g parameter) is not compatible with\n"
				<< "     the given configuration file (-c parameter). It should be:\n     Size = "
				<< refImg->GetSize() << "um, Res = " << refImg->GetResolution().GetRes()
				<< "px/um, Offset = " << refImg->GetOffset() << "um.";
			delete refImg;
			throw ERROR_REPORT(err.str());
		}

	 //sync resolutions (we know that they differ at most by 0.001 px/um "diagonally")
	 DEBUG_REPORT("mask image is compatible with ini file");
	 userProvidedMasks->SetResolution(refImg->GetResolution().GetRes());

	 //the refImg is now not needed anymore
	 delete refImg;

	 //okay, save metadata and continue...
	 sceneRes = userProvidedMasks->GetResolution().GetRes();
	 sceneSize = i3d::PixelsToMicrons(userProvidedMasks->GetSize(),sceneRes);
	 sceneOffset = userProvidedMasks->GetOffset();

	 DEBUG_REPORT("sceneSize = " << sceneSize
		<< "um, sceneOffset = " << sceneOffset << "um");
	 DEBUG_REPORT("sceneRes = " << sceneRes << "px/um");

	 // create empty initial mask frame
	 // and add it to the sceneMasks
	 i3d::Image3d<MV> *initialMask = new i3d::Image3d<MV>();
	 initialMask->CopyMetaData(*userProvidedMasks);
	 initialMask->Clear();
	 sceneMasks.push_back(initialMask);

#ifdef GTGEN_WITH_NUCLEIMASKS
	 // create empty initial nuclei frame
	 // and add it to the sceneNuclei
	 i3d::Image3d<MV> *initialNuclei = new i3d::Image3d<MV>();
	 initialNuclei->CopyMetaData(*userProvidedMasks);
	 initialNuclei->Clear();
	 sceneNuclei.push_back(initialNuclei);
#endif
	 // create empty initial phantom frame
	 // and add it to the scenePhantoms
	 i3d::Image3d<PV> *initialPhantom = new i3d::Image3d<PV>();
	 initialPhantom->CopyMetaData(*userProvidedMasks);
	 initialPhantom->Clear();
	 scenePhantoms.push_back(initialPhantom);

#ifdef GTGEN_WITH_TERRITORIES
	 i3d::Image3d<PV> *initialTerritory = new i3d::Image3d<PV>();
	 initialTerritory->CopyMetaData(*userProvidedMasks);
	 initialTerritory->Clear();
	 sceneTerritories.push_back(initialTerritory);
#endif
	 // go through all the masks in the provided labeled image and 
	 // - replicate the masks into the 'sceneMasks' variable and 
	 // - create new phantoms in 'scenePhantoms' variable
	 MV minLabel = userProvidedMasks->GetVoxelData().min()+1;
	 MV maxLabel = userProvidedMasks->GetVoxelData().max();

	 if (configIni["testing"].present("init mask min label"))
	 {
	 	REPORT("TESTING MODE: looking for cells from ID>="
			<< (int)configIni["testing"]["init mask min label"]);
		minLabel=(int)configIni["testing"]["init mask min label"];
	 }
	 if (configIni["testing"].present("init mask max label"))
	 {
	 	REPORT("TESTING MODE: looking for cells up to ID<="
			<< (int)configIni["testing"]["init mask max label"]);
		maxLabel=(int)configIni["testing"]["init mask max label"];
	 }

	 for (MV label=minLabel; label<=maxLabel; label++)
	 {
		  // create new cell
		  Cell<MV, PV> *newCell = 
					 new Cell<MV, PV>(this->configIni, *this, timeStamp);
		  
		  // set 'initial phase'
		  newCell->SetPhase(newBorn);

		  // set initial delay to disynchronize the cells
		  if (! configIni["testing"].present("cell initial G2 life"))
		  {
#ifdef GTGEN_WITH_STRONGSYNCHRONIZATION
				const float delayVariance=framesPerCycle/5;
#else
				const float delayVariance=framesPerCycle;
#endif
		  		newCell->SetDelay( (size_t)ceil(GetRandomUniform(0,delayVariance)) );
		  }
		  else
		  {
				REPORT("TESTING MODE: fixing initial G2 phase life to "
					<< (int)configIni["testing"]["cell initial G2 life"] << " frames.");
				newCell->SetDelay( (int)configIni["testing"]["cell initial G2 life"] );
		  }

		  // reads its mask and store it in the global scene
		  newCell->ReadMaskInG2Phase(*userProvidedMasks,label);

		  // create the texture and store it in the global scene
		  newCell->GenerateDotDistributionInG2Phase();

		  // insert the cell into scheduler
		  cells.push_back(newCell);
	 }

	 delete userProvidedMasks;

	 //construct the string and create the lock file
	 sprintf(lockFile,"mitopacq_PID%u.LOCK",getpid());

	 int file=creat(lockFile,O_RDONLY | S_IRUSR);
	 if (file == -1)
	   throw ERROR_REPORT("Can't create a lock file. How can I write the result of simulation then?");
	 else
	   close(file);
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
Scheduler<MV, PV>::Scheduler(const IniHandler &_configIni) :
		  configIni(_configIni),
		  sceneSize(0.0f,0.0f,0.0f),
		  sceneRes(0.0f,0.0f,0.0f),
		  sceneOffset(0.0f,0.0f,0.0f),
		  sceneMasks(0, NULL),
#ifdef GTGEN_WITH_NUCLEIMASKS
		  sceneNuclei(0, NULL),
#endif
		  scenePhantoms(0, NULL),
#ifdef GTGEN_WITH_TERRITORIES
		  sceneTerritories(0, NULL),
#endif
		  timeStamp(0),
		  oldestScene(0),
		  newestScene(0),
		  cells(0, NULL),
		  stMasks(0),
		  stPhantoms(0),
#ifdef GTGEN_WITH_TERRITORIES
		  stTerritories(0),
#endif
		  pnArray(NULL),
		  pnRealWidth(0)
{
	 // fill variables from ini structure
	 framesPerCycle = (int) configIni["cell"]["number of frames per cell cycle"];
	 size_t cellCount = (int) configIni["scheduler"]["number of initial cells"];
	 sliceOfInterest = (int) configIni["scheduler"]["slice of interest"];

	 // prepare tracker structure
	 tracker.clear(); 

	 // create initial reference image
	 i3d::Image3d<MV> refImg;
	 // this function initiates refImg with ISOtropic resolution,
	 CreateEmptyImage(configIni, refImg);

	 sceneRes = refImg.GetResolution().GetRes();
	 sceneSize = i3d::PixelsToMicrons(refImg.GetSize(),sceneRes);
	 sceneOffset = refImg.GetOffset();

	 DEBUG_REPORT("sceneSize = " << sceneSize
		<< "um, sceneOffset = " << sceneOffset << "um");
	 DEBUG_REPORT("sceneRes = " << sceneRes << "px/um");

	 // create empty initial mask frame
	 // and add it to the sceneMasks
	 i3d::Image3d<MV> *initialMask = new i3d::Image3d<MV>();
	 initialMask->CopyMetaData(refImg);
	 initialMask->Clear();
	 sceneMasks.push_back(initialMask);

#ifdef GTGEN_WITH_NUCLEIMASKS
	 // create empty initial nuclei frame
	 // and add it to the sceneNuclei
	 i3d::Image3d<MV> *initialNuclei = new i3d::Image3d<MV>();
	 initialNuclei->CopyMetaData(refImg);
	 initialNuclei->Clear();
	 sceneNuclei.push_back(initialNuclei);
#endif
	 // create empty initial phantom frame
	 // and add it to the scenePhantoms
	 i3d::Image3d<PV> *initialPhantom = new i3d::Image3d<PV>();
	 initialPhantom->CopyMetaData(refImg);
	 initialPhantom->Clear();
	 scenePhantoms.push_back(initialPhantom);

#ifdef GTGEN_WITH_TERRITORIES
	 i3d::Image3d<PV> *initialTerritory = new i3d::Image3d<PV>();
	 initialTerritory->CopyMetaData(refImg);
	 initialTerritory->Clear();
	 sceneTerritories.push_back(initialTerritory);
#endif
	 DEBUG_REPORT("going to generate " << cellCount << " new cells. It may take some time...");
	 
	 for (size_t i=0; i<cellCount; i++)
	 {
		  // create new cell
		  Cell<MV, PV> *newCell = 
					 new Cell<MV, PV>(this->configIni, *this, timeStamp, framesPerCycle);
		  
		  // set 'initial phase'
		  newCell->SetPhase(newBorn);

		  // set initial delay to disynchronize the cells
		  if (! configIni["testing"].present("cell initial G2 life"))
		  {
#ifdef GTGEN_WITH_STRONGSYNCHRONIZATION
				const float delayVariance=framesPerCycle/5;
#else
				const float delayVariance=framesPerCycle;
#endif
		  		newCell->SetDelay( (size_t)ceil(GetRandomUniform(0,delayVariance)) );
		  }
		  else
		  {
				REPORT("TESTING MODE: fixing initial G2 phase life to "
					<< (int)configIni["testing"]["cell initial G2 life"] << " frames.");
				newCell->SetDelay( (int)configIni["testing"]["cell initial G2 life"] );
		  }

		  // create its mask (respect the neighbourhood)
		  // and store it in the global scene
		  newCell->GenerateMaskInG2Phase();

#ifdef GTGEN_DEBUG
		  char fn[50];
		  sprintf(fn,"initial_mask_after_%.3lu.ics",i+1);
		  sceneMasks[0]->SaveImage(fn);
#endif
		  // create the texture and store it in the global scene
		  newCell->GenerateDotDistributionInG2Phase();

		  // insert the cell into scheduler
		  cells.push_back(newCell);
	 }

	 //construct the string and create the lock file
	 sprintf(lockFile,"mitopacq_PID%u.LOCK",getpid());

	 int file=creat(lockFile,O_RDONLY | S_IRUSR);
	 if (file == -1)
	   throw ERROR_REPORT("Can't create a lock file. How can I write the result of simulation then?");
	 else
	   close(file);
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
Scheduler<MV, PV>::~Scheduler()
{
	 DEBUG_REPORT("Destructing scheduler");
	 typename std::list<Cell<MV,PV>*>::iterator it;

	 for (it=cells.begin(); it!=cells.end(); it++)
	 {
		  DEBUG_REPORT("deleting cell with ID = " << (*it)->GetID());
		  delete (*it);
	 }

	 cells.clear();

	 typename std::vector<i3d::Image3d<MV>*>::iterator it_m;
				
	 for (it_m=sceneMasks.begin(); it_m != sceneMasks.end(); it_m++)
	 {
		  if (*it_m) delete (*it_m);
	 }

	 sceneMasks.clear();
				
#ifdef GTGEN_WITH_NUCLEIMASKS
	 for (it_m=sceneNuclei.begin(); it_m != sceneNuclei.end(); it_m++)
	 {
		  if (*it_m) delete (*it_m);
	 }

	 sceneNuclei.clear();
#endif

	 typename std::vector<i3d::Image3d<PV>*>::iterator it_p;
				
	 for (it_p=scenePhantoms.begin(); it_p != scenePhantoms.end(); it_p++)
	 {
		  if (*it_p) delete (*it_p);
	 }

	 scenePhantoms.clear();

#ifdef GTGEN_WITH_TERRITORIES
	 for (it_p=sceneTerritories.begin(); it_p != sceneTerritories.end(); it_p++)
	 {
		  if (*it_p) delete (*it_p);
	 }

	 sceneTerritories.clear();
#endif

	 DEBUG_REPORT("Saving spatiotemporal mask image");
	 // store the spatiotemporal mask image
	 if (!stMasks.empty())
	 {
		  // set up size and resolution
		  const size_t duration = stMasks.size();
		  const size_t width = stMasks.begin()->GetSizeX();
		  const size_t height = stMasks.begin()->GetSizeY();
		  i3d::Vector3d<float> resOfMasks = 
					 stMasks.begin()->GetResolution().GetRes();
		  i3d::Vector3d<float> res(resOfMasks.x, resOfMasks.y, 1);

		  // create the image
		  i3d::Image3d<MV> stMaskImageToSave;
		  stMaskImageToSave.MakeRoom(width, height, duration);
		  stMaskImageToSave.SetResolution(res);

		  // fill the image with individual slices
		  typename std::list<i3d::Image3d<MV>/**/>::iterator itMV;
		  size_t counter = 0;

		  for (itMV = stMasks.begin(); 
				 itMV != stMasks.end(); itMV++, counter++)
		  {
				stMaskImageToSave.SetSliceZ(*itMV, counter);
		  }

		  char fn[50];
		  sprintf(fn, "stMasksFromSlice%.3lu.%s", sliceOfInterest,SUFFIX);
		  stMaskImageToSave.SaveImage(fn);

		  // destroy the list
		  stMasks.clear();
	 }

	 DEBUG_REPORT("Saving spatiotemporal phantom image");
	 // store the spatiotemporal mask image
	 if (!stPhantoms.empty())
	 {
		  // set up size and resolution
		  const size_t duration = stPhantoms.size();
		  const size_t width = stPhantoms.begin()->GetSizeX();
		  const size_t height = stPhantoms.begin()->GetSizeY();
		  i3d::Vector3d<float> resOfPhantoms = 
					 stPhantoms.begin()->GetResolution().GetRes();
		  i3d::Vector3d<float> res(resOfPhantoms.x, resOfPhantoms.y, 1);

		  // create the image
		  i3d::Image3d<PV> stPhantomImageToSave;
		  stPhantomImageToSave.MakeRoom(width, height, duration);
		  stPhantomImageToSave.SetResolution(res);

		  // fill the image with individual slices
		  typename std::list<i3d::Image3d<PV>/**/>::iterator itPV;
		  size_t counter = 0;

		  for (itPV = stPhantoms.begin(); 
				 itPV != stPhantoms.end(); itPV++, counter++)
		  {
				stPhantomImageToSave.SetSliceZ(*itPV, counter);
		  }

		  char fn[50];
		  sprintf(fn, "stPhantomsFromSlice%.3lu.%s", sliceOfInterest,SUFFIX);
		  stPhantomImageToSave.SaveImage(fn);

		  // destroy the list
		  stPhantoms.clear();
	 }

#ifdef GTGEN_WITH_TERRITORIES
	 DEBUG_REPORT("Saving spatiotemporal territories image");
	 if (!stTerritories.empty())
	 {
		  // set up size and resolution
		  const size_t duration = stTerritories.size();
		  const size_t width = stTerritories.begin()->GetSizeX();
		  const size_t height = stTerritories.begin()->GetSizeY();
		  i3d::Vector3d<float> resOfTerritories = 
					 stTerritories.begin()->GetResolution().GetRes();
		  i3d::Vector3d<float> res(resOfTerritories.x, resOfTerritories.y, 1);

		  // create the image
		  i3d::Image3d<PV> stTerritoryImageToSave;
		  stTerritoryImageToSave.MakeRoom(width, height, duration);
		  stTerritoryImageToSave.SetResolution(res);

		  // fill the image with individual slices
		  typename std::list<i3d::Image3d<PV>/**/>::iterator itPV;
		  size_t counter = 0;

		  for (itPV = stTerritories.begin(); 
				 itPV != stTerritories.end(); itPV++, counter++)
		  {
				stTerritoryImageToSave.SetSliceZ(*itPV, counter);
		  }

		  char fn[50];
		  sprintf(fn, "stTerritoriesInAF.%s", SUFFIX);
		  stTerritoryImageToSave.SaveImage(fn);

		  // destroy the list
		  stTerritories.clear();
	 }
#endif

	 // write out the track record:
	 typename std::map<MV, TrackRecord<MV> /**/ >::iterator itTr;
	 std::ofstream of(fnameOfTracksTxt);

	 for (itTr = tracker.begin(); itTr != tracker.end(); itTr++)
	 {
		  of << itTr->first << " " 
					 << itTr->second.fromTimeStamp << " "
					 << itTr->second.toTimeStamp << " "
					 << itTr->second.parentID << std::endl;
	 }

	//remove the FFTW array with power noise
	if (pnArray) fftwf_free((fftwf_complex*)pnArray);

	//remove the lock file (without checking status of the removal)
	unlink(lockFile);
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::SOFA_InitialExport(void)
{
	//go through the cell list and ask each to do the export...
	typename std::list< Cell<MV,PV>* >::iterator iter=cells.begin();
	while (iter != cells.end())
	{
		(*iter)->SOFA_InitialExport();
		++iter;
	}
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::Run(const size_t noFrames)
{
	//minimum timeStamp that the simulation should reach
	const size_t stopTimeStamp=timeStamp+noFrames;

	//until the system has not moved enough forward...
	while (timeStamp < stopTimeStamp)
	{
		// also insert time stamp before every call of Cell::DoNextPhase()
		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("Generating frame no. " << timeStamp << " at "
			<< time_and_date_string << ", there are "
			<< cells.size() << " cells currently in the system");

		//look for the oldest cell
		size_t oldestTimePoint=INT_MAX;
		typename std::list< Cell<MV,PV>* >::iterator iterToRun=cells.begin();

		//go through the cell list
		typename std::list< Cell<MV,PV>* >::iterator iter=cells.begin();
		while (iter != cells.end()) {
			 /*
			  * old version - first come first served with priority
			  * controlled by the lenght of phase:
			  * size_t tP=(*iter)->GetTimePoint();
			  */

			/*
			 * new version - earliest deadline first
			 */
			size_t tP=(*iter)->GetTimePoint() 
					  + (*iter)->GetPhaseFrameBudget((*iter)->GetNextPhase());

			if (tP < oldestTimePoint) 
			{
				//new grandma cell ;-)
				oldestTimePoint=tP;
				iterToRun=iter;
			}
			++iter;
		}

		//ask the oldest cell to "move forward in time"
		//the cell will ask scheduler to prepare new scenes eventually
		(*iterToRun)->DoNextPhase();

		// the cell produced two new daughter cells, kill her
		// and remove from the list
		if ((*iterToRun)->GetCurrentPhase() == Cytokinesis)
		{
			 DEBUG_REPORT("deleting cell with ID = "
			 		<< (*iterToRun)->GetID() << ", after mitosis");
			 delete (*iterToRun);
			 cells.erase(iterToRun);
		}

		//just a report of how many cells are cometing "outside" the scene
		GetCellCometsLeavingFactor();

		//stops the simulation if user wants to
		UserAbort(lockFile);

		//anything to remove from the memory?
		ReleaseOldScenes();
	}
}

//-----------------------------------------------------------------------------
///helper local structure to aid sorting of cells according to some distance
template <class MV,class PV>
struct CSPair {
	CSPair(	Cell<MV,PV>* const CELL,
		const float DIST):
			cell(CELL), dist(DIST) {};

	Cell<MV,PV>* cell;
	float dist;
}; 

template <class MV, class PV> 
void Scheduler<MV, PV>::CreateCellComets(const size_t count, const float dist)
{
	//compute micron x,y coordinates of scene boundaries
	const float LEFT=sceneOffset.x;
	const float TOP=sceneOffset.y;
	const float RIGHT=sceneSize.x + LEFT;
	const float BOTTOM=sceneSize.y + TOP;

	//ordered lists of cells
	std::list< CSPair<MV,PV> /**/ > rightList;
	std::list< CSPair<MV,PV> /**/ > topList;
	std::list< CSPair<MV,PV> /**/ > leftList;
	std::list< CSPair<MV,PV> /**/ > bottomList;

	typename std::list<Cell<MV,PV>*>::const_iterator Cit=cells.begin();

	//initial filling
	float ldist=(*Cit)->GetCellCentrePosition().x - LEFT;
	leftList.push_back(CSPair<MV,PV>(*Cit,ldist));

	ldist=RIGHT - (*Cit)->GetCellCentrePosition().x;
	rightList.push_back(CSPair<MV,PV>(*Cit,ldist));

	ldist=(*Cit)->GetCellCentrePosition().y - TOP;
	topList.push_back(CSPair<MV,PV>(*Cit,ldist));

	ldist=BOTTOM - (*Cit)->GetCellCentrePosition().y;
	bottomList.push_back(CSPair<MV,PV>(*Cit,ldist));

	typename std::list< CSPair<MV,PV> >::iterator Sit;

	//move over the initially processed cell
	++Cit;

	//go over cells and sort them according to the respective distance
	for (; Cit!=cells.end(); ++Cit) {
		ldist=(*Cit)->GetCellCentrePosition().x - LEFT;
		Sit=leftList.begin();
		while ((Sit != leftList.end()) && (Sit->dist < ldist)) ++Sit;
		//list handles inserting even before end() position
		leftList.insert(Sit,CSPair<MV,PV>(*Cit,ldist));

		ldist=RIGHT - (*Cit)->GetCellCentrePosition().x;
		Sit=rightList.begin();
		while ((Sit != rightList.end()) && (Sit->dist < ldist)) ++Sit;
		rightList.insert(Sit,CSPair<MV,PV>(*Cit,ldist));

		ldist=(*Cit)->GetCellCentrePosition().y - TOP;
		Sit=topList.begin();
		while ((Sit != topList.end()) && (Sit->dist < ldist)) ++Sit;
		topList.insert(Sit,CSPair<MV,PV>(*Cit,ldist));

		ldist=BOTTOM - (*Cit)->GetCellCentrePosition().y;
		Sit=bottomList.begin();
		while ((Sit != bottomList.end()) && (Sit->dist < ldist)) ++Sit;
		bottomList.insert(Sit,CSPair<MV,PV>(*Cit,ldist));
	}

#ifdef GTGEN_DEBUG
	REPORT("Results of sorting towards right boundary:");
	Sit=rightList.begin();
	while (Sit != rightList.end()) {
		REPORT("ID: " << Sit->cell->GetID()
			<< ", comet state is " << Sit->cell->GetCometState() << " (nHTF)"
			<< ", distance=" << Sit->dist << " microns");
		++Sit;
	}
	REPORT("Results of sorting towards top boundary:");
	Sit=topList.begin();
	while (Sit != topList.end()) {
		REPORT("ID: " << Sit->cell->GetID()
			<< ", comet state is " << Sit->cell->GetCometState() << " (nHTF)"
			<< ", distance=" << Sit->dist << " microns");
		++Sit;
	}
	REPORT("Results of sorting towards left boundary:");
	Sit=leftList.begin();
	while (Sit != leftList.end()) {
		REPORT("ID: " << Sit->cell->GetID()
			<< ", comet state is " << Sit->cell->GetCometState() << " (nHTF)"
			<< ", distance=" << Sit->dist << " microns");
		++Sit;
	}
	REPORT("Results of sorting towards bottom boundary:");
	Sit=bottomList.begin();
	while (Sit != bottomList.end()) {
		REPORT("ID: " << Sit->cell->GetID()
			<< ", comet state is " << Sit->cell->GetCometState() << " (nHTF)"
			<< ", distance=" << Sit->dist << " microns");
		++Sit;
	}
#endif

	//compute threshold distances from respective boundaries, in microns
	const float xDist=dist*(RIGHT-LEFT);
	const float yDist=dist*(BOTTOM-TOP);

	//now, for every boundary choose up to the count first cells whose dist is lower 
	size_t added=0;
	Sit=rightList.begin();
	while ((added < count) && (Sit != rightList.end())) {
		if ((Sit->dist < xDist) && (Sit->cell->GetCometState() == noComet)) {
			Sit->cell->BecomeComet(rightBoundary);
			REPORT("Asking cell ID " << Sit->cell->GetID() << " to become right boundary comet");
			++added;
		}
		++Sit;
	}

	added=0;
	Sit=leftList.begin();
	while ((added < count) && (Sit != leftList.end())) {
		if ((Sit->dist < xDist) && (Sit->cell->GetCometState() == noComet)) {
			Sit->cell->BecomeComet(leftBoundary);
			REPORT("Asking cell ID " << Sit->cell->GetID() << " to become left boundary comet");
			++added;
		}
		++Sit;
	}

	added=0;
	Sit=topList.begin();
	while ((added < count) && (Sit != topList.end())) {
		if ((Sit->dist < yDist) && (Sit->cell->GetCometState() == noComet)) {
			Sit->cell->BecomeComet(topBoundary);
			REPORT("Asking cell ID " << Sit->cell->GetID() << " to become top boundary comet");
			++added;
		}
		++Sit;
	}

	added=0;
	Sit=bottomList.begin();
	while ((added < count) && (Sit != bottomList.end())) {
		if ((Sit->dist < yDist) && (Sit->cell->GetCometState() == noComet)) {
			Sit->cell->BecomeComet(bottomBoundary);
			REPORT("Asking cell ID " << Sit->cell->GetID() << " to become bottom boundary comet");
			++added;
		}
		++Sit;
	}
}

template <class MV, class PV> 
float Scheduler<MV, PV>::GetCellCometsLeavingFactor(void)
{
	int sumA=0, sumB=0;

	//go over all cells available in the population
	typename std::list<Cell<MV,PV>*>::const_iterator Cit=cells.begin();
	for (; Cit!=cells.end(); ++Cit) {

		switch ((*Cit)->GetCometState())
		{
			case noComet:
			case hesitating:
			case fromBoundary:
				++sumA;
				break;

			case toBoundary:
				++sumB;
				break;

			default:
				throw ERROR_REPORT("unknown cell comet state");
		}
	}

	DEBUG_REPORT("there are " << sumA << " 'living' and " << sumB
		<< " 'cut away' cells, ratio=" << (float)sumA / (float)sumB);

	return ( (float)sumA / (float)sumB );
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::PrintPhaseBudgets(void)
{
	REPORT("Overview of lengths of cell phases (in number of frames)\n" \
	   << "when mean cell cycle length is " << framesPerCycle << ".");

	REPORT("G1Phase: 3 times "
	   << this->GetPhaseFrameBudget(G1Phase_p1) << " frames, "
	   << 3*this->GetPhaseFrameBudget(G1Phase_p1) << " frames in total");
	REPORT("SPhase: 2 times "
	   << this->GetPhaseFrameBudget(SPhase_p1) << " frames, "
	   << 2*this->GetPhaseFrameBudget(SPhase_p1) << " frames in total");
	REPORT("G2Phase: "
	   << this->GetPhaseFrameBudget(G2Phase) << " frames");
	REPORT("Prophase: "
	   << this->GetPhaseFrameBudget(Prophase) << " frames");
	REPORT("Metaphase: "
	   << this->GetPhaseFrameBudget(Metaphase) << " frames");
	REPORT("Anaphase: "
	   << this->GetPhaseFrameBudget(Anaphase) << " frames");
	REPORT("Telophase: "
	   << this->GetPhaseFrameBudget(Telophase) << " frames");
	REPORT("Cytokinesis: "
	   << this->GetPhaseFrameBudget(Cytokinesis) << " frames");
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
size_t Scheduler<MV, PV>::GetInterphaseBudget(void)
{
	size_t sum=0;
	//compensation for the SetDelay() after Cytokinesis
	sum+=(size_t)ceilf(framesPerCycle * 0.05f);
	
	//regular budgets for the interphase phases
	sum+=3*this->GetPhaseFrameBudget(G1Phase_p1);
	sum+=2*this->GetPhaseFrameBudget(SPhase_p1);
	sum+=this->GetPhaseFrameBudget(G2Phase);

	return(sum);
}

//------------------------------------------------------------------
template <class MV, class PV> 
size_t Scheduler<MV, PV>::GetPhaseFrameBudget(const ListOfPhases &phase)
{
	 float budget;

	 switch (phase)
	 {
		  case G1Phase_p1: 
		  case G1Phase_p2: 
		  case G1Phase_p3: 
					 budget = (framesPerCycle/3) * (float)configIni["cell"]["duration of G1 phase"] / 100.0f;
					 break;
		  case SPhase_p1:
		  case SPhase_p2:
					 budget = (framesPerCycle/2) * (float)configIni["cell"]["duration of S phase"] / 100.0f;
					 break;
		  case G2Phase:
					 budget = framesPerCycle * (float)configIni["cell"]["duration of G2 phase"] / 100.0f;
					 break;
		  case Prophase:
					 budget = framesPerCycle * (float)configIni["cell"]["duration of prophase"] / 100.0f;
					 break;
		  case Metaphase:
					 budget = framesPerCycle * (float)configIni["cell"]["duration of metaphase"] / 100.0f;
					 break;
		  case Anaphase:
					 budget = framesPerCycle * (float)configIni["cell"]["duration of anaphase"] / 100.0f;
					 break;
		  case Telophase:
					 budget = framesPerCycle * (float)configIni["cell"]["duration of telophase"] / 100.0f;
					 break;
		  case Cytokinesis:
					 budget = framesPerCycle * (float)configIni["cell"]["duration of cytokinesis"] / 100.0f;
					 break;
		  default:	REPORT("Unknown cell phase.");
		  		budget = 1.0f;
	 }

	 budget = ceilf(budget);

	 // cannot be less than one frame!
	 return std::max(size_t(budget), size_t(1));
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::AddNewScenes(size_t noNewScenes)
{
	 if (this->sceneMasks.empty())
		  throw ERROR_REPORT("Array 'sceneMasks' has not been intialized yet.");

	 DEBUG_REPORT("asked to add " << noNewScenes << " frames");

	 i3d::Image3d<MV> *lastScene = this->sceneMasks.back();
	 i3d::Image3d<PV> *lastPhantom = this->scenePhantoms.back();

	 if ((!lastScene) || (!lastPhantom))
		throw ERROR_REPORT("Missing frame in 'sceneMasks' or in 'scenePhantoms' array. Not initialized?");

#ifdef GTGEN_WITH_NUCLEIMASKS
	i3d::Image3d<MV> *lastNuclei = this->sceneNuclei.back();
	if (!lastNuclei)
		throw ERROR_REPORT("Missing frame in 'sceneNuclei' array. Not initialized?");
#endif
#ifdef GTGEN_WITH_TERRITORIES
	i3d::Image3d<PV> *lastTerritory = this->sceneTerritories.back();
	if (!lastTerritory)
		throw ERROR_REPORT("Missing frame in 'sceneTerritories' array. Not initialized?");
#endif

	 for (size_t i=0; i<noNewScenes; i++)
	 {
		  // allocate new image and make it a copy of last scene mask
		  i3d::Image3d<MV> *newSceneMask = new i3d::Image3d<MV>(*lastScene);
		  this->sceneMasks.push_back(newSceneMask);

		  // allocate new empty image and make it as last scene phantom 
		  i3d::Image3d<PV> *newScenePhantom = new i3d::Image3d<PV>;
		  newScenePhantom->CopyMetaData(*lastPhantom);
		  newScenePhantom->Clear();
		  this->scenePhantoms.push_back(newScenePhantom);

#ifdef GTGEN_WITH_NUCLEIMASKS
		  // allocate also a new empty image for scene nuclei
		  i3d::Image3d<MV> *newSceneNuclei = new i3d::Image3d<MV>;
		  newSceneNuclei->CopyMetaData(*lastNuclei);
		  newSceneNuclei->Clear();
		  this->sceneNuclei.push_back(newSceneNuclei);
#endif
#ifdef GTGEN_WITH_TERRITORIES
		  // allocate also a new empty image for scene territories
		  i3d::Image3d<PV> *newSceneTerritory = new i3d::Image3d<PV>;
		  newSceneTerritory->CopyMetaData(*lastTerritory);
		  newSceneTerritory->Clear();
		  this->sceneTerritories.push_back(newSceneTerritory);
#endif
	 }

	 // increase the counter
	 newestScene += noNewScenes;

	 REPORT(newestScene-oldestScene+1 << " frames are kept in memory");
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::ReleaseOldScenes(bool releaseAll)
{
	 char fn[50];

	 if (this->sceneMasks.empty())
		  throw ERROR_REPORT("Array 'sceneMasks' has not been initialized yet.");

	 //oldest timeStamp, aka how much to actually release?
	 size_t oldestTimePoint=INT_MAX;

	 if (!releaseAll) {
		 //go through the cell list and look for the oldest one
		 typename std::list< Cell<MV,PV>* >::const_iterator iter=cells.begin();
		 while (iter != cells.end()) {
			size_t tP=(*iter)->GetTimePoint();
			if (tP < oldestTimePoint) oldestTimePoint=tP;
			++iter;
		 }
	 } else {
		//this will force the following code
		//to flush everything, no frame will be
		//kept in the memory
	 	oldestTimePoint=newestScene+1;
	 }

	 //anything to remove from memory?
	 while (oldestScene < oldestTimePoint) {
		if ((!sceneMasks[oldestScene]) || (!scenePhantoms[oldestScene]))
			throw ERROR_REPORT("Missing frame in 'sceneMasks' or in 'scenePhantoms' array. Already saved?");
#ifdef GTGEN_WITH_NUCLEIMASKS
		if (!sceneNuclei[oldestScene])
			throw ERROR_REPORT("Missing frame in 'sceneNuclei' array. Already saved?");
#endif
#ifdef GTGEN_WITH_TERRITORIES
		if (!sceneTerritories[oldestScene])
			throw ERROR_REPORT("Missing frame in 'sceneTerritories' array. Already saved?");
#endif

#ifdef GTGEN_WITH_NUCLEIMASKS
		//
		// exporting of nuclei masks
		//
		//establish filename, save to harddrive, clear from memory
		sprintf(fn, "sceneNuclei%.3lu.%s",oldestScene,SUFFIX);
		sceneNuclei[oldestScene]->SaveImage(fn);
		delete sceneNuclei[oldestScene];
		sceneNuclei[oldestScene]=NULL;
#endif

#ifdef GTGEN_WITH_TERRITORIES
		//
		// exporting of territories image
		//
		//first, the AF into the spatiotemporal image:
		i3d::Image3d<PV> oneTerritorySlice;
		i3d::Get_AF_XY(*sceneTerritories[oldestScene],oneTerritorySlice);
		stTerritories.push_back(oneTerritorySlice);
		oneTerritorySlice.DisposeData();

		//second, the territories image itself:
		//establish filename, save to harddrive, clear from memory
		sprintf(fn, "sceneTerritories%.3lu.%s", oldestScene,SUFFIX);
		sceneTerritories[oldestScene]->SaveImage(fn);
		delete sceneTerritories[oldestScene];
		sceneTerritories[oldestScene]=NULL;
#endif

		//
		// exporting ST images of phantoms and cell masks
		// (before they may get resampled...)
		//
		// Store one of its slices (do not save it now as we do not
		// now the z-depth of the spatiotemporal image). The final
		// flush to disk will be performed during calling the destructor
		// of scheduler.
		i3d::Image3d<PV> onePhantomSlice;
		scenePhantoms[oldestScene]->GetSliceZ(onePhantomSlice, sliceOfInterest);
		stPhantoms.push_back(onePhantomSlice);
		onePhantomSlice.DisposeData();

		i3d::Image3d<MV> oneMaskSlice;
		sceneMasks[oldestScene]->GetSliceZ(oneMaskSlice, sliceOfInterest);
		stMasks.push_back(oneMaskSlice);
		oneMaskSlice.DisposeData();

		//
		// exporting of ISOtropic phantom image
		//
		
		//establish filename, save to harddrive, clear from memory
		sprintf(fn, "scenePhantoms%.3lu.%s", oldestScene,SUFFIX);
		scenePhantoms[oldestScene]->SaveImage(fn);

		//
		// exporting of ISOtropic mask image
		//
		//establish filename, save to harddrive, clear from memory
		sprintf(fn, "sceneMasks%.3lu.%s", oldestScene,SUFFIX);
		sceneMasks[oldestScene]->SaveImage(fn);

#ifdef GTGEN_WITH_FINALIMAGES
		//
		// exporting of final (preview) image
		//
		// note that masks and phantoms will be resampled, if required, in both
		// the PrepareFinalImage() and PrepareFinalPreviewImage() functions
		i3d::Image3d<PV> finalImg;

#ifdef GTGEN_WITH_REAL_PSF
		PrepareFinalImage(*scenePhantoms[oldestScene],
								*sceneMasks[oldestScene],
								finalImg,
								configIni);
		sprintf(fn, "sceneFinal%.3lu.%s", oldestScene,SUFFIX);
#else
		PrepareFinalPreviewImage(*scenePhantoms[oldestScene],
										 *sceneMasks[oldestScene],
										 finalImg,
										 configIni);
		sprintf(fn, "sceneFinalPreview%.3lu.%s", oldestScene,SUFFIX);
#endif

		// report a SNR of the prepared image:
		ComputeSNR(finalImg,*sceneMasks[oldestScene]);

		// save the final (preview) image:
		finalImg.SaveImage(fn);
		finalImg.DisposeData();

#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 '.')
		sprintf(fn, "scenePhantoms%.3lu_nonISO.%s", oldestScene,SUFFIX);
		REPORT("saving also resampled phantom image under a new name " << fn);
		scenePhantoms[oldestScene]->SaveImage(fn);

		sprintf(fn, "sceneMasks%.3lu_nonISO.%s", oldestScene,SUFFIX);
		REPORT("saving also resampled mask image under a new name " << fn);
		sceneMasks[oldestScene]->SaveImage(fn);
#endif
#endif

		//
		// clearing memory after the phantom and mask images
		//
		delete scenePhantoms[oldestScene];
		scenePhantoms[oldestScene]=NULL;

		delete sceneMasks[oldestScene];
		sceneMasks[oldestScene]=NULL;

		REPORT("scene #" << oldestScene << " saved");

	 	++oldestScene;
	 }

	 DEBUG_REPORT("decided and removed " << oldestScene-timeStamp << " frames");
	 timeStamp=oldestScene;

	 REPORT(newestScene-oldestScene+1 << " frames are kept in memory");
}

//-----------------------------------------------------------------------------
// helper functions for the Scheduler::PnInitArray() functions

//threading helper stuff...
#ifdef _WIN32
	#include <windows.h>
	volatile HANDLE hMutex = CreateMutex(NULL,FALSE,NULL);
#else
	#include <pthread.h>
	pthread_mutex_t job_queue_mutexPN = PTHREAD_MUTEX_INITIALIZER;
#endif

void MUTEX_LOCK()
{
#ifdef _WIN32 
	WaitForSingleObject(hMutex,INFINITE);
#else
	pthread_mutex_lock(&job_queue_mutexPN);
#endif
}

void MUTEX_UNLOCK()
{
#ifdef _WIN32
	ReleaseMutex(hMutex);
#else
	pthread_mutex_unlock(&job_queue_mutexPN);
#endif
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::PnInitArray(i3d::Vector3d<size_t> const &size, const size_t frames,
					const char *fileName)
{
#ifdef GTGEN_WITH_MULTITHREADING
	// if required, init multi-threaded computing
	const size_t CPUs = i3d::GetNumberOfProcessors();
	DEBUG_REPORT("Going to use multiprocessing, found " << CPUs << " CPUs");

	MUTEX_LOCK();

	fftwf_init_threads();
	fftwf_plan_with_nthreads(CPUs);

	MUTEX_UNLOCK();
#endif
	// initialize random generator
	gsl_rng *r = gsl_rng_alloc(gsl_rng_default);
	unsigned long s=-1 * (int) time(NULL);
	DEBUG_REPORT("randomness started with seed " << s);
	gsl_rng_set(r,s);


	if (sceneRes.x != sceneRes.y)
		throw ERROR_REPORT("Sorry, I'm not implemented for anisotropy even in XY.");
	const float zCorrection=sceneRes.z / sceneRes.x;

	//helper constants
	const size_t size_xComp=size.x/2 +1; //x size of the complex image
	const size_t max_size = std::max(size_xComp,
				std::max(size.y, std::max(size.z, frames)));
	const float freqNormalization = max_size*max_size*max_size*max_size*max_size*max_size;
	#define gaussian_noise_sigma  5
	const size_t image_sz = 2 * size_xComp * size.y * size.z * frames;

	// allocate random Fourier image
	// first, free if not empty
	if (pnArray) fftwf_free((fftwf_complex*)pnArray);
	pnArray = (float*)fftwf_malloc(image_sz * sizeof(float));

	//enough memory?
	if (pnArray == NULL) {
		std::ostringstream err;
		err << "Not enough memory for power noise image of "
		    << image_sz * sizeof(float) << " Bytes";

		throw ERROR_REPORT(err.str());
	}

	DEBUG_REPORT("Starting to init the Fourier image of size "
			<< image_sz*sizeof(float)/(1<<20) << " MBytes");

	//working pointer
	float *buffer = pnArray;

#ifdef GTGEN_WITH_SOFA
	//higher leads to slower temporal changes
	const float TemporalFreqAttenuation=0.8f;
#else
	const float TemporalFreqAttenuation=0.66f;
#endif

	// initialize random Fourier image
	for (size_t t=0; t < frames; ++t) {
	 //note: the higher the K in the t*K+1 term, the slower the temporal changes are
	 //note: the K is now the TemporalFreqAttenuation
	 const float tFreq=((float)t*TemporalFreqAttenuation+1.0f)
	 						* ((float)t*TemporalFreqAttenuation+1.0f);

	 for (size_t z=0; z < size.z; ++z) {
	  const float ztFreq=((float)z*zCorrection+1.0f) * ((float)z*zCorrection+1.0f) +tFreq;

	  for (size_t y=0; y < size.y; ++y) {
	   const float yztFreq=((float)y+1.0f) * ((float)y+1.0f) +ztFreq;

	   for (size_t x=0; x < size_xComp; ++x) {
		//size of the frequency vector which is associated to the
		//coordinate [x,y,z,t] in this 4D Fourier image
		const float frequency = ((float)x+1.0f) * ((float)x+1.0f)  +yztFreq;

		//attenuating normalized amplitude (weight, in fact) ...
		const float amplitude = freqNormalization/(frequency*frequency*frequency);

		//... of some random complex pixel from the Fourier image
		//note: fftwf_complex = float[2]

		*buffer=amplitude*gsl_ran_gaussian(r, gaussian_noise_sigma); //real
		++buffer;
		*buffer=amplitude*gsl_ran_gaussian(r, gaussian_noise_sigma); //imaginary
		++buffer;
	   }
	  }
	 }
	}

	// close the random generator
	gsl_rng_free(r);

	DEBUG_REPORT("Starting inverse Fourier transform");

	//now, convert the prepared Fourier image into a spatial 'power noise' image
#ifdef GTGEN_WITH_MULTITHREADING
	MUTEX_LOCK();
#endif
	const int dims[4] = { (int)frames, (int)size.z, (int)size.y, (int)size.x };
	fftwf_plan plan = fftwf_plan_dft_c2r(4, dims, (fftwf_complex*)pnArray, pnArray, FFTW_ESTIMATE);
#ifdef GTGEN_WITH_MULTITHREADING
	MUTEX_UNLOCK();
#endif
	fftwf_execute(plan);
	fftwf_destroy_plan(plan);
	fftwf_cleanup();
#ifdef GTGEN_WITH_MULTITHREADING
	fftwf_cleanup_threads();
#endif

	DEBUG_REPORT("Starting to normalize the power noise image");

	// normalize the output
	float fmin = FLT_MAX;
	float fmax = FLT_MIN;
	double fmean=0;

	for (size_t t = 0; t < image_sz; ++t) {
		fmin = std::min(fmin, *(pnArray+t));
		fmax = std::max(fmax, *(pnArray+t));
		fmean+= *(pnArray+t);
	}
	fmean/=(double)image_sz;

	//divide the span by 2, to allow it to stretch over [-1:+1]
	//
	//we have observed, with PnPrintArrayStats(), that the
	//'power noise' distribution resembles Gaussian with 
	//std. variation of 0.25 (and mean zero)
	const float MaxMinSpan=(fmax-fmin)/2.f;
	for (size_t t = 0; t < image_sz; ++t)
		*(pnArray+t) = (*(pnArray+t)-fmean)/MaxMinSpan;
	//note: values now range from [-1.0 : 1.0]
	//note: mostly occuring values range from [-0.5 : 0.5], a 4-sigma interval

	//save metadata
	pnRealWidth=size.x;
	pnSizes[0]= 2 * size_xComp;
	pnSizes[1]=size.y;
	pnSizes[2]=size.z;
	pnSizes[3]=frames;

	DEBUG_REPORT("Created: RealWidth=" << pnRealWidth << ", pnSizes[]="
			<< pnSizes[0] << "," << pnSizes[1] << ","
			<< pnSizes[2] << "," << pnSizes[3]);

	if (fileName) {
		DEBUG_REPORT("Starting to save the cache file " << fileName);

		std::ofstream FLE;
		FLE.open(fileName,std::ios_base::binary);
		if (!FLE.is_open()) {
			std::ostringstream err;
			err << "Can't open file " << fileName << " for writing.";
			throw ERROR_REPORT(err.str());
		}

		/* text metadata */
		FLE << "x,y,z,t: " << pnSizes[0] << " " << pnSizes[1] << " "
		    << pnSizes[2] << " " << pnSizes[3] << "\n";
		FLE << "RealWidth: " << pnRealWidth << "\n";

		/* binary dara */
		FLE.write((const char*)pnArray,image_sz*sizeof(float));

		//everything OK?
		if (!FLE.good()) {
			std::ostringstream err;
			err << "Error writing data to file " << fileName << ".";
			throw ERROR_REPORT(err.str());
		}

		FLE.close();
/*
 * UNCOMMENT HERE TO EXPORT THE pnHints IMAGE ON HARDDISK
 *
		//export PN image:
		i3d::Image3d<float> pnHints;

		// helper constants
		// note: the pnArray 'power noise' image may be padded
		const size_t buffer_line_length = pnSizes[0];
		const size_t slice_sz = pnSizes[1] * buffer_line_length;
		const size_t frame_sz = pnSizes[2] * slice_sz;

		//init output image
		pnHints.MakeRoom(buffer_line_length,pnSizes[1],pnSizes[2]);
		pnHints.SetOffset(i3d::Offset(0.f,0.f,0.f));
		pnHints.SetResolution(i3d::Resolution(1.0f,1.0f,1.0f));
		float *pA=pnArray + 8*frame_sz;
		float *pH=pnHints.GetFirstVoxelAddr();

		for (int z=0; z < pnSizes[2]; ++z)
		  for (int y=0; y < pnSizes[1]; ++y)
		    for (int x=0; x < pnSizes[0]; ++x)
			 {
			 	*pH= *(pA + z*slice_sz + y*buffer_line_length + x);
				*pH-=*(pA + z*slice_sz + y*buffer_line_length + x -frame_sz);
				++pH;
			 }

		pnHints.SaveImage("hints.ics");
*/
	}

	DEBUG_REPORT("Finished");
	//the pnArray will be destroyed in the Scheduler::DESTRUCTOR
}


//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::PnInitArray(const char *fileName)
{
	if (!fileName)
		throw ERROR_REPORT("NULL pointer for fileName");

	DEBUG_REPORT("Starting to load from cache file " << fileName);

	std::ifstream FLE;
	FLE.open(fileName,std::ios_base::binary);
	if (!FLE.is_open()) {
		std::ostringstream err;
		err << "Can't open file " << fileName << " for reading.";
		throw ERROR_REPORT(err.str());
	}

	/* text version */
	char skipChar;
	for (int q=0; q < 8; ++q) FLE >> skipChar;
	FLE >> pnSizes[0] >> pnSizes[1] >> pnSizes[2] >> pnSizes[3];
	for (int q=0; q < 10; ++q) FLE >> skipChar;
	FLE >> pnRealWidth;

	DEBUG_REPORT("Discovered: RealWidth=" << pnRealWidth << ", pnSizes[]="
			<< pnSizes[0] << "," << pnSizes[1] << ","
			<< pnSizes[2] << "," << pnSizes[3]);

	//helper metainformation
	const size_t image_sz = pnSizes[0] * pnSizes[1] * pnSizes[2] * pnSizes[3];

	// allocate 'power noise' image
	// first, free if not empty
	if (pnArray) fftwf_free((fftwf_complex*)pnArray);
	pnArray = (float*)fftwf_malloc(image_sz * sizeof(float));

	//enough memory?
	if (pnArray == NULL) {
		std::ostringstream err;
		err << "Not enough memory for power noise image of "
		    << image_sz * sizeof(float) << " Bytes";

		throw ERROR_REPORT(err.str());
	}

	DEBUG_REPORT("Reading power noise image of size "
			<< image_sz*sizeof(float)/(1<<20) << " MBytes");

	//start reading right from the beginning of the third line
	//(for some reason it had to reinitialized like this...)
	FLE.seekg(0,std::ios_base::beg);
	FLE.getline((char*)pnArray,200);
	FLE.getline((char*)pnArray,200);
	FLE.read((char*)pnArray,image_sz*sizeof(float));
	
	//everything OK?
	if (!FLE.good()) {
		std::ostringstream err;
		err << "Error reading data from file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}

	FLE.close();

	DEBUG_REPORT("Finished");
	//the pnArray will be destroyed in the Scheduler::DESTRUCTOR
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::PnGetMeNonrigidHints(std::vector< i3d::Vector3d<float> /**/> const &BPList,
						const size_t BPLength,
						i3d::Image3d<float> &Hints)
{
	//consistency checks...
	if (!pnArray)
		throw ERROR_REPORT("'power noise' image (pnArray) is not ready");
	if (BPList.size() == 0)
		throw ERROR_REPORT("Empty list given");
	if (BPList.size() < BPLength)
		throw ERROR_REPORT("Empty list too short!?");
	if (pnSizes[3] == 0)
		throw ERROR_REPORT("'power noise' image (pnArray) is crippled, it is ready for zero frames");

	//find the (rectangular coordinate-system axis-aligned) bounding box
	//in microns...
	float xFrom=100000000000000.f;
	float yFrom=100000000000000.f;
	float zFrom=100000000000000.f;
	float xTo= -100000000000000.f;
	float yTo= -100000000000000.f;
	float zTo= -100000000000000.f;

	//sweep over the list and seek extremal coordinates
	for (size_t i=0; i < BPLength; ++i) {
		//tested position
		const float x=BPList[i].x;
		const float y=BPList[i].y;
		const float z=BPList[i].z;

		if (x < xFrom) xFrom=x;
		if (y < yFrom) yFrom=y;
		if (z < zFrom) zFrom=z;
		if (x > xTo) xTo=x;
		if (y > yTo) yTo=y;
		if (z > zTo) zTo=z;
	}

	DEBUG_REPORT("points span x=" << xFrom << " to " << xTo << "; y="
	       << yFrom << " to " << yTo << "; z=" << zFrom << " to " << zTo);

	//minimum required size of the pnArray in pixels
	const size_t xSize=(size_t)ceilf( (xTo-xFrom) * sceneRes.x );
	const size_t ySize=(size_t)ceilf( (yTo-yFrom) * sceneRes.y );
	const size_t zSize=(size_t)ceilf( (zTo-zFrom) * sceneRes.z );
	DEBUG_REPORT("span in pixels is " << xSize << "; " << ySize << "; " << zSize);

	if ((xSize > pnRealWidth) || (ySize > pnSizes[1]) || (zSize > pnSizes[2]))
		throw ERROR_REPORT("given cell is larger than 'power noise' image (pnArray)");

	//now, determine some random integer pixel offset
	const size_t xOffs=(size_t)floorf(GetRandomUniform(0,pnRealWidth-xSize));
	const size_t yOffs=(size_t)floorf(GetRandomUniform(0,pnSizes[1]-ySize));
	const size_t zOffs=(size_t)floorf(GetRandomUniform(0,pnSizes[2]-zSize));
	DEBUG_REPORT("since pnArray has size " << pnRealWidth << "," << pnSizes[1]
		     << "," << pnSizes[2] << ", will use offset " << xOffs << ","
	     	     << yOffs << "," << zOffs);

	//init output image
	Hints.MakeRoom(BPLength,pnSizes[3]-1,1);
	Hints.SetOffset(i3d::Offset(0.f,0.f,0.f));
	Hints.SetResolution(i3d::Resolution(1.0f,1.0f,1.0f));

	float *pH=Hints.GetFirstVoxelAddr();

	// helper constants
	// note: the pnArray 'power noise' image may be padded
	const size_t buffer_line_length = pnSizes[0];
	const size_t slice_sz = pnSizes[1] * buffer_line_length;
	const size_t frame_sz = pnSizes[2] * slice_sz;

	/*
	//for computing mean and variance values of used 'power noise' values
	double sum=0;
	double sum2=0;

	int histo[1001]; //TEST
	for (size_t i=0; i < 1001; ++i) histo[i]=0;
	*/

	//to access pixel at [x,y,z,t], one needs to look at
	// *(pnArray + t*frame_sz + z*slice_sz + y*buffer_line_length + x)
	// it has a type 'float'

	//make a copy of values we plan to use for the Hints image
	//and compute their stats at the same time
	for (size_t t=1; t < pnSizes[3]; ++t) {
	  float *pA=pnArray + t*frame_sz;

	  for (size_t i=0; i < BPLength; ++i, ++pH) {
		//convert absolute micron size to pixels within the pnArray
		const size_t x=(size_t)roundf((BPList[i].x-xFrom)*sceneRes.x) + xOffs;
		const size_t y=(size_t)roundf((BPList[i].y-yFrom)*sceneRes.y) + yOffs;
		const size_t z=(size_t)roundf((BPList[i].z-zFrom)*sceneRes.z) + zOffs;
		*pH= *(pA + z*slice_sz + y*buffer_line_length + x);
		*pH-=*(pA + z*slice_sz + y*buffer_line_length + x -frame_sz);

		/*
		sum+=*pH;
		sum2+=*pH * *pH;
		++histo[ (int)floorf(*pH*100.f)+500 ]; //TEST
		*/
	  }
	}

	/*
	//finish computations of the mean and std. variance
	double var=sum2 - sum*sum/(double)(BPLength*(pnSizes[3]-1));
	var=sqrt(var / (double)(BPLength*(pnSizes[3]-1)));

	DEBUG_REPORT("mean is " << sum/(double)(BPLength*(pnSizes[3]-1))
			<< ", std. variance is " << var);

	std::ofstream FLE("getme.stats"); //TEST
	for (size_t i=0; i < 1001; ++i) FLE << i << " " << histo[i] << "\n";
	FLE.close();
	*/

	//subtract means and stretch appropriately, row by row
	pH=Hints.GetFirstVoxelAddr();
	for (size_t t=1; t < pnSizes[3]; ++t) {
		//first, observe current situation in the row
		double mean=0.;
		float minPN=10000.f;
		float maxPN=-10000.f;
		for (size_t i=0; i < BPLength; ++i) {
			mean+=*(pH+i);
			if (*(pH+i) < minPN) minPN=*(pH+i);
			if (*(pH+i) > maxPN) maxPN=*(pH+i);
		}

		//plan changes:
		//mean to subtract
		const float fmean=static_cast<float>(mean/(double)BPLength);

		//stretch factor so that maximum detected shift of any boundary
		//point is cellNonRigidDeformation microns, new extremas:
		minPN=fabsf(minPN-fmean);
		maxPN=fabsf(maxPN-fmean);
		if (minPN > maxPN) maxPN=minPN;
		//note: maxPN now holds max(minPN,maxPN)
		const float stretch=(float)configIni["cell"]["cell nonrigid deformation"]/maxPN;
		//DEBUG_REPORT("row " << t-1 << "\t mean is " << fmean << ", stretch is " << stretch);

		//commit changes
		for (size_t i=0; i < BPLength; ++i, ++pH) *pH=(*pH-fmean)*stretch;
	}
}

//-----------------------------------------------------------------------------
template <class MV, class PV> 
void Scheduler<MV, PV>::PnPrintArrayStats(void)
{
	int histo[1001];
	for (size_t i=0; i < 1001; ++i) histo[i]=0;

	const size_t image_sz = pnSizes[0] * pnSizes[1] * pnSizes[2] * pnSizes[3];
	double mean=0;
	double sum2=0;
	for (size_t i=0; i < image_sz; ++i) {
		++histo[ (int)floorf(*(pnArray+i)*100.f)+500 ];

		mean+=*(pnArray+i);
		sum2+=*(pnArray+i) * *(pnArray+i);
	}
	double var=sum2 - mean*mean/(double)image_sz;
	var=sqrt(var / (double)image_sz);
	mean/=(double)image_sz;

	std::ofstream FLE("pnArray.stats");
	for (size_t i=0; i < 1001; ++i) FLE << i << " " << histo[i] << "\n";

	REPORT("mean is " << mean << ", std. variance is " << var);

	FLE.close();
}

//-----------------------------------------------------------------------------
// explicit instantiations

template class Scheduler<i3d::GRAY8,  i3d::GRAY16>;
template class Scheduler<i3d::GRAY16, i3d::GRAY16>;
