/**********************************************************************
* 
* cell.cpp
*
* This file is part of MitoGen
* 
* Copyright (C) 2013-2016 -- Centre for Biomedical Image Analysis (CBIA)
* http://cbia.fi.muni.cz/
* 
* MitoGen is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 
* MitoGen is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with MitoGen. If not, see <http://www.gnu.org/licenses/>.
* 
* Author: David Svoboda and Vladimir Ulman
* 
* Description: Definition of methods that implement the creation and
* life (cell cycle) of a cell model. The model is composed of 
* cytoplasm and nucleus.
*
***********************************************************************/

#include <map>
#include <float.h>

#include <i3d/transform.h>
#include <i3d/threshold.h>
#include <i3d/morphology.h>
#include <i3d/FastLevelSet.h>
#include <i3d/descriptors/shape.h>
#include <i3d/DistanceTransform.h>
#include <i3d/draw.h>

#include "cell.h"
#include "toolbox/rnd_generators.h"
#include "toolbox/deformations.h"
#include "toolbox/shape_rendering.h"
#include "toolbox/collisions.h"
#include "toolbox/hungarian/Hungarian.h"
#include "toolbox/hungarian/BipartiteGraph.h"
#include "toolbox/importexport.h"

//------------------------------------------------------------------
template <class ID_TYPE> ID_TYPE GetNewCellID(void) 
{
	static ID_TYPE lastCellID=0;

	return (++lastCellID);
}

//------------------------------------------------------------------
template <class MV, class PV>
float Cell<MV, PV>::GetBleachFactor(size_t initialTime, size_t currentTime)
{
	 if (currentTime < initialTime)
	 {
		  REPORT("initialTime: " << initialTime);
		  REPORT("currentTime: " << currentTime);
		  throw "Photobleaching -- bad time point";
	 } 

	 size_t frameDiff = currentTime - initialTime;
	 
	 // read constants
	 float photobleachingRate = configIni["fluorophore"]["photobleaching rate"];
	 float acquisitionTime = configIni["acquisition"]["time"]; // miliseconds 
	 
	 // how long the fluorophore molecules are exposed to light source 
	 // (number of seconds)
	 float totalTime = frameDiff * acquisitionTime / 1000.0f;
	 float multiplier = exp((-1.f) * photobleachingRate * totalTime);
	 
	 return multiplier;
}

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

	 switch (phase)
	 {
		  case G1Phase_p1: 
		  case G1Phase_p2: 
		  case G1Phase_p3: 
					 budget = (cellCycleLength/3) * durationOfG1Phase / 100.0f;
					 break;
		  case SPhase_p1:
		  case SPhase_p2:
					 budget = (cellCycleLength/2) * durationOfSPhase / 100.0f;
					 break;
		  case G2Phase:
					 budget = cellCycleLength * durationOfG2Phase / 100.0f;
					 break;
		  case Prophase:
					 budget = cellCycleLength * durationOfProphase / 100.0f;
					 break;
		  case Metaphase:
					 budget = cellCycleLength * durationOfMetaphase / 100.0f;
					 break;
		  case Anaphase:
					 budget = cellCycleLength * durationOfAnaphase / 100.0f;
					 break;
		  case Telophase:
					 budget = cellCycleLength * durationOfTelophase / 100.0f;
					 break;
		  case Cytokinesis:
					 budget = cellCycleLength * durationOfCytokinesis / 100.0f;
					 break;
		  default:	REPORT("Unknown cell phase.");
		  		budget = 1.0f;
	 }

	 budget = ceilf(budget);

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

//------------------------------------------------------------------
template <class MV, class PV> 
void Cell<MV,PV>::GenerateDotDistributionInG2Phase()
{
	 // Prepare distance map for solving the problem of dot/molecules
	 // escaping from the nucleus mask due to concavity. The distance 
	 // map created on demand.
	 //
	 // will be used a lot later but has to defined here "in the global space"
	 i3d::Image3d<float> *distanceMap = NULL;

	//obtain the list of nucleus and nucleoli boundary points either by importing it from the SOFA,
	//or by generating it
#ifdef GTGEN_WITH_SOFA
	 if ( ((int)configIni["sofa"]["export mode"] != 1)
	 	|| configIni["sofa"].present("crippled export mode that plays") )
	 {
		//we're in SOFA play mode, or in the crippled export...
		//see config file for an explanation
		DEBUG_REPORT("SOFA play: reading nucleus BPs and nucleoli BPs.");

	 	char fn[100];

		//nucleus
		sprintf(fn,(const char*)configIni["sofa"]["import name nucleus BP"],this->ID,0);
		scmNucleusOuterBPNumber=
			ImportAndResetPointList(scmNucleusBPList,&scmNucleusBPCentre,fn,15000);

#ifdef ENABLE_NUCLEOLI
		sprintf(fn,(const char*)configIni["sofa"]["import name nucleoli1 BP"],this->ID,0);
		scmNucleoli1OuterBPNumber=
				ImportAndResetPointList(scmNucleoli1BPList,&scmNucleoli1BPCentre,fn,500);

		sprintf(fn,(const char*)configIni["sofa"]["import name nucleoli2 BP"],this->ID,0);
		scmNucleoli2OuterBPNumber=
				ImportAndResetPointList(scmNucleoli2BPList,&scmNucleoli2BPCentre,fn,500);
#endif

		if (!configIni["sofa"].present("crippled export mode that plays"))
		{
			DEBUG_REPORT("SOFA play: reading chromatin points.");
			//chromatin
			sprintf(fn,(const char*)configIni["sofa"]["import name chromatin molecules"],this->ID,0);
			ImportAndResetPointList(chrDotList,NULL,fn,650000);

			chrDots=chrDotList.size() / chrCount;
			DEBUG_REPORT("import enforces to use " << chrDots << " molecules for a chromosome");

			if (chrDots*chrCount != chrDotList.size())
				throw ERROR_REPORT("New \"initial\" chromatin positions are not OK");

			//also move the phantom to the reference position
			SOFA_MoveImportedToReferencePosition();
		}
		else
		{
			 //this is a sort of cut-and-paste from the (pure) export mode branch of this function
			 i3d::Image3d<bool> nucleusMask;
			 GetLabelMask(*(scheduler.sceneMasks[this->timePoint]), 
							  this->ID,
							  nucleusMask);
			 nucleusMask.GetVoxelData()=false;
			 RenderBPListIntoMask(nucleusMask,true,  scmNucleusBPList,  scmNucleusBPCentre,  scmNucleusOuterBPNumber);
			 RenderBPListIntoMask(nucleusMask,false, scmNucleoli1BPList,scmNucleoli1BPCentre,scmNucleoli1OuterBPNumber);
			 RenderBPListIntoMask(nucleusMask,false, scmNucleoli2BPList,scmNucleoli2BPCentre,scmNucleoli2OuterBPNumber);
			 ChrInitialDistribution(nucleusMask, coverage);
			 DEBUG_REPORT("Seeds placed for the new chromosomes.");
			 for (size_t i=0; i < chrCount; i++) 
				  ChrRandomWalk(i, 0.13f, 2.6f, 0.2f, nucleusMask, distanceMap, true);
		}
	 }
	 else
#endif
	 {
	 DEBUG_REPORT("Generating nucleus mask.");
	 // Withdraw the cellMask from the labeled image and set it as
	 // an initial state of nucleusMask prior to the degenerative deformation
	 i3d::Image3d<bool> nucleusMask;
	 GetLabelMask(*(scheduler.sceneMasks[this->timePoint]), 
					  this->ID,
					  nucleusMask);

	 /*
	 //TODO VLADO : k cemu uz ted?
	 // create centrosomes in the cell mask
	 centrosomeA = new Dot;
	 centrosomeB = new Dot;

	 GenerateCentrosomes(nucleusMask, centrosomeA->pos, centrosomeB->pos);
	 // centrosomes created
	 */


	 i3d::fls::Routine<int> *routine = 
				i3d::fls::MCMRoutine(nucleusMask, NULL, 7);

	 routine -> Initialize(); 

	 // Surface of a cuboid (number of voxels)
	 size_t surface = (nucleusMask.GetSizeX() * nucleusMask.GetSizeY() +
							 nucleusMask.GetSizeY() * nucleusMask.GetSizeZ() +
							 nucleusMask.GetSizeZ() * nucleusMask.GetSizeX()) * 2; 
		 
	 // Empirically tested that one half (0.5) of the image 
	 // surface is a suitable number representing the nubmer of iterations.
	 routine -> Iterate((size_t)round(surface * 0.5f)); 
	 routine -> Mask(nucleusMask.begin(), 
						  i3d::fls::ResultCreator::InteriorAndInterface);

	 delete routine;

	 // regenerate nucleus boundary points from the nucleus mask
	 scmNucleusOuterBPNumber=ResetBoundaryPoints(scmNucleusBPList, 
								scmNucleusBPCentre, 
								nucleusMask);

	 DEBUG_REPORT("Nucleus mask created successfully.");

#ifdef ENABLE_NUCLEOLI
	 DEBUG_REPORT("Generating nucleoli.");

	 i3d::Image3d<bool> nucleolus1, nucleolus2;

	 GenerateNucleolus(nucleusMask, 
							 nucleolusDiameter, 
							 nucleolus1);

	 scmNucleoli1OuterBPNumber = ResetBoundaryPoints(scmNucleoli1BPList,
																	 scmNucleoli1BPCentre,
																	 nucleolus1);

	 GenerateNucleolus(nucleusMask, 
							 nucleolusDiameter, 
							 nucleolus2);

	 scmNucleoli2OuterBPNumber = ResetBoundaryPoints(scmNucleoli2BPList,
																	 scmNucleoli2BPCentre,
																	 nucleolus2);

	if ((!scmNucleoli1OuterBPNumber) || (!scmNucleoli2OuterBPNumber))
	{
		//rather stop and re-run than wait hours for inevitable disaster
		throw ERROR_REPORT("Some nucleolus, or both of them, was not created.");
	}

	/*
	//TEST TODO DISABLE LATER
	sprintf(fn, "ID%.2u_Tini_mask_nucleolus1.ics", this->ID);
	nucleolus1.SaveImage(fn);
	sprintf(fn, "ID%.2u_Tini_mask_nucleolus2.ics", this->ID);
	nucleolus2.SaveImage(fn);
	*/

	 DEBUG_REPORT("Nucleoli generated successfully.");
#endif

	 // insert molecules of fluorophore (these visualize chromatine)
	 // into the nucleus. Due to the previous generation of nucleoli,
	 // the mask also contains holes representing nucleoli
	 ChrInitialDistribution(nucleusMask, coverage);
	 DEBUG_REPORT("Seeds placed for the new chromosomes.");

	 //it is vital for ChrRandomWalk() that chrCentres are set correctly now,
	 //but ChrInitialDistribution() fortunately provides that

	 // Spread the molecules around the central point using the algorithm
	 // 'random walk' with (spherical) boundaries and some tolerance
	 // for boundaries to prevent sharp edges revealing shape of sphere
	 for (size_t i=0; i < chrCount; i++) 
	 {
		  ChrRandomWalk(i, 0.13f, 2.6f, 0.2f, nucleusMask, distanceMap, true);
	 }

	 } // after if "SOFA play mode?" statement


	 // destroy the object if allocated before
	 if (distanceMap)
		  delete distanceMap;

	 ChrUpdateStats();

#ifdef GTGEN_WITH_NUCLEIMASKS
	 //write also the initial nucleus mask into the sceneNuclei
	 RenderBPListIntoMask(*(scheduler.sceneNuclei[timePoint]),(MV)this->ID,
		scmNucleusBPList,scmNucleusBPCentre,scmNucleusOuterBPNumber);
#endif
	/*
	//TEST TODO DISABLE LATER
	char fn[50];
	sprintf(fn, "ID%.2u_Tini_mask_nucleus.ics", this->ID);
	nucleusMask.SaveImage(fn);
	*/

	  // fill in also the initial phantom image
	  ChrRenderIntoPhantoms(*scheduler.scenePhantoms[0]);
#ifdef GTGEN_WITH_TERRITORIES
	  ChrRenderIntoTerritories(*scheduler.sceneTerritories[0]);
#endif

#ifdef GTGEN_WITH_FLOWFIELDS
	char fn[100];
	sprintf(fn,"ID%02u_T000_chromatinPoints.txt", ID );
	ExportPointList(chrDotList,chrDotList.size(),fn);
#endif

	 DEBUG_REPORT("Initial generation of dots and molecules finished.");
}

//------------------------------------------------------------------
template <class MV, class PV> 
void Cell<MV,PV>::ReadMaskInG2Phase(const i3d::Image3d<MV> &cellMask,
												const MV templateID)
{
	 if (scheduler.sceneMasks.empty())
		  throw ERROR_REPORT("Array 'sceneMasks' not intialized.");
	 if (scheduler.newestScene < this->timePoint)
	 	throw ERROR_REPORT("Array 'sceneMasks' is not ready, it is too behind.");
	 if (scheduler.oldestScene > this->timePoint)
	 	throw ERROR_REPORT("Array 'sceneMasks' is not ready, it was already stored on disk.");

	//obtain the list of cell boundary points either by importing it from the SOFA,
	//or by "reading" it from the given mask image
#ifdef GTGEN_WITH_SOFA
	 if ( ((int)configIni["sofa"]["export mode"] != 1)
	 	|| configIni["sofa"].present("crippled export mode that plays") )
	 {
	 	//SOFA play mode, or in the crippled export...
		DEBUG_REPORT("SOFA play: reading cell BPs.");

	 	char fn[100];
		sprintf(fn,(const char*)configIni["sofa"]["import name nucleus BP"],this->ID,0);
		scmCellOuterBPNumber=
			ImportAndResetPointList(scmCellBPList,&scmCellBPCentre,fn,15000);

		if (!configIni["sofa"].present("crippled export mode that plays"))
		{
			//first, we need to init the reference position
			SOFA_InitReferencePosition();

			//now, move the cell to it
			SOFA_MoveInitialToReferencePosition();
		}
	 }
	 else
#endif
	 {
	 REPORT("Using mask image ID=" << templateID
	 	<< " for this cell with ID=" << this->ID);

	 //obtain the list of BP points from the supplied image
	 scmCellOuterBPNumber=ResetBoundaryPoints(scmCellBPList,scmCellBPCentre,cellMask,templateID);
	 }

	 //to have all masks consistently generated, we must genereate even this initial mask
	 //with the ScmRenderCellIntoMask() function, which needs to know the CellBP vectors
	 ScmRenderCellIntoMask(*scheduler.sceneMasks[this->timePoint]);

	 //also init the cell polarity vector
	 ScmResetCellPolarityVector();

	 //'power noise' hints for non-rigid cell deformation will be
	 //prepared right before they would be required
}

//------------------------------------------------------------------
template <class MV, class PV> 
void Cell<MV,PV>::GenerateMaskInG2Phase()
{
	 if (scheduler.sceneMasks.empty())
		  throw ERROR_REPORT("Array 'sceneMasks' not intialized.");
	 if (scheduler.newestScene < this->timePoint)
	 	throw ERROR_REPORT("Array 'sceneMasks' is not ready, it is too behind.");
	 if (scheduler.oldestScene > this->timePoint)
	 	throw ERROR_REPORT("Array 'sceneMasks' is not ready, it was already stored on disk.");

	//obtain the list of cell boundary points either by importing it from the SOFA,
	//or by generating it
#ifdef GTGEN_WITH_SOFA
	 if ( ((int)configIni["sofa"]["export mode"] != 1)
	 	|| configIni["sofa"].present("crippled export mode that plays") )
	 {
	 	//SOFA play mode, or in the crippled export...
		DEBUG_REPORT("SOFA play: reading cell BPs.");

	 	char fn[100];
		sprintf(fn,(const char*)configIni["sofa"]["import name nucleus BP"],this->ID,0);
		scmCellOuterBPNumber=
			ImportAndResetPointList(scmCellBPList,&scmCellBPCentre,fn,15000);

		if (!configIni["sofa"].present("crippled export mode that plays"))
		{
			//first, we need to init the reference position
			SOFA_InitReferencePosition();

			//now, move the cell to it
			SOFA_MoveInitialToReferencePosition();
		}
	 }
	 else
#endif
	 {
	 i3d::Image3d<bool> cellMask;

	 RenderBlankCellMask(scheduler.GetSceneRes(), cellMask, cellDiameter);

	 i3d::Image3d<bool> occupiedSpace, occupiedSpaceB;
	 i3d::Threshold(*(scheduler.sceneMasks[this->timePoint]), occupiedSpace, MV(1));

	 //enforce some minimal distance between cells by dilating the occupied space mask
	 i3d::Dilation(occupiedSpace, occupiedSpaceB, i3d::nb3D_26);
	 i3d::Dilation(occupiedSpaceB, occupiedSpace, i3d::nb3D_26);
	 i3d::Dilation(occupiedSpace, occupiedSpaceB, i3d::nb3D_26);
	 occupiedSpace.DisposeData();

	 i3d::Vector3d<float> *fPos = FindPosition(cellMask, occupiedSpaceB);

	 if (fPos == NULL)
		  throw ERROR_REPORT("VOI too small for new cell");

	 i3d::Vector3d<float> centre = i3d::PixelsToMicrons(cellMask.GetSize(), 
																		 cellMask.GetResolution()) / 2.0f;

	 DEBUG_REPORT("shift: " << *fPos - centre);

	 cellMask.SetOffset(*fPos - centre);
	 delete fPos;

	 //obtain the list of BP points from the generated image
	 scmCellOuterBPNumber=ResetBoundaryPoints(scmCellBPList,scmCellBPCentre,cellMask);
	 }

	 //to have all masks consistently generated, we must genereate even this initial mask
	 //with the ScmRenderCellIntoMask() function, which needs to know the CellBP vectors
	 ScmRenderCellIntoMask(*scheduler.sceneMasks[this->timePoint]);

	 //also init the cell polarity vector
	 ScmResetCellPolarityVector();

	 //'power noise' hints for non-rigid cell deformation will be
	 //prepared right before they would be required
}

//------------------------------------------------------------------

#ifdef ENABLE_NUCLEOLI

template <class MV, class PV>
void Cell<MV, PV>::GenerateNucleolus(i3d::Image3d<bool> &nucleusMask,
												 float nucleolusSize,
												 i3d::Image3d<bool> &nucleolusMask)
{
	 i3d::Vector3d<float> centre, dims, *fPos = NULL;
	 size_t attempt = 0;

	 do 
	 { 
		  if (attempt > 0)
		  {
				nucleolusSize *= 0.9f;
		  }

		  i3d::Vector3d<float> r; 
		  
		  r.x = GetRandomGauss (nucleolusSize/2.0f, nucleolusSize/18.0f);
		  r.y = nucleolusSize/2.0f + (nucleolusSize/2.0f - r.x);
		  r.z = GetRandomGauss (nucleolusSize/3.0f, nucleolusSize/18.0f); 
		  
		  float xy_rotation = GetRandomGauss(0.0f, 45.0f);
		  float xz_rotation = GetRandomGauss(0.0f, 45.0f); 
		  
		  i3d::Vector3d<size_t> sz;
		  float max_r = std::max(r.x, std::max(r.y, r.z));

		  centre = i3d::Vector3d<float>(2.0f*max_r);
		  dims = centre * 2.0f; 
		  
		  sz = i3d::MicronsToPixels(dims, scheduler.GetSceneRes());

		  nucleolusMask.MakeRoom(sz); // plus some boundary
		  nucleolusMask.SetResolution(scheduler.GetSceneRes());
		  nucleolusMask.Clear(); 
		  
		  // create the cellMask object
		  RenderEllipsoid (nucleolusMask, centre, r, xy_rotation, xz_rotation, true); 
		  
		  // slightly deform the initial object
		  i3d::Image3d<float> speedImg;
		  speedImg.CopyMetaData(nucleolusMask); 

		  // store the data in case the deformation eliminates the object
		  i3d::Image3d<bool> nucleolusBackup = nucleolusMask;
		  
		  GenerateSpeedImage(speedImg, (r.x + r.y + r.z)/4.5f);
		  float deformationLevel = 0.2f;
		  Deformation(nucleolusMask, NULL, speedImg, deformationLevel); 

		  // test if the deformation has (not) destroyed the shape
		  // of nucleolus
		  {
				size_t counter = 0;
				for (size_t i=0; i<nucleolusMask.GetImageSize(); i++)
				{
					 if (nucleolusMask.GetVoxel(i))
						  counter++;
				}

				// deformation eliminated the nucleolus
				if (counter == 0)
				{
					 DEBUG_REPORT("Deformation would destroy nucleolus. Skipping.");
					 nucleolusMask = nucleolusBackup;
				}
		  }
		  
		  i3d::Image3d<bool> occupiedSpace(nucleusMask);
		  i3d::Negative(occupiedSpace);
		  
		  fPos = FindPosition(nucleolusMask, occupiedSpace, 0.0f, false);

		  attempt++;

		  DEBUG_REPORT("attempt number: " << attempt);
	 } while ((fPos == NULL) && (attempt < 3));

	 if (fPos == NULL)
	 {
		  throw ERROR_REPORT("VOI too small for new nucleolus.");
	 }

	 DEBUG_REPORT("shift: " << *fPos - centre);

	 nucleolusMask.SetOffset(*fPos - centre);

	 SubtractImageRespectingItsOffset(nucleusMask, nucleolusMask);

	 delete fPos;
}

#endif

//------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::ShowDomainForThisPhase(const i3d::Image3d<bool> &mask,
													  i3d::Image3d<bool> &domain,
													  size_t noFrames)
{
	domain.CopyMetaData(*scheduler.sceneMasks[timePoint]);
	domain.GetVoxelData() = true; // initial state of domain
	
	for (size_t i=timePoint+1; i<timePoint+1+noFrames; i++)
	{
		 // modify the domain
		 for (size_t j=0; j<scheduler.sceneMasks[i]->GetImageSize(); j++)
		 {
			  if ((scheduler.sceneMasks[i]->GetVoxel(j) != 0) &&
					(scheduler.sceneMasks[i]->GetVoxel(j) != this->ID))
			  {
					domain.SetVoxel(j, false);
			  }
		 }
	}

	// use only a part of domain (do not waste with CPU and memory)
	i3d::Vector3d<float> off = mask.GetOffset() - domain.GetOffset();
	i3d::Vector3d<float> res = mask.GetResolution().GetRes();
	i3d::VOI<i3d::PIXELS> maskVOI;

	maskVOI.offset.x = static_cast<i3d::PIXELS::T_offset>(roundf(off.x * res.x));
	maskVOI.offset.y = static_cast<i3d::PIXELS::T_offset>(roundf(off.y * res.y));
	maskVOI.offset.z = static_cast<i3d::PIXELS::T_offset>(roundf(off.z * res.z));
	maskVOI.size = mask.GetSize();

	i3d::Image3d<bool> reducedDomain;
	reducedDomain.CopyFromVOI(domain, maskVOI, true, false);

	i3d::Image3d<bool> erodedDomain;
	i3d::Erosion(reducedDomain, erodedDomain, i3d::nb3D_26);
	domain = erodedDomain;

	// consistency check:
	size_t errCounter = 0;

	for (size_t i=0; i<domain.GetImageSize(); i++)
	{
		 if ((domain.GetVoxel(i) == false) &&
			  (mask.GetVoxel(i) == true))
		 {
			  domain.SetVoxel(i, true);
			  errCounter++;
		 }
	}

	if (errCounter > 0)
	{
		 DEBUG_REPORT("Deformation domain did not contain the whole cell mask.");
		 DEBUG_REPORT("The difference is: " << errCounter << "(voxels).");
		 DEBUG_REPORT("May be due to redundant domain erosion :)");
	}

}

//------------------------------------------------------------------
template <class MV, class PV>
ListOfPhases Cell<MV, PV>::GetNextPhase() const
{
	 ListOfPhases nextPhase; 

	 switch (currentPhase)
	 {
		case G1Phase_p1: nextPhase = G1Phase_p2; break;
		case G1Phase_p2: nextPhase = G1Phase_p3; break;
		case G1Phase_p3: nextPhase = SPhase_p1; break;
		case SPhase_p1: nextPhase = SPhase_p2; break;
		case newBorn:
		case SPhase_p2: nextPhase = G2Phase; break;
		case G2Phase: nextPhase = Prophase; break;
		case Prophase: nextPhase = Metaphase; break;
		case Metaphase: nextPhase = Anaphase; break;
		case Anaphase: nextPhase = Telophase; break;
		case Telophase: nextPhase = Cytokinesis; break;
		case Cytokinesis: nextPhase = G1Phase_p1; break;
		default: throw 
			ERROR_REPORT("Out of cell cycle ... may be apoptosis? :)");
	 }

	 return nextPhase;
}

//------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::DoNextPhase(void)
{
	 const ListOfPhases oldPhase=this->currentPhase;

#ifndef GTGEN_WITH_SOFA
	 // switch to next phase in the cell cycle
	 this->currentPhase = GetNextPhase();
#else
	 //SOFA not supported in other phases so far...
	 this->currentPhase = G2Phase;
#endif

	 size_t frameBudget=delay;
	 delay=0;

	 //if I'm 'newBorn' do not add the G2 phase real budget, "do" only the delay...
	 if (oldPhase != newBorn) 
	 	frameBudget += GetPhaseFrameBudget(currentPhase);

	 // do we have this->scheduler.scene* images prepared?
	 // if not, we must generate them by calling the 'AddNewScenes' routine
	 if (timePoint+frameBudget > scheduler.newestScene)
	 {
		  scheduler.AddNewScenes(timePoint+frameBudget - scheduler.newestScene);
	 } 
	 
	 // call the phase, which incrementally updates the this->timePoint
	 switch (currentPhase)
	 {
		  case G1Phase_p1:
				DoG1Phase(3*frameBudget,0,frameBudget);
				break;
		  case G1Phase_p2:
				DoG1Phase(3*frameBudget,frameBudget,2*frameBudget);
				break;
		  case G1Phase_p3:
				DoG1Phase(3*frameBudget,2*frameBudget,3*frameBudget);
				break;
		  case SPhase_p1:
				DoSPhase(2*frameBudget,0,frameBudget);
				break;
		  case SPhase_p2:
				DoSPhase(2*frameBudget,frameBudget,2*frameBudget);
				break;
		  case G2Phase:
				DoG2Phase(frameBudget);
				break;
		  case Prophase:
				DoProphase(frameBudget);
				break;
		  case Metaphase:
				DoMetaphase(frameBudget);
				break;
		  case Anaphase:
				DoAnaphase(frameBudget);
				break;
		  case Telophase:
				DoTelophase(frameBudget);
				break;
		  case Cytokinesis:
				DoCytokinesis(frameBudget);
				break;
		  default: throw 
					  ERROR_REPORT("Out of cell cycle ... may be apoptosis? :)");
	 }

	// now, the cell has probably different shape and different position in the sceneMasks...
	// it should "softly" reserve its new position in any already existing future sceneMasks
	// (while replacing its former and now invalid reservations)
	//
	// but only if we are not returning from cytokinesis, which has already provided the update
	if (currentPhase != Cytokinesis) {
		size_t cellTP=this->timePoint+1;
		while (cellTP <= scheduler.newestScene) {
			DEBUG_REPORT("updating reservation in sceneMask # " << cellTP);

			ScmRemoveCellFromMask(*scheduler.sceneMasks[cellTP]);
			SoftRenderBPListIntoMask(*scheduler.sceneMasks[cellTP],this->ID,
					scmCellBPList,scmCellBPCentre,scmCellOuterBPNumber);
			//note: the SoftRenderBPListIntoMask() does not check for FF leaks
			//but it is not required here as the phase-functions were ending by
			//rendering the cell into sceneMasks image with testing for FF leaks,
			//hence if we got here, no FF leak has occured...
			cellTP++;
		}
		DEBUG_REPORT("changed reservations in " << scheduler.newestScene-timePoint << " sceneMasks");
	} else {
		DEBUG_REPORT("no reservation changes cause these were already done in the Cytokinesis phase atomic function");
	}
}

//-------------------------------------------------------------------
void GetCellPolarityAndCentreVector(const i3d::Image3d<bool> &cellMask,
												i3d::Vector3d<float> &polVec,
												i3d::Vector3d<float> &centre)
{
	 i3d::Shape<bool> shape;
	 shape.SetImage(&cellMask);
	 shape.SetLabel(true);

	 DEBUG_REPORT("Computing principal axes");
	 i3d::PrincipalAxes pas = shape.GetPrincipalAxes();

	 DEBUG_REPORT("axis1: " << pas.axe1);
	 DEBUG_REPORT("axis2: " << pas.axe2);
	 DEBUG_REPORT("axis3: " << pas.axe3);

	 DEBUG_REPORT("val1: " << pas.val1);
	 DEBUG_REPORT("val2: " << pas.val2);
	 DEBUG_REPORT("val3: " << pas.val3);

	 // Find the maximum eigenvalue and its eigenvector:
	 float max_val = std::max(pas.val1, std::max(pas.val2, pas.val3));

	 if (pas.val1 == max_val)
	 {
		  polVec = pas.axe1;
	 }
	 else if (pas.val2 == max_val)
	 {
		  polVec = pas.axe2;
	 }
	 else
	 {
		  polVec = pas.axe3;
	 }

	 polVec /= i3d::Norm(polVec);

	 DEBUG_REPORT("polarity vector is: " << polVec);

	 centre= i3d::PixelsToMicrons(shape.Centroid(), cellMask.GetResolution())
	 			+ cellMask.GetOffset();

	 DEBUG_REPORT("centroid (in microns): "  << centre);
}

//-------------------------------------------------------------------
void GenerateCentrosomes(const i3d::Image3d<bool> &cellMask,
								 i3d::Vector3d<float> &centrosomeA,
								 i3d::Vector3d<float> &centrosomeB)
{
	 DEBUG_REPORT("Generating centrosomes");
	 i3d::Resolution res = cellMask.GetResolution();
	 i3d::Offset off = cellMask.GetOffset();

	 // Centroid and the eigenvector corresponding to the highest eigenvalue
	 // form the line on which the centrosomes should naturally appear.
	 i3d::Vector3d<float> max_vec, cell_center;
	 GetCellPolarityAndCentreVector(cellMask,max_vec,cell_center);

	 // Now, we will compute the distance map. It will help us to find 
	 // the centrosomes in the specific distance from the border of the cell
	 i3d::Image3d<float> fimg;
	 fimg.CopyMetaData(cellMask);
	 fimg.Clear();

	 for (size_t i=0; i<fimg.GetImageSize(); i++)
	 {
		  if (cellMask.GetVoxel(i) == false)
		  {
				fimg.SetVoxel(i, 1.0f);
		  }
	 }

	 DEBUG_REPORT("Doing distance transform");
	 i3d::EDM(fimg);

	/*
	//TEST TODO DISABLE LATER
	static int ID=1;
	char fn[50];
	sprintf(fn, "ID%.2u_Tini_DTcellmask.ics", ID++);
	fimg.SaveImage(fn);
	*/

	 // Let the centrosomes are located 'n' microns from the cell membrane;
	 // and let 'n' equal to 30% of the distance of the cell centre from the membrane
	 const float dist=0.3f * fimg.GetVoxel(i3d::MicronsToPixels(cell_center-off,res));

	 //TODO: go along the max_vec until cell end is reached, put centrosome in the middle
	 //between this edge point and the cell centre

	 // Generate the position of 'centrosome A'
	 size_t skip = 0;
	 i3d::Vector3d<size_t> i_pos;

	 DEBUG_REPORT("max dist: " << fimg.GetVoxelData().max());

	 do {
		  centrosomeA = cell_center + max_vec * (float)skip;
		  i_pos = i3d::MicronsToPixels(centrosomeA - off, res);
		  skip++;
	 } while (fimg.GetVoxel(i_pos) > dist);

	 DEBUG_REPORT("centrosome A: " << centrosomeA);

	 // Generate the position of 'centrosome B'
	 skip = 0;

	 do {
		  centrosomeB = cell_center - max_vec * (float)skip;
		  i_pos = i3d::MicronsToPixels(centrosomeB - off, res);
		  skip++;
	 } while (fimg.GetVoxel(i_pos) > dist);

	 DEBUG_REPORT("centrosome B: " << centrosomeB);

	 DEBUG_REPORT("centrosomes generated.");

	 if (centrosomeA == centrosomeB)
	 {
		  cellMask.SaveImage("maska.ics");
		  throw ERROR_REPORT("stejne centrosomy :-/");
	 }
}

//----------------------------------------------------------------------
void SplitMaskIntoTwoHalves(const i3d::Image3d<bool> &cellMask,
									 i3d::Image3d<bool> &leftMask,
									 i3d::Image3d<bool> &rightMask,
									 const i3d::Vector3d<float> &centrosomeA,
									 const i3d::Vector3d<float> &centrosomeB)
{
	 // resolution of the image
	 i3d::Resolution res = cellMask.GetResolution();
	 i3d::Offset off = cellMask.GetOffset();

	 // centrosomes are used to define metaphase plate
	 i3d::Vector3d<float> center = (centrosomeA + centrosomeB)/2.0f;
	 i3d::Vector3d<float> norm_vec = centrosomeA - centrosomeB;

	 // norm vector of metaphase plate (plane)
	 norm_vec /= i3d::Norm(norm_vec);

	 // Finally, we will generate the metaphase plate (all its voxels):
	 // equation of plane in 3D: ax + by + cz + d = 0
	 float a = norm_vec.x;
	 float b = norm_vec.y;
	 float c = norm_vec.z;
	 float d = -(a * center.x + 
					 b * center.y +
					 c * center.z);

	 leftMask.CopyMetaData(cellMask);
	 leftMask.Clear();

	 rightMask.CopyMetaData(cellMask);
	 rightMask.Clear();
	
	 // go through all the image voxels, test if they belong to cell mask.
	 // If so, test, if they belong to metaphase plate (equatorial plate)
	 for (size_t i=0; i<cellMask.GetImageSize(); i++)
	 {
		  if (cellMask.GetVoxel(i))
		  {
				i3d::Vector3d<size_t> i_pos = cellMask.GetPos(i);
				i3d::Vector3d<float> f_pos = PixelsToMicrons(i_pos, res) + off;

				// rasterize the plane with the subpixel (1/2 pixel) precision
				if (a * f_pos.x + b * f_pos.y + c * f_pos.z + d >= 0)
					 leftMask.SetVoxel(i, true);
				else
					 rightMask.SetVoxel(i, true);
		  }
	 }
}

//-------------------------------------------------------------------
void GenerateMovementOfChromocenters
	(const std::vector< Dot > &chromocenters,
	 const std::vector< Dot > &chromocentersAligned,
	 size_t numberOfSteps,
	 std::vector < std::vector<Dot> > &movementOfChromocenters)
{
	 DEBUG_REPORT("Pushing the chromosomes to the required position.");
	 size_t sz = chromocenters.size();

	 if (chromocentersAligned.size() != sz)
	 {
		  throw ERROR_REPORT("Wrong number of chromocenters.");
	 }
	 
	 // solve the problem of minimum cost alignment = make pairs of
	 // chromocenters and aligned chromocenters while keeping the sum 
	 // of distance between all these pari to be minimum

	 // TODO: tenhle kod uz je u kousek niz implementovn ve funkci
	 // AssignNearestDots ... do budoucna bude dobre to unifikovat 
	 // a nemiv to redundantni
	 DEBUG_REPORT("Allocating matrix for assignment problem.");
	 Matrix paths;
	 paths.resize(sz);

	 for (size_t i=0; i<sz; i++)
	 {
		  paths[i].resize(sz);

		  for (size_t j=0; j<sz; j++)
		  {
				float dist = i3d::Norm2(chromocenters[i].pos - 
										 chromocentersAligned[j].pos);
				paths[i][j].SetWeight(-dist); // looking for minimum
		  }
	 }
	 DEBUG_REPORT("Matrix allocated.");

	 DEBUG_REPORT("Solving assignment problem");
	 // Solve the minimum cost assignment problem.
    BipartiteGraph bg(paths);
	 Hungarian h(bg);
	 h.HungarianAlgo();
    const std::vector<EID> solution = h.getMatching();
	 DEBUG_REPORT("Assignment problem solved.");

#ifdef GTGEN_DEBUG 
	 for (size_t i=0; i<sz; i++)
	 {
		  std::cout << chromocentersAligned[solution[i].second].pos << 
					" <---> " << 
					chromocenters[solution[i].first].pos << std::endl; 
	 }
#endif

	 movementOfChromocenters.clear();

	// Generate the movement of chromocenters into the position of
	// alignment (in the metaphase plate)
	
   for (size_t j=0; j<numberOfSteps; j++)
	{
		 std::vector<Dot> chromocentersInCurrentTimeStamp; 
		 
		 chromocentersInCurrentTimeStamp.clear(); 
		 
		 for (size_t i=0; i<sz; i++)
		 {
			  i3d::Vector3d<float> start_position = 
						 chromocenters[solution[i].first].pos;

			  i3d::Vector3d<float> final_position = 
						 chromocentersAligned[solution[i].second].pos;

			  i3d::Vector3d<float> vec = final_position - start_position;
			  i3d::Vector3d<float> current_position = 
						 start_position + vec * (((float)j)/numberOfSteps);

			  chromocentersInCurrentTimeStamp.push_back(current_position);
		 }

		 movementOfChromocenters.push_back(chromocentersInCurrentTimeStamp);
	}

	DEBUG_REPORT("num of steps generated: " << movementOfChromocenters.size());
}

//----------------------------------------------------------------------
template <class T>
void GenerateRandomPositions(const i3d::Image3d<T> &mask,
									  const size_t count,
									  std::vector<Dot>& positions,
									  const T label)
{
	 const i3d::Offset off = mask.GetOffset();
	 const i3d::Vector3d<float> res = mask.GetResolution().GetRes();
	 size_t order = 0;
	 i3d::Image3d<float> distanceMap, modifiedMask;

	 modifiedMask.CopyMetaData(mask);

	 for (size_t i=0; i<mask.GetImageSize(); i++)
	 {
		  if (mask.GetVoxel(i) == label)
				modifiedMask.SetVoxel(i, 1.0f); // any non-zero value
		  else
				modifiedMask.SetVoxel(i, 0.0f); // background
	 }

	 // initialize the size of the output vector
	 positions.resize(count);
	
	 // first, generate the position of the first initial 'Dot'
	 bool found = false;

	 while (!found)
	 {
		  size_t index = static_cast<size_t>(GetRandomUniform(0,mask.GetImageSize()));
		  if (mask.GetVoxel(index) == label)
		  {
				found = true;
				positions[order] = 
						  Dot(i3d::PixelsToMicrons(mask.GetPos(index), res) + off);
				modifiedMask.SetVoxel(index, 0.0f);
		  }
	 }

	 order++;

	 // At this moment, the initial 'Dot' is located. Each new dot will be 
	 // generated in the far-most position from all the previously
	 // generated dots. 
	 while (order < count)
	 {
		  distanceMap = modifiedMask;

		  i3d::EDM(distanceMap, 0, 1.0f, false);

		  // find the position where the max value is located
		  size_t maxValuePos = 0;
		  float maxValue = 0.0f;
		  for (size_t i=0; i<distanceMap.GetImageSize(); i++)
		  {
				float fValue = distanceMap.GetVoxel(i);

				if (fValue > maxValue)
				{
					 maxValue = fValue;
					 maxValuePos = i;
				}
		  }

		  // store the information concerning the newly
		  // located dot in the image buffer 'modifiedMask'
		  modifiedMask.SetVoxel(maxValuePos, 0.0f); 
		  
		  positions[order] = 
					 Dot(i3d::PixelsToMicrons(mask.GetPos(maxValuePos), res) + off);

		  order++;
	 }
}


//----------------------------------------------------------------------
void AssignNearestDots(const std::vector<Dot> &oldDots,
							  std::vector<Dot> &newDots)
{
	 DEBUG_REPORT("Allocating matrix for assignment problem.");

	 size_t sz = oldDots.size();

	 if (newDots.size() != sz)
	 {
		  throw ERROR_REPORT("Wrong number of chromocenters.");
	 }
	 
	 Matrix paths;
	 paths.resize(sz);

	 for (size_t i=0; i<sz; i++)
	 {
		  paths[i].resize(sz);

		  for (size_t j=0; j<sz; j++)
		  {
				float dist = i3d::Norm2(oldDots[i].pos - newDots[j].pos);
				paths[i][j].SetWeight(-dist); // looking for minimum
		  }
	 }
	 DEBUG_REPORT("Matrix allocated.");

	 DEBUG_REPORT("Solving assignment problem");
	 // Solve the minimum cost assignment problem.
    BipartiteGraph bg(paths);
	 Hungarian h(bg);
	 h.HungarianAlgo();
    const std::vector<EID> solution = h.getMatching();
	 DEBUG_REPORT("Assignment problem solved.");

	 // back up the 'newDots' variable
	 std::vector<Dot> backupDots = newDots;

	 for (size_t i=0; i<sz; i++)
	 {
		  newDots[solution[i].first] = backupDots[solution[i].second];
	 }
}



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

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

template i3d::GRAY8  GetNewCellID<i3d::GRAY8>(void);
template i3d::GRAY16 GetNewCellID<i3d::GRAY16>(void);

template void GenerateRandomPositions<bool>(const i3d::Image3d<bool> &mask,
														  const size_t count,
														  std::vector<Dot>& positions, 
														  bool label);
template void GenerateRandomPositions<i3d::GRAY8>(const i3d::Image3d<i3d::GRAY8> &mask,
														  const size_t count,
														  std::vector<Dot>& positions, 
														  i3d::GRAY8 label);
template void GenerateRandomPositions<i3d::GRAY16>(const i3d::Image3d<i3d::GRAY16> &mask,
														  const size_t count,
														  std::vector<Dot>& positions, 
														  i3d::GRAY16 label);
