/**********************************************************************
* 
* cell_05_Cytokinesis.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: 
* An implementation of events that happen in a cell during cytokinesis.
*
***********************************************************************/

#include <sstream>
#include <i3d/FastLevelSet.h>
#include <i3d/morphology.h>
#include <i3d/transform.h>
#include <i3d/toolbox.h>
#include <i3d/descriptors/shape.h>
#include <i3d/draw.h>

#include "cell.h"
#include "toolbox/flowfields.h"
#include "toolbox/collisions.h"
#include "toolbox/shape_rendering.h"


//
// look into cell_00_Samplephase.cpp to see full version of the
// template for an atomic cell phase function like this one
//


//--------------------------------------------------------------------------
// This function accepts on its output the mask of the cell that
// should be separated, further the domain, where the defomration
// is allowed and finally the two centrosomes which precisely define the 
// position and orientation of contractile ring (splitting plane).
//
// In this function the cell mask is split into two (equal) parts.
// This split is performed via fast level se deformations. The individual time
// points of the deformation, during which the cell mask is split and two
// new mask are pulled one from each other, are stored in the output
// variables 'leftMasks' and 'rightMasks'. Also the movement of centroids
// of each mask is stored in the variables 'leftCentroids' and rigthCentroids'.
//
// The last output variable stores the information about 
// the number of components 
// in the given timepoint. Number '1' means that the original mask has not been 
// split yet. Number '2' means that the original mask has already been split
// into two new masks.
//--------------------------------------------------------------------------
void GenerateMaskDivision(const i3d::Image3d<bool> &cellMask,
								  const i3d::Image3d<bool> &domainMask,
								  const i3d::Vector3d<float> &centrosomeA,
								  const i3d::Vector3d<float> &centrosomeB,
								  ArrayOfMasks &leftMasks,
								  ArrayOfMasks &rightMasks,
								  std::vector< i3d::Vector3d<float> /**/ > &leftCentroids,
								  std::vector< i3d::Vector3d<float> /**/ > &rightCentroids,
								  std::vector< size_t> &numberOfComponents,
								  float cellDiameter);

//--------------------------------------------------------------------------
void Smooth(i3d::Image3d<bool> &mask,
				const i3d::Image3d<bool> &domain,
				size_t iterations = 1);

//--------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::DoCytokinesis(const size_t noFrames)
{
	 REPORT("\n   *****   *****   cell ID=" << ID << 
			  ", STARTING Cytokinesis at timePoint=" << 
			  timePoint); 

	// ---------- local working masks ----------
	//local working copy the cell mask
	i3d::Image3d<bool> maskCell;
	maskCell.SetResolution(scheduler.sceneRes);
	maskCell.SetOffset(scheduler.sceneMasks[timePoint]->GetOffset());

	RenderBPListIntoNewMask(maskCell,
									true,
									scmCellBPList,
									scmCellBPCentre,
									scmCellOuterBPNumber,
									noFrames,
									cellLookDistance,
									1.0f,1.0f,1.0f);

	i3d::Image3d<MV> leftMaskNucleus, rightMaskNucleus;

	i3d::Image3d<float> 
			  *leftDistanceMap = NULL,
			  *rightDistanceMap = NULL;

	// ---------- helper flow fields ----------
	FlowField<float> slsFF;
	InitFlowField(maskCell,slsFF);

	FlowField<float> FF;
	InitFlowField(maskCell,FF);

	// ---- actions a phase needs to do before the frame generating cycle ----
	// prepare the activity plan to spread the phase duties over 
	// the given number of frames create the domain, in which the cell can 
	// move and can be split into two pieces
	// (daughter cells)
	i3d::Image3d<bool> domain;
	ShowDomainForThisPhase(maskCell, domain, noFrames);

	// take the cell mask and split it (inside the allowed domain)
	ArrayOfMasks leftMasks, rightMasks;
	std::vector< i3d::Vector3d<float> /**/ > leftCentroids, rightCentroids;
	i3d::Image3d<bool> lastLeftMask, lastRightMask;
	std::vector< size_t > noOfComponents;

	GenerateMaskDivision(maskCell,
								domain,
								centrosomeA->pos,
								centrosomeB->pos,
								leftMasks,
								rightMasks,
								leftCentroids,
								rightCentroids,
								noOfComponents,
								cellDiameter);

	// resample the sequence of time frames into required number of samples
	DEBUG_REPORT("Division arrays are " << leftMasks.size() << " frames long");

	DEBUG_REPORT("Resampling to required number of frames.");
	ResampleArrayNN(leftMasks, noFrames+1);
	ResampleArrayNN(rightMasks, noFrames+1);
	ResampleArrayNN(leftCentroids, noFrames+1);
	ResampleArrayNN(rightCentroids, noFrames+1);
	ResampleArrayNN(noOfComponents, noFrames+1);

	//determine time point when both daughter cells become clearly separated
	size_t lastSingleCellTimePoint=0;
	while ((lastSingleCellTimePoint < (noFrames+1))
	    && (noOfComponents[lastSingleCellTimePoint] == 1))
	{
		 ++lastSingleCellTimePoint;
	}

	if (lastSingleCellTimePoint == (noFrames+1))
	{
		throw ERROR_REPORT("cytokinesis does not plan to split the cell...");
	}

	lastSingleCellTimePoint+=this->timePoint -1;
	//note: ArrayOfMasks contains current situation in the 0th index,
	//and the new generated situations start with index 1 -- in cytokinesis
	//we simply count generated frames from 1, not from 0; hence the '-1' subtraction

	// create two new daughter cells (this operation assigns them new unique IDs
	Cell<MV,PV> 
			  *daughter1 = new Cell<MV,PV>(this->configIni,
													 this->scheduler,
													 lastSingleCellTimePoint+1,
													 this->ID),
			  *daughter2 = new Cell<MV,PV>(this->configIni,
													 this->scheduler,
													 lastSingleCellTimePoint+1,
													 this->ID);

	// create some buffer here to avoid instantiating and allocating it
	// in each loop 
	std::vector<Dot> 
			  oldLeftChrCentres(chrCount/2), 
			  oldRightChrCentres(chrCount/2);

	//the main cycle
	for (size_t T=1; T < (noFrames+1); ++T) 
	{
		REPORT("\n   *****   *****   cell ID=" << ID << 
				 ", doing Cytokinesis at timePoint=" << timePoint \
		       << " without sync");

		//
		// internals
		//
		DEBUG_REPORT("Reading new cell masks into cell Boundary point lists");
		duplicatedCell1->scmCellOuterBPNumber = 
					ResetBoundaryPoints(duplicatedCell1->scmCellBPList,
											  duplicatedCell1->scmCellBPCentre,
											  leftMasks[T]);

		duplicatedCell2->scmCellOuterBPNumber =
				  ResetBoundaryPoints(duplicatedCell2->scmCellBPList,
											 duplicatedCell2->scmCellBPCentre,
											 rightMasks[T]);

		// sync-back the cell BP list to the current position
		DEBUG_REPORT("Re-syncing the new cell Boundary point lists");
		MoveBPListAndCentre(duplicatedCell1->scmCellBPList,
								  duplicatedCell1->scmCellBPCentre,
								  slsFF);

		MoveBPListAndCentre(duplicatedCell2->scmCellBPList,
								  duplicatedCell2->scmCellBPCentre,
								  slsFF);

		// deform (deflate and smooth) cell mask into nucleus mask
		DEBUG_REPORT("Updating nuclei boundary point lists");
		i3d::Image3d<bool> leftMaskNucleusBin = leftMasks[T];
		i3d::Image3d<bool> rightMaskNucleusBin = rightMasks[T];

		Smooth(leftMaskNucleusBin, leftMaskNucleusBin, 1);
		Smooth(rightMaskNucleusBin, rightMaskNucleusBin, 1);

		// reset boundary point list for each nucleus
		duplicatedCell1->scmNucleusOuterBPNumber = 
				  ResetBoundaryPoints(duplicatedCell1->scmNucleusBPList,
											 duplicatedCell1->scmNucleusBPCentre,
											 leftMaskNucleusBin); 
		
		duplicatedCell2->scmNucleusOuterBPNumber = 
				  ResetBoundaryPoints(duplicatedCell2->scmNucleusBPList,
											 duplicatedCell2->scmNucleusBPCentre,
											 rightMaskNucleusBin);

		DEBUG_REPORT("Updating nucleoli boundary point lists due "
						 "to internal cell movement");

		i3d::Vector3d<float> leftMovement, rightMovement;

		leftMovement = leftCentroids[T] - leftCentroids[T-1];
		rightMovement = rightCentroids[T] - rightCentroids[T-1];

		DEBUG_REPORT("left movement: " << leftMovement);
		DEBUG_REPORT("right movement: " << rightMovement);

#ifdef ENABLE_NUCLEOLI
		// move each nucleoli in order to follow the movement of cell membrane
		MoveBPListAndCentre(duplicatedCell1->scmNucleoli1BPList,
								  duplicatedCell1->scmNucleoli1BPCentre,
								  leftMovement);
		MoveBPListAndCentre(duplicatedCell1->scmNucleoli2BPList,
								  duplicatedCell1->scmNucleoli2BPCentre,
								  leftMovement);

		MoveBPListAndCentre(duplicatedCell2->scmNucleoli1BPList,
								  duplicatedCell2->scmNucleoli1BPCentre,
								  rightMovement);
		MoveBPListAndCentre(duplicatedCell2->scmNucleoli2BPList,
								  duplicatedCell2->scmNucleoli2BPCentre,
								  rightMovement);
#endif

		DEBUG_REPORT("Rendering the updated nuclei and nucleoli");
		// render everything
		leftMaskNucleus.CopyMetaData(leftMasks[T]);
		leftMaskNucleus.Clear();
		RenderBPListIntoMask(leftMaskNucleus,(MV)1,
									 duplicatedCell1->scmNucleusBPList,
									 duplicatedCell1->scmNucleusBPCentre,
									 duplicatedCell1->scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
		//render all nucleoli with different colour into the nucleus mask
		RenderBPListIntoMask(leftMaskNucleus,(MV)2,
									 duplicatedCell1->scmNucleoli1BPList,
									 duplicatedCell1->scmNucleoli1BPCentre,
									 duplicatedCell1->scmNucleoli1OuterBPNumber);

		RenderBPListIntoMask(leftMaskNucleus,(MV)3,
									 duplicatedCell1->scmNucleoli2BPList,
									 duplicatedCell1->scmNucleoli2BPCentre,
									 duplicatedCell1->scmNucleoli2OuterBPNumber); 
#endif

		rightMaskNucleus.CopyMetaData(rightMasks[T]);
		rightMaskNucleus.Clear();
		RenderBPListIntoMask(rightMaskNucleus,(MV)4,
									 duplicatedCell2->scmNucleusBPList,
									 duplicatedCell2->scmNucleusBPCentre,
									 duplicatedCell2->scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
		RenderBPListIntoMask(rightMaskNucleus,(MV)5, 
									 duplicatedCell2->scmNucleoli1BPList,
									 duplicatedCell2->scmNucleoli1BPCentre,
									 duplicatedCell2->scmNucleoli1OuterBPNumber);

		RenderBPListIntoMask(rightMaskNucleus,(MV)6,
									 duplicatedCell2->scmNucleoli2BPList,
									 duplicatedCell2->scmNucleoli2BPCentre,
									 duplicatedCell2->scmNucleoli2OuterBPNumber);
#endif

		DEBUG_REPORT("Consistency checking the updated nuclei "
						 "and nucleoli masks");
		//left nucleus with nucleoli vs left mask
		int problemCount=0;
		const bool *cm=leftMasks[T].GetFirstVoxelAddr(); //cellmask
		MV *N=leftMaskNucleus.GetFirstVoxelAddr(); //N-point
		//note: leftMaskNucleus is exactly the same size, 
		//res and offset as leftMasks[T]
		
		for (size_t i=0; i < leftMaskNucleus.GetImageSize(); ++i, ++cm, ++N)
			if ((*N) && (! *cm)) { *N=0; ++problemCount; }

		if (problemCount > 0) 
		{
			//we have found some N-points out of the cell mask,
			//we must reset them immediately so that they
			//got rendered nicely later into the sceneNuclei
			REPORT(problemCount << 
							 " left nucleus pixels were out from cell mask");

			duplicatedCell1->scmNucleusOuterBPNumber=
			  ResetBoundaryPoints(	duplicatedCell1->scmNucleusBPList,
			  			duplicatedCell1->scmNucleusBPCentre,
						leftMaskNucleus);

#ifdef ENABLE_NUCLEOLI
			duplicatedCell1->scmNucleoli1OuterBPNumber=
			  ResetBoundaryPoints(	duplicatedCell1->scmNucleoli1BPList,
			  			duplicatedCell1->scmNucleoli1BPCentre,
						leftMaskNucleus,(MV)2);

			duplicatedCell1->scmNucleoli2OuterBPNumber=
			  ResetBoundaryPoints(	duplicatedCell1->scmNucleoli2BPList,
			  			duplicatedCell1->scmNucleoli2BPCentre,
						leftMaskNucleus,(MV)3);
#endif
		}

		//right nucleus with nucleoli vs right mask
		problemCount=0;
		cm=rightMasks[T].GetFirstVoxelAddr(); //cellmask
		N=rightMaskNucleus.GetFirstVoxelAddr(); //N-point
		//note: rightMaskNucleus is exactly the same size, res and offset as rightMasks[T]
		for (size_t i=0; i < rightMaskNucleus.GetImageSize(); ++i, ++cm, ++N)
			if ((*N) && (! *cm)) { *N=0; ++problemCount; }

		if (problemCount > 0) 
		{
			//we have found some N-points out of the cell mask,
			//we must reset them immediately so that they
			//got rendered nicely later into the sceneNuclei
			REPORT(problemCount << 
							 " right nucleus pixels were out from cell mask");

			duplicatedCell2->scmNucleusOuterBPNumber=
			  ResetBoundaryPoints(	duplicatedCell2->scmNucleusBPList,
			  			duplicatedCell2->scmNucleusBPCentre,
						rightMaskNucleus);

#ifdef ENABLE_NUCLEOLI
			duplicatedCell2->scmNucleoli1OuterBPNumber=
			  ResetBoundaryPoints(	duplicatedCell2->scmNucleoli1BPList,
			  			duplicatedCell2->scmNucleoli1BPCentre,
						rightMaskNucleus,(MV)5);

			duplicatedCell2->scmNucleoli2OuterBPNumber=
			  ResetBoundaryPoints(	duplicatedCell2->scmNucleoli2BPList,
			  			duplicatedCell2->scmNucleoli2BPCentre,
						rightMaskNucleus,(MV)6);
#endif
		}

		DEBUG_REPORT("Moving with chromosomes (local affairs).");

		if (leftDistanceMap) 
		{
			delete leftDistanceMap;
			leftDistanceMap=NULL;
		}

		if (rightDistanceMap) 
		{
			delete rightDistanceMap;
			rightDistanceMap=NULL;
		}

		// Here, we create temporal arrays 'oldLeftChrCentres' and
		// 'oldRightChrCentres'. Left ones represent even chromosomes,
		// right ones the others. Then, we randomly generate new positions
		// into the array 'newLeftChrCentres' and 'newRightChrCentres'.
		// Finally, using KuhnMunkres alogithm we assign the new positions
		// to the new ones respecting the minimal motion/shaking.

		for (size_t i=0; i < chrCount; i++) 
		{
			 if (EVEN(i))
				  oldLeftChrCentres[i/2] = chrCentres[i];
			 else
				  oldRightChrCentres[(i-1)/2] = chrCentres[i];
		}

		//helper tmp image just for the two erosions below
		i3d::Image3d<MV> erodedImage;

		std::vector<Dot> newLeftChrCentres;
		i3d::Erosion(leftMaskNucleus, erodedImage, i3d::nb3D_26);
		GenerateRandomPositions(erodedImage, 
										chrCount/2, 
										newLeftChrCentres, 
										MV(1));

		std::vector<Dot> newRightChrCentres;
		i3d::Erosion(rightMaskNucleus, erodedImage, i3d::nb3D_26);
		GenerateRandomPositions(erodedImage, 
										chrCount/2, 
										newRightChrCentres, 
										MV(4));

		erodedImage.DisposeData();

		AssignNearestDots(oldLeftChrCentres, newLeftChrCentres);
		AssignNearestDots(oldRightChrCentres, newRightChrCentres);

		for (size_t i=0; i < chrCount; i++) 
		{
			 if (EVEN(i))
			 {
				  const size_t Lindex = i/2;
				  const i3d::Vector3d<float> Lshift = 
					 newLeftChrCentres[Lindex].pos - oldLeftChrCentres[Lindex].pos;

				  // move to a position respecting the new 
				  // shape and position of nucleus
				  ChrMoveByVector(i,
										Lshift, 
										leftMaskNucleus,
										leftDistanceMap,
										MV(1));

				  // apply some Brownian motion
				  ChrMoveWithBrown(i,0.02f,leftMaskNucleus,leftDistanceMap,MV(1)); 
			 }
			 else
			 {
				  const size_t Rindex = (i-1)/2;
				  const i3d::Vector3d<float> Rshift =
					 newRightChrCentres[Rindex].pos - oldRightChrCentres[Rindex].pos;

				  // move to a position respecting the new 
				  // shape and position of nucleus
				  ChrMoveByVector(i,
										Rshift, 
										rightMaskNucleus,
										rightDistanceMap,
										MV(4));

				  // apply some Brownian motion
				  ChrMoveWithBrown(i,0.02f,rightMaskNucleus,rightDistanceMap,MV(4)); 
			 }
		}

		//
		// externals
		//

		//reset the current flow field
		FF.x->GetVoxelData()=0.f;
		FF.y->GetVoxelData()=0.f;
		if (FF.z) FF.z->GetVoxelData()=0.f;

		DEBUG_REPORT("Merging cell BPlists");
		ScmMergeCellBPLists();

		DEBUG_REPORT("Computing whole-cell movement");
		ScmUniversalMovement(FF);

		//
		// a footer common to all phase functions:
		// 

		//append the new changes, does not change FF
		ConcatenateFlowFields(slsFF,FF);

		// move the boundary points and draw cell into sceneMasks
		//
		DEBUG_REPORT("Applying whole-cell movement");
		MoveBPListAndCentre(duplicatedCell1->scmCellBPList, duplicatedCell1->scmCellBPCentre, FF);
		MoveBPListAndCentre(duplicatedCell2->scmCellBPList, duplicatedCell2->scmCellBPCentre, FF);

		DEBUG_REPORT("Removing old masks");
		ScmRemoveCellFromMask(*scheduler.sceneMasks[timePoint+1]);

		if (noOfComponents[T] == 1)
		{
			 DEBUG_REPORT("Rendering new masks as one common");
			 RenderBPListIntoMask(*scheduler.sceneMasks[timePoint+1], 
										 ID, 
										 duplicatedCell1->scmCellBPList,
										 duplicatedCell1->scmCellBPCentre,
										 duplicatedCell1->scmCellOuterBPNumber); 
			 
			 RenderBPListIntoMask(*scheduler.sceneMasks[timePoint+1], 
										 ID, 
										 duplicatedCell2->scmCellBPList,
										 duplicatedCell2->scmCellBPCentre,
										 duplicatedCell2->scmCellOuterBPNumber);
#ifdef GTGEN_WITH_NUCLEIMASKS
			RenderBPListIntoMask(*scheduler.sceneNuclei[timePoint+1],ID, duplicatedCell1->scmNucleusBPList,
				 duplicatedCell1->scmNucleusBPCentre, duplicatedCell1->scmNucleusOuterBPNumber,slsFF);
			RenderBPListIntoMask(*scheduler.sceneNuclei[timePoint+1],ID, duplicatedCell2->scmNucleusBPList,
				 duplicatedCell2->scmNucleusBPCentre, duplicatedCell2->scmNucleusOuterBPNumber,slsFF);
#endif
		}
		else // (noOfComponents == 2)
		{
			 DEBUG_REPORT("Rendering new masks as two separated");
			 RenderBPListIntoMask(*scheduler.sceneMasks[timePoint+1], 
										 daughter1->GetID(), 
										 duplicatedCell1->scmCellBPList,
										 duplicatedCell1->scmCellBPCentre,
										 duplicatedCell1->scmCellOuterBPNumber); 
			 
			 RenderBPListIntoMask(*scheduler.sceneMasks[timePoint+1], 
										 daughter2->GetID(), 
										 duplicatedCell2->scmCellBPList,
										 duplicatedCell2->scmCellBPCentre,
										 duplicatedCell2->scmCellOuterBPNumber);
#ifdef GTGEN_WITH_NUCLEIMASKS
			RenderBPListIntoMask(*scheduler.sceneNuclei[timePoint+1],daughter1->GetID(), duplicatedCell1->scmNucleusBPList,
				 duplicatedCell1->scmNucleusBPCentre, duplicatedCell1->scmNucleusOuterBPNumber,slsFF);
			RenderBPListIntoMask(*scheduler.sceneNuclei[timePoint+1],daughter2->GetID(), duplicatedCell2->scmNucleusBPList,
				 duplicatedCell2->scmNucleusBPCentre, duplicatedCell2->scmNucleusOuterBPNumber,slsFF);
#endif
		}

		//if we are in the last frame:
		if (T == noFrames) {
				DEBUG_REPORT("Synchronization got turned ON.");

				DEBUG_REPORT("Re-syncing nucleus and nucleoli masks (global motion).");

				MoveBPListAndCentre(duplicatedCell1->scmNucleusBPList,
										  duplicatedCell1->scmNucleusBPCentre,
										  slsFF);

#ifdef ENABLE_NUCLEOLI
				MoveBPListAndCentre(duplicatedCell1->scmNucleoli1BPList,
										  duplicatedCell1->scmNucleoli1BPCentre,
										  slsFF);

				MoveBPListAndCentre(duplicatedCell1->scmNucleoli2BPList,
										  duplicatedCell1->scmNucleoli2BPCentre,
										  slsFF);
#endif

				MoveBPListAndCentre(duplicatedCell2->scmNucleusBPList,
										  duplicatedCell2->scmNucleusBPCentre,
										  slsFF);

#ifdef ENABLE_NUCLEOLI
				MoveBPListAndCentre(duplicatedCell2->scmNucleoli1BPList,
										  duplicatedCell2->scmNucleoli1BPCentre,
										  slsFF);

				MoveBPListAndCentre(duplicatedCell2->scmNucleoli2BPList,
										  duplicatedCell2->scmNucleoli2BPCentre,
										  slsFF);
#endif

				DEBUG_REPORT("Rendering the updated nucleus and nucleoli");

				//render the current/updated nucleus with nucleoli,
				//the R...NewMask will appropriatelly adjust size
				RenderBPListIntoNewMask(leftMaskNucleus,(MV)1, 
												duplicatedCell1->scmNucleusBPList,
												duplicatedCell1->scmNucleusBPCentre, 
												duplicatedCell1->scmNucleusOuterBPNumber,
												noFrames, cellLookDistance, 1.0f,1.0f,1.0f);

#ifdef ENABLE_NUCLEOLI
				RenderBPListIntoMask(leftMaskNucleus,(MV)2, 
											duplicatedCell1->scmNucleoli1BPList,
											duplicatedCell1->scmNucleoli1BPCentre, 
											duplicatedCell1->scmNucleoli1OuterBPNumber);

				RenderBPListIntoMask(leftMaskNucleus,(MV)3, 
											duplicatedCell1->scmNucleoli2BPList,
											duplicatedCell1->scmNucleoli2BPCentre, 
											duplicatedCell1->scmNucleoli2OuterBPNumber); 
#endif
				
				RenderBPListIntoNewMask(rightMaskNucleus,(MV)4, 
												duplicatedCell2->scmNucleusBPList,
												duplicatedCell2->scmNucleusBPCentre, 
												duplicatedCell2->scmNucleusOuterBPNumber,
												noFrames, cellLookDistance, 1.0f,1.0f,1.0f);

#ifdef ENABLE_NUCLEOLI
				RenderBPListIntoMask(rightMaskNucleus,(MV)5, 
											duplicatedCell2->scmNucleoli1BPList,
											duplicatedCell2->scmNucleoli1BPCentre, 
											duplicatedCell2->scmNucleoli1OuterBPNumber);

				RenderBPListIntoMask(rightMaskNucleus,(MV)6, 
											duplicatedCell2->scmNucleoli2BPList,
											duplicatedCell2->scmNucleoli2BPCentre, 
											duplicatedCell2->scmNucleoli2OuterBPNumber);
#endif

				DEBUG_REPORT("Moving with chromosomes (global motion).");

				//reset DM masks
				if (leftDistanceMap) {
					delete leftDistanceMap;
					leftDistanceMap=NULL;
				}
				if (rightDistanceMap) {
					delete rightDistanceMap;
					rightDistanceMap=NULL;
				}

				// left mask
				for (size_t i=0; i < chrCount; i+=2) 
					 ChrMoveByFlow(i,slsFF,leftMaskNucleus,leftDistanceMap,MV(1));

				// right mask
				for (size_t i=1; i < chrCount; i+=2) 
					 ChrMoveByFlow(i,slsFF,rightMaskNucleus,rightDistanceMap,MV(4));

				//no need to update centrosomes as they are not used
				//until again at the end of G2

				ChrUpdateStats();

				//draw the phantom image
				DEBUG_REPORT("Rendering chromosomes.");
				ChrRenderIntoPhantoms(*scheduler.scenePhantoms[timePoint+1]);
#ifdef GTGEN_WITH_TERRITORIES
				ChrRenderIntoTerritories(*scheduler.sceneTerritories[timePoint+1]);
#endif
		} else {
			//not in the last (and synchronized) frame, doing it "asynchronically" ;)

			ChrUpdateStats();
			//note: can't move both ChrUpdateStats() before this if-statement
			//as chrDots change in the other branch

			//draw the phantom image to where we should appear
			DEBUG_REPORT("Rendering chromosomes.");
			ChrRenderIntoPhantoms(*scheduler.scenePhantoms[timePoint+1],slsFF);
#ifdef GTGEN_WITH_TERRITORIES
			ChrRenderIntoTerritories(*scheduler.sceneTerritories[timePoint+1],slsFF);
#endif
		}

		++timePoint;

	} //the main cell cycle

	for (size_t i=timePoint+1; i<=scheduler.newestScene; i++)
	{
		 DEBUG_REPORT("replacing reservation in " << i << " sceneMask (ID:"
		 	<< ID << " goes to " << daughter1->GetID()
			<< " and " << daughter2->GetID() << ")");

		 // remove all old estimates
		 ScmRemoveCellFromMask(*scheduler.sceneMasks[i]);

		 // update new estimate according to the most recent cell mask shape
		 SoftRenderBPListIntoMask(*scheduler.sceneMasks[i], 
									daughter1->GetID(), 
									duplicatedCell1->scmCellBPList,
									duplicatedCell1->scmCellBPCentre,
									duplicatedCell1->scmCellOuterBPNumber);

		 SoftRenderBPListIntoMask(*scheduler.sceneMasks[i], 
									daughter2->GetID(), 
									duplicatedCell2->scmCellBPList,
									duplicatedCell2->scmCellBPCentre,
									duplicatedCell2->scmCellOuterBPNumber);
	}

	// left cell - last finished phase and timepoint
	daughter1->currentPhase = Cytokinesis;
	daughter1->timePoint=timePoint;

	// left cell - chromosomes
	daughter1->chrCount = this->chrCount / 2;
	daughter1->chrDots = this->chrDots;
	daughter1->chrDotList.clear();
	daughter1->chrDotList.reserve(this->chrDotList.size()/2);
	for (size_t i=0; i<chrDotList.size(); i++)
	{
		 if (EVEN(i/this->chrDots))
			  daughter1->chrDotList.push_back(this->chrDotList[i]);
	}
	daughter1->ChrUpdateStats();

	// left cell - masks
	daughter1->scmCellBPList = 
			  this->duplicatedCell1->scmCellBPList;
	daughter1->scmCellOuterBPNumber = 
			  this->duplicatedCell1->scmCellOuterBPNumber; 
	daughter1->scmCellBPCentre = 
			  this->duplicatedCell1->scmCellBPCentre;
	daughter1->scmNucleusBPList =
			  this->duplicatedCell1->scmNucleusBPList;
	daughter1->scmNucleusOuterBPNumber =
			  this->duplicatedCell1->scmNucleusOuterBPNumber;
	daughter1->scmNucleusBPCentre =
			  this->duplicatedCell1->scmNucleusBPCentre;

#ifdef ENABLE_NUCLEOLI
	daughter1->scmNucleoli1BPList =
			  this->duplicatedCell1->scmNucleoli1BPList;
	daughter1->scmNucleoli1OuterBPNumber =
			  this->duplicatedCell1->scmNucleoli1OuterBPNumber;
	daughter1->scmNucleoli1BPCentre =
			  this->duplicatedCell1->scmNucleoli1BPCentre;
	daughter1->scmNucleoli2BPList =
			  this->duplicatedCell1->scmNucleoli2BPList;
	daughter1->scmNucleoli2OuterBPNumber =
			  this->duplicatedCell1->scmNucleoli2OuterBPNumber;
	daughter1-> scmNucleoli2BPCentre =
			  this->duplicatedCell1->scmNucleoli2BPCentre;
#endif

	// right cell - last finished phase and timepoint
	daughter2->currentPhase = Cytokinesis;
	daughter2->timePoint=timePoint;

	// right cell - chromosomes
	daughter2->chrCount = this->chrCount / 2;
	daughter2->chrDots = this->chrDots;
	daughter2->chrDotList.clear();
	daughter2->chrDotList.reserve(this->chrDotList.size()/2);
	for (size_t i=0; i<chrDotList.size(); i++)
	{
		 if (ODD(i/this->chrDots))
			  daughter2->chrDotList.push_back(this->chrDotList[i]);
	}
	daughter2->ChrUpdateStats();

	// right cell - masks
	daughter2->scmCellBPList = 
			  this->duplicatedCell2->scmCellBPList;
	daughter2->scmCellOuterBPNumber = 
			  this->duplicatedCell2->scmCellOuterBPNumber; 
	daughter2->scmCellBPCentre = 
			  this->duplicatedCell2->scmCellBPCentre;
	daughter2->scmNucleusBPList =
			  this->duplicatedCell2->scmNucleusBPList;
	daughter2->scmNucleusOuterBPNumber =
			  this->duplicatedCell2->scmNucleusOuterBPNumber;
	daughter2->scmNucleusBPCentre =
			  this->duplicatedCell2->scmNucleusBPCentre;

#ifdef ENABLE_NUCLEOLI
	daughter2->scmNucleoli1BPList =
			  this->duplicatedCell2->scmNucleoli1BPList;
	daughter2->scmNucleoli1OuterBPNumber =
			  this->duplicatedCell2->scmNucleoli1OuterBPNumber;
	daughter2->scmNucleoli1BPCentre =
			  this->duplicatedCell2->scmNucleoli1BPCentre;
	daughter2->scmNucleoli2BPList =
			  this->duplicatedCell2->scmNucleoli2BPList;
	daughter2->scmNucleoli2OuterBPNumber =
			  this->duplicatedCell2->scmNucleoli2OuterBPNumber;
	daughter2-> scmNucleoli2BPCentre =
			  this->duplicatedCell2->scmNucleoli2BPCentre;
#endif

	//cleaning here if necessary
	if (leftDistanceMap)
		 delete leftDistanceMap;

	if (rightDistanceMap)
		 delete rightDistanceMap;

	delete duplicatedCell1;
	duplicatedCell1 = NULL;

	delete duplicatedCell2;
	duplicatedCell2 = NULL;

	// store both new cells in scheduler
	scheduler.cells.push_back(daughter1);
	scheduler.cells.push_back(daughter2);

	// set initial delay to disynchronize both daughters:
	//the delay interval is 0% to 10% of cell cycle with mean at 5%
	const float delayMean=(float)cellCycleLength/20.f;
#ifdef GTGEN_WITH_STRONGSYNCHRONIZATION
	const float delayVariance=delayMean/2.0f;
#else
	const float delayVariance=delayMean/1.3f;
#endif
	daughter1->SetDelay( (size_t)ceil(
			std::max(0.f,std::min(2.f*delayMean,
				GetRandomGauss(delayMean,delayVariance)
			))
		) );
	daughter2->SetDelay( (size_t)ceil(
			std::max(0.f,std::min(2.f*delayMean,
				GetRandomGauss(delayMean,delayVariance)
			))
		) );

	// if mother was a comet of certain type, select one daughter to
	// continue in the mother's fate; select her with respect to the comet type
	// (intentionally, we do not continue cometing if the mother was
	// on her way back from the scene boundary)
	if ((scmCometState == hesitating) || (scmCometState == toBoundary)) {
		//cometing... yes
		//scene boundary coordinates in "absolute microns"
		const float LEFT=scheduler.sceneOffset.x;
		const float TOP=scheduler.sceneOffset.y;
		const float RIGHT=scheduler.sceneSize.x + LEFT;
		const float BOTTOM=scheduler.sceneSize.y + TOP;

		//what are the respective distances?
		float curDist1=999999.f;
		float curDist2=999999.f;

		switch (scmCometType) {
		case leftBoundary:
			curDist1=daughter1->scmCellBPCentre.x - LEFT;
			curDist2=daughter2->scmCellBPCentre.x - LEFT;
			break;
		case rightBoundary:
			curDist1=RIGHT - daughter1->scmCellBPCentre.x;
			curDist2=RIGHT - daughter2->scmCellBPCentre.x;
			break;
		case topBoundary:
			curDist1=daughter1->scmCellBPCentre.y - TOP;
			curDist2=daughter2->scmCellBPCentre.y - TOP;
			break;
		case bottomBoundary:
			curDist1=BOTTOM - daughter1->scmCellBPCentre.y;
			curDist2=BOTTOM - daughter2->scmCellBPCentre.y;
			break;
		}

		//now choose "more optimal" daughter
		if (curDist1 < curDist2)
			daughter1->BecomeComet(scmCometType);
		else
			daughter2->BecomeComet(scmCometType);
	}

	//reset the timePoint in the current cell to where it ought to be
	this->timePoint=lastSingleCellTimePoint;

	//note: I should have re-init the hints image for non-rigid
	//because this phase changed boundary points, but it makes
	//no sense as this cell will be destroyed immediately
}

//-------------------------------------------------------------------
void MoveToPoints(i3d::Image3d<bool> &mask,
						const i3d::Image3d<bool> &domain,
						const i3d::Vector3d<float> &leftPoint,
						const i3d::Vector3d<float> &rightPoint)
{
	 const float radius = 7.0f;
	 float inflation_impact = 1.5f;
	 float curvature_impact = 0.2f;

	 // find both centrosomes in the given mask
	 i3d::Image3d<float> speedImage;
	 speedImage.CopyMetaData(mask);
	 
	 // check each pixel - if it's between centrosomes. If so,
	 // set the values to "-" otherwise to "+". The value are computed
	 // via dot product, i.e. the domain si smooth and hence suitable
	 // to be a speed function
	 i3d::Vector3d<float> off = speedImage.GetOffset();
	 i3d::Resolution res = speedImage.GetResolution();

	 for (size_t i=0; i<speedImage.GetImageSize(); i++)
	 {
		  i3d::Vector3d<size_t> i_pos = speedImage.GetPos(i);
		  i3d::Vector3d<float> f_pos = i3d::PixelsToMicrons(i_pos, res) + off;

		  float val = 
					 i3d::Dot(f_pos - leftPoint, rightPoint - leftPoint) * 
					 i3d::Dot(f_pos - rightPoint, leftPoint - rightPoint);

		  speedImage.SetVoxel(i, -val);
	 }
	 
	 // normalize the speed image
	 float max_value = speedImage.GetVoxelData().max();
	 float min_value = speedImage.GetVoxelData().min();
	 float abs_limit = std::max(abs(max_value), abs(min_value));

	 for (size_t i=0; i<speedImage.GetImageSize(); i++)
	 {
		  float value = speedImage.GetVoxel(i);
		  float modified = (value - min_value)/abs_limit;
		  speedImage.SetVoxel(i, powf(modified, 2.0f)); /// empirically measured value
		  /// 2.0f  causes the deformed shape to be circular like
	 }

	 // extend the speed image to be suitable input for
	 // fast level sets
	 i3d::Image3d<float> speedImageExt;
    i3d::Mirror(speedImage, speedImageExt);

	 i3d::Image3d<float> speed, speedExt;
	 speed.CopyMetaData(mask);
	 speed.GetVoxelData() = 1.0f;
    i3d::Mirror(speed, speedExt);

	 i3d::fls::Routine<int> *mover;

	 mover = i3d::fls::DefRoutine(speedImageExt, 
											mask, 
											&domain, 
											radius, 
											inflation_impact, 
											curvature_impact); 
	 
	 mover->Initialize(); 

	 mover->Iterate(mover -> InterfaceSize()); 
	 mover->Mask(mask.begin(), 
					 i3d::fls::ResultCreator::InteriorAndInterface, 
					 true);
	 delete mover;
}

//-------------------------------------------------------------------
void Smooth(i3d::Image3d<bool> &mask,
				const i3d::Image3d<bool> &domain,
				size_t iterations)
{
	 const float radius = 13.0f;

	 i3d::fls::Routine<int> *smoother;

	 smoother = i3d::fls::MCMRoutine(mask, &domain, radius);
	 
	 smoother->Initialize(); 

	 smoother->Iterate(smoother -> InterfaceSize() * iterations); 
	 smoother->Mask(mask.begin(), 
						 i3d::fls::ResultCreator::InteriorAndInterface, 
						 true);

	 delete smoother;
}

//-------------------------------------------------------------------
void GenerateMaskDivision(const i3d::Image3d<bool> &cellMask,
								  const i3d::Image3d<bool> &domainMask,
								  const i3d::Vector3d<float> &centrosomeA,
								  const i3d::Vector3d<float> &centrosomeB,
								  ArrayOfMasks &leftMasks,
								  ArrayOfMasks &rightMasks,
								  std::vector< i3d::Vector3d<float> /**/ > &leftCentroids,
								  std::vector< i3d::Vector3d<float> /**/ > &rightCentroids,
								  std::vector< size_t> &numberOfComponents,
								  float cellDiameter)
{
	 DEBUG_REPORT("Started");

	 i3d::Vector3d<float> res = cellMask.GetResolution().GetRes();
	 i3d::Vector3d<float> off = cellMask.GetOffset();
	 i3d::Vector3d<float> cellCentre = (centrosomeA + centrosomeB)/2.0f;

	 i3d::Image3d<bool> leftDomain, rightDomain, oldLeftMask, oldRightMask;

	 // split cell mask into left and right part
	 SplitMaskIntoTwoHalves(cellMask, 
									oldLeftMask, 
									oldRightMask, 
									centrosomeA, 
									centrosomeB);

	 // split domain mask into left and right part
	 SplitMaskIntoTwoHalves(domainMask, 
									leftDomain, 
									rightDomain, 
									centrosomeA, 
									centrosomeB);

	 i3d::Image3d<bool> newLeftMask, newRightMask;

	 // Factor 0.9 (90% of diameter) means that the new daughter cell
	 // will have approx 75% volume of mother cell.
	 RenderBlankCellMask(res, newLeftMask, cellDiameter*0.99f, 16.0f);
	 AlignMasks(oldLeftMask, newLeftMask);

	 RenderBlankCellMask(res, newRightMask, cellDiameter*0.99f, 16.0f);
	 AlignMasks(oldRightMask, newRightMask);

	 // find a suitable position of two new cells. These should be located
	 // in the domain defined in this function and they both should have
	 // centroids on the line defined by centrosomeA and centrosomeB
	
	 // first take care of left part
	 i3d::Negative(leftDomain);
	 i3d::Vector3d<float> 
				*leftPos = FindNewPosition(oldLeftMask, 
													newLeftMask, 
													leftDomain);
				
	 // then take care of right part
	 i3d::Negative(rightDomain);
	 i3d::Vector3d<float> 
				*rightPos = FindNewPosition(oldRightMask,
													 newRightMask,
													 rightDomain);

	 if ((leftPos == NULL) || (rightPos == NULL))
	 { 
		  DEBUG_REPORT("Too narrow space for cytokinesis. Trying to "
							"merge domains for both daughter cells. It might "
							"help.");
	 }

	 // Left daughter cell has no place to live. Let us merge
	 // domains (left and right) together. The cells will share them.
	 // The left cell will start searching for the location.
	 //
	 // It may also happen that neither left nor right cell can find
	 // the location from themselves, i.e. for pointer are set to
	 // NULL. We will assume that merging the domains can help.
	 if (leftPos == NULL)
	 { 
		  // use both non-split domains
		  i3d::Image3d<bool> fullDomain(domainMask);

		  // invert the domain
		  i3d::Negative(fullDomain);

		  // try to find position for the left daughter cell
		  leftPos = FindNewPosition(oldLeftMask, 
											 newLeftMask, 
											 fullDomain);

		  // in case of failure stop computation because there is no remedy
		  if (leftPos == NULL)
		  {
				throw ERROR_REPORT("Even second attempt of cytokinesis "
										 "did not help to solve the problem "
										 "with few place for cell division.");
		  }
	 
		  // shift the newly generated cell the to prepared location
		  i3d::Offset leftOffset = 
				*leftPos - i3d::PixelsToMicrons(newLeftMask.GetSize(), res)/2.0f;
		  newLeftMask.SetOffset(leftOffset);

		  // and project it into the domain to prevent the right cell to
		  // find its location in the same area
		  AddImageRespectingItsOffset(fullDomain, newLeftMask); 
		 
		  // try to find position for the right daughter cell
		  rightPos = FindNewPosition(oldRightMask,
											  newRightMask, 
											  fullDomain);

		  if (rightPos == NULL)
		  {
				throw ERROR_REPORT("Even second attempt of cytokinesis"
										 " did not help to solve the problem"
										 " with few place for cell division.");
		  }
	
		  // At this point, both pointers (leftPos, rightPos) cannot
		  // be equal to NULL. 
	 }

	 // Right daughter cell has no place to live. Let us merge
	 // domains (left and right) together. The cells will share them.
	 // The right cell will start searching for the location.
	 if (rightPos == NULL)
	 {
		  // use both non-split domains
		  i3d::Image3d<bool> fullDomain(domainMask);

		  // invert the domain
		  i3d::Negative(fullDomain);

		  // try to find position for the right daughter cell
		  rightPos = FindNewPosition(oldRightMask, 
											  newRightMask, 
											  fullDomain);

		  // in case of failure stop computation because there is no remedy
		  if (rightPos == NULL)
		  {
				throw ERROR_REPORT("Even second attempt of cytokinesis "
										 "did not help to solve the problem "
										 "with few place for cell division.");
		  }
	 
		  // shift the newly generated cell the to prepared location
		  i3d::Offset rightOffset = 
				*rightPos - i3d::PixelsToMicrons(newRightMask.GetSize(), res)/2.0f;
		  newRightMask.SetOffset(rightOffset);

		  // and project it into the domain to prevent the left cell to
		  // find its location in the same area
		  AddImageRespectingItsOffset(fullDomain, newRightMask); 
		 
		  // try to find position for the right daughter cell
		  leftPos = FindNewPosition(oldLeftMask,
											 newLeftMask, 
											 fullDomain);

		  if (leftPos == NULL)
		  {
				throw ERROR_REPORT("Even second attempt of cytokinesis"
										 " did not help to solve the problem"
										 " with few place for cell division.");
		  }
	
		  // At this point, both pointers (leftPos, rightPos) cannot
		  // be equal to NULL.
	 }

	 i3d::Offset leftOffset = 
				*leftPos - i3d::PixelsToMicrons(newLeftMask.GetSize(), res)/2.0f;
	 newLeftMask.SetOffset(leftOffset);
	 delete leftPos;

	 i3d::Offset rightOffset = 
				*rightPos - i3d::PixelsToMicrons(newRightMask.GetSize(), res)/2.0f;
	 newRightMask.SetOffset(rightOffset);
	 delete rightPos;
		
	 // TODO:
	 // Tady uz mame presne lokalizovano, kde se maji nachazi a jak
	 // maji vypada nove dcerinne bunky po rozdeleni jejich materske bunky. Tim
	 // padem by slo v tomto miste provest morphing:
	 // - z oldLeftMask do newLeftMask a
	 // - z oldRightMask do newRightMask
	 // a ulozit do pripravenych poli. Ted to osulime a udelame z toho jen pole delky
	 // '2' protoze tato faze bunecneho cyklu je obvykle skutecne jen velmi kratka,
	 // takze i kdybychom to navzrokovali na jemno v ramci jednotlivych kroku morphingu, 
	 // tak nic neuvidime. Zase budeme mit ve vysledku jen jeden posledni krok. Pro
	 // release verzi do clanku by se to ale hodil doladit, abychom mohli ladit delsi
	 // sekvence s libovolnou presnosti.

	 leftMasks.reserve(2);
	 rightMasks.reserve(2);

	 leftCentroids.reserve(2);
	 rightCentroids.reserve(2);

	 numberOfComponents.reserve(2);

	 i3d::Shape<bool> shape;
	 
	 // first round
	 leftMasks.push_back(oldLeftMask);
	 shape.SetImage(&oldLeftMask);
	 shape.SetLabel(true);
	 leftCentroids.push_back(i3d::PixelsToMicrons(shape.Centroid(), res) + 
									 oldLeftMask.GetOffset()); 

	 rightMasks.push_back(oldRightMask); 
	 shape.SetImage(&oldRightMask);
	 shape.SetLabel(true);
	 rightCentroids.push_back(i3d::PixelsToMicrons(shape.Centroid(), res) + 
									  oldRightMask.GetOffset());

	 numberOfComponents.push_back(1);
 
	 // second round
	 // patch - needed for correct floodfill:
	 oldLeftMask.Clear();
	 AddImageRespectingItsOffset(oldLeftMask, newLeftMask);
	 leftMasks.push_back(oldLeftMask);
	 //leftMasks.push_back(newLeftMask);
	 shape.SetImage(&newLeftMask);
	 shape.SetLabel(true);
	 leftCentroids.push_back(i3d::PixelsToMicrons(shape.Centroid(), res) + 
									 newLeftMask.GetOffset()); 
	
	 // patch - needed for correct floodfill:
	 oldRightMask.Clear();
	 AddImageRespectingItsOffset(oldRightMask, newRightMask);
	 rightMasks.push_back(oldRightMask);
	 //rightMasks.push_back(newRightMask); 
	 shape.SetImage(&newRightMask);
	 shape.SetLabel(true);
	 rightCentroids.push_back(i3d::PixelsToMicrons(shape.Centroid(), res) + 
									  newRightMask.GetOffset());

	 numberOfComponents.push_back(2);

	 DEBUG_REPORT("Finished - successfully");
}

//-------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::ScmMergeCellBPLists(void)
{
	if ((!duplicatedCell1) || (!duplicatedCell2))
		throw ERROR_REPORT("Missing one or both daughter cells to merge with.");

	//we copy only the first part of both boundary lists because these
	//are the, so called, outer boundary points -- points examined by
	//the Cell::ScmUniversalMovement() function
	
	//prepare the merged list
	scmCellOuterBPNumber= duplicatedCell1->scmCellOuterBPNumber
			    + duplicatedCell2->scmCellOuterBPNumber;
	scmCellBPList.clear();
	scmCellBPList.resize(scmCellOuterBPNumber);

	size_t i=0;
	for (size_t j=0; j < duplicatedCell1->scmCellOuterBPNumber; ++j, ++i)
		scmCellBPList[i]=duplicatedCell1->scmCellBPList[j];
	for (size_t j=0; j < duplicatedCell2->scmCellOuterBPNumber; ++j, ++i)
		scmCellBPList[i]=duplicatedCell2->scmCellBPList[j];
		
	//adjust the centre of both
	scmCellBPCentre=duplicatedCell1->scmCellBPCentre + duplicatedCell2->scmCellBPCentre;
	scmCellBPCentre/=2.f;
}

//----------------------------------------------------------------------
// explicit instantiations to force explicit build of the functions 
// in this file because otherwise the linker couldn't find them
//----------------------------------------------------------------------
template class Cell<i3d::GRAY8,  i3d::GRAY16>;
template class Cell<i3d::GRAY16, i3d::GRAY16>;
