#include <sstream>
#include <i3d/toolbox.h>
#include <i3d/FastLevelSet.h>

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

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

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

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

	// ---------- 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);

	i3d::Image3d<bool> leftMask, rightMask;

	// recall the metaphase plate and split the cell mask into two halves
	DEBUG_REPORT("Splitting mask into two halves");
	SplitMaskIntoTwoHalves(maskCell,
								  leftMask,
								  rightMask,
								  centrosomeA->pos,
								  centrosomeB->pos);

	// make sure that no chromatin is in wrong mask. Here, slight movement
	// with the verification of positions should repair any misplacement.
	DEBUG_REPORT("Splitting chromosomes into two halves");
	for (size_t i=0; i < chrCount; ++i) 
	{ 
		 if (EVEN(i))
		 {
			  ChrMoveWithBrownBM(i,0.05f,leftMask,leftDistanceMap,1);
		 }
		 else
		 {
			  ChrMoveWithBrownBM(i,0.05f,rightMask,rightDistanceMap,1);
		 }
	}

	// make the newly created nucleus membrane as a copy of left and right
	// cell masks. Then deform it slightly (deflate).
	i3d::fls::Routine<int> *smoother;
	const float radius = 7.0f;
	const size_t levelOfSmoothness = 3; //to create smaller nucleus

	duplicatedCell1 = new NewCell; 

	// deform left mask and create nucleoli
	DEBUG_REPORT("Smoothing left mask");
	smoother = i3d::fls::MCMRoutine(leftMask, &leftMask, radius);
	smoother->Initialize();

	for (size_t l=0; l<levelOfSmoothness; l++)
	{
		 // deform
		 smoother->Iterate(smoother->InterfaceSize()); 
		 smoother->Mask(leftMask.begin(),
							 i3d::fls::ResultCreator::InteriorAndInterface);

		 // mask has changed -> reset distance map
		 if (leftDistanceMap)
		 {
			  delete leftDistanceMap;
			  leftDistanceMap = NULL;
		 }

		 // and shift keep the molecules inside the mask
		 for (size_t i=0; i < chrCount; i+=2) 
		 {
			  ChrMoveWithBrownBM(i,0.05f,leftMask,leftDistanceMap,1);
		 }
	}
	delete smoother; 
	
	duplicatedCell1->scmNucleusOuterBPNumber =
			  ResetBoundaryPoints(duplicatedCell1->scmNucleusBPList, 
										 duplicatedCell1->scmNucleusBPCentre, 
										 leftMask);

#ifdef ENABLE_NUCLEOLI
	i3d::Image3d<bool> leftNucleolus1, leftNucleolus2;

	// create the first nucleolus in the left mask
	DEBUG_REPORT("Creating nucleolus 1/2 in the left mask");
	GenerateNucleolus(leftMask, nucleolusDiameter, leftNucleolus1);

	// and store into the object
	duplicatedCell1->scmNucleoli1OuterBPNumber = 
			  ResetBoundaryPoints(duplicatedCell1->scmNucleoli1BPList,
										 duplicatedCell1->scmNucleoli1BPCentre,
										 leftNucleolus1); 
		 
	// create the second nucleolus in the left mask
	DEBUG_REPORT("Creating nucleolus 2/2 in the left mask");
	GenerateNucleolus(leftMask, nucleolusDiameter, leftNucleolus2);

	// and store into the object
	duplicatedCell1->scmNucleoli2OuterBPNumber = 
			  ResetBoundaryPoints(duplicatedCell1->scmNucleoli2BPList,
										 duplicatedCell1->scmNucleoli2BPCentre,
										 leftNucleolus2); 
#endif
		 
	duplicatedCell2 = new NewCell;

	// deform right mask and create nucleoli
	DEBUG_REPORT("Smoothing right mask");
	smoother = i3d::fls::MCMRoutine(rightMask, &rightMask, radius);
	smoother->Initialize();

	for (size_t l=0; l<levelOfSmoothness; l++)
	{ 
		 // deform
		 smoother->Iterate(smoother->InterfaceSize()); //*levelOfSmoothness); 
		 smoother->Mask(rightMask.begin(), 
							 i3d::fls::ResultCreator::InteriorAndInterface);

		 // mask has changed -> reset distance map
		 if (rightDistanceMap)
		 {
			  delete rightDistanceMap;
			  rightDistanceMap = NULL;
		 }

		 // and shift keep the molecules inside the mask
		 for (size_t i=1; i < chrCount; i+=2) 
		 {
			  ChrMoveWithBrownBM(i,0.05f,rightMask,rightDistanceMap,1);
		 }
	}
	delete smoother;

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

#ifdef ENABLE_NUCLEOLI
	i3d::Image3d<bool> rightNucleolus1, rightNucleolus2;

	// create the first nucleolus in the right mask
	DEBUG_REPORT("Creating nucleolus 1/2 in the right mask");
	GenerateNucleolus(rightMask, nucleolusDiameter, rightNucleolus1);

	// and store into the object
	duplicatedCell2->scmNucleoli1OuterBPNumber = 
			  ResetBoundaryPoints(duplicatedCell2->scmNucleoli1BPList,
										 duplicatedCell2->scmNucleoli1BPCentre,
										 rightNucleolus1); 
		 
	// create the second nucleolus in the right mask
	DEBUG_REPORT("Creating nucleolus 2/2 in the right mask");
	GenerateNucleolus(rightMask, nucleolusDiameter, rightNucleolus2);

	// and store into the object
	duplicatedCell2->scmNucleoli2OuterBPNumber = 
			  ResetBoundaryPoints(duplicatedCell2->scmNucleoli2BPList,
										 duplicatedCell2->scmNucleoli2BPCentre,
										 rightNucleolus2); 
#endif

	// up to now, everything was created in binary version. Here, let us
	// convert it into labeled image. This way we save some memory. The 
	// practical reason is that Vlado likes labeled images and David binary ones :)
		 
	i3d::Image3d<MV> maskNucleus;
	maskNucleus.CopyMetaData(maskCell);
	maskNucleus.Clear(); //inits with zeros

	DEBUG_REPORT("Rendering left mask");
	RenderBPListIntoMask(maskNucleus,(MV)1,
								 duplicatedCell1->scmNucleusBPList,
								 duplicatedCell1->scmNucleusBPCentre,
								 duplicatedCell1->scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
	//render all nucleoli with different colour into the nucleus mask
	DEBUG_REPORT("Rendering nucleolus 1/2 in the left mask");
	RenderBPListIntoMask(maskNucleus,(MV)2,
								 duplicatedCell1->scmNucleoli1BPList,
								 duplicatedCell1->scmNucleoli1BPCentre,
								 duplicatedCell1->scmNucleoli1OuterBPNumber);

	DEBUG_REPORT("Rendering nucleolus 2/2 in the left mask");
	RenderBPListIntoMask(maskNucleus,(MV)3,
								 duplicatedCell1->scmNucleoli2BPList,
								 duplicatedCell1->scmNucleoli2BPCentre,
								 duplicatedCell1->scmNucleoli2OuterBPNumber);
#endif

	DEBUG_REPORT("Rendering right mask");
	RenderBPListIntoMask(maskNucleus,(MV)4,
								 duplicatedCell2->scmNucleusBPList,
								 duplicatedCell2->scmNucleusBPCentre,
								 duplicatedCell2->scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
	DEBUG_REPORT("Rendering nucleolus 1/2 in the right mask");
	RenderBPListIntoMask(maskNucleus,(MV)5, 
								 duplicatedCell2->scmNucleoli1BPList,
								 duplicatedCell2->scmNucleoli1BPCentre,
								 duplicatedCell2->scmNucleoli1OuterBPNumber);

	DEBUG_REPORT("Rendering nucleolus 2/2 in the right mask");
	RenderBPListIntoMask(maskNucleus,(MV)6,
								 duplicatedCell2->scmNucleoli2BPList,
								 duplicatedCell2->scmNucleoli2BPCentre,
								 duplicatedCell2->scmNucleoli2OuterBPNumber);

	// now in the maskNucleus image: 0 means outside,
	// 1,4 nucleus, 2,3,5,6 each nucleolus
#endif


	DEBUG_REPORT("Rendering finished");

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

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

	// update chromocentres - we must update their position before
	// we proceed as the chromosomes were moved (split into two halves)
	ChrUpdateStats();

	// ----- 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
	
	// make copy of left chromocentres and move them to the new position
	std::vector<Dot> newLeftChrCentres;
	GenerateRandomPositions(leftMask, chrCount/2, newLeftChrCentres);

	std::vector<Dot> oldLeftChrCentres(0);
	for (size_t i=0; i<chrCentres.size(); i++)
	{
		 if (EVEN(i))
		 {
			  oldLeftChrCentres.push_back(chrCentres[i]);
		 }
	}

	std::vector< std::vector<Dot> /**/> leftMovement;

	GenerateMovementOfChromocenters(oldLeftChrCentres,
							newLeftChrCentres,
							noFrames+1,
							leftMovement);

	// make copy of right chromocentres and move them to the new position
	std::vector<Dot> newRightChrCentres;
	GenerateRandomPositions(rightMask, chrCount/2, newRightChrCentres);

	std::vector<Dot> oldRightChrCentres(0);
	for (size_t i=0; i<chrCentres.size(); i++)
	{
		 if (ODD(i))
		 {
			  oldRightChrCentres.push_back(chrCentres[i]);
		 }
	}

	std::vector< std::vector<Dot> /**/> rightMovement;

	GenerateMovementOfChromocenters(oldRightChrCentres,
							newRightChrCentres,
							noFrames+1,
							rightMovement);

	//should not be required as 'maskNucleus' was rendered from BP lists
	//that were reseted from last versions of 'leftMask' and 'rightMask'
	//
	//and last 'leftDistanceMap' and 'rightDistanceMap' were regenerated
	//from the last versions of 'leftMask' and 'rightMask'
	//
	//but anyway, only to be mega-sure: reset DM masks
	if (leftDistanceMap) {
		delete leftDistanceMap;
		leftDistanceMap=NULL;
	}
	if (rightDistanceMap) {
		delete rightDistanceMap;
		rightDistanceMap=NULL;
	}

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

		//
		// internals
		//

		//no change of cell shape here

		//just movement of dots:
		//over all chromosomes
		float progress = ((float) T+1.0f) / ((float)noFrames);

		REPORT("Starting " << 100*progress << "% of decondensation and shift");
		for (size_t i=0; i < chrCount; ++i) 
		{
			 // Manage separately new cells. Even chromosomes belong to left cell. 
			 // Odd chromosomes belong to right cell.
			 if (EVEN(i))
			 { 
				  size_t Lindex = i/2;

				  i3d::Vector3d<float> Lshift =
						 leftMovement[T+1][Lindex].pos - leftMovement[T][Lindex].pos;

				  ChrMoveByVector(i, Lshift, maskNucleus, leftDistanceMap, (MV)1);

				  // TODO: tady je ted problem, protoze vygenerovani
				  // RandomWalk by melo byt az na konci Telofaze. V jejich prubehu
				  // by struktura chromatinu k tomu mela konvergovat.
				  // Protoze je ale teto faze velmi kratka, tak tu nevzknika
				  // zadna pekna sekvence, ale primo to preskoci na konec teto faze
				  // Zaver: je to treba doladit, ale nespecha to :-)
				  //
				  if (T==0)
				  {
						ChrRandomWalk(i, 0.13f, 1.4f, 0.0f, 
										  maskNucleus, leftDistanceMap, (MV) 1);
				  }
				  else
				  { 
						for (size_t j=0; j<5; j++)
						{
							 ChrMoveWithBrown(i, progress*0.05f,maskNucleus,
													leftDistanceMap, (MV) 1);
						}
				  }
			 }
			 else
			 {
				  size_t Rindex = (i-1)/2;

				  i3d::Vector3d<float> Rshift =
					 rightMovement[T+1][Rindex].pos - rightMovement[T][Rindex].pos;

				  ChrMoveByVector(i, Rshift, maskNucleus, rightDistanceMap, (MV) 4);

				  if (T==0)
				  {
						ChrRandomWalk(i, 0.13f, 1.4f, 0.0f, 
										  maskNucleus, rightDistanceMap, (MV) 4);
				  }
				  else
				  { 
						for (size_t j=0; j<5; j++)
						{
							 ChrMoveWithBrown(i, progress*0.05f,maskNucleus,
													rightDistanceMap, (MV) 4);
						}
				  }
			 }
		}
		REPORT(100*progress << " % of decondensation and shift completed");


		//
		// externals
		//

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

		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
		MoveBPListAndCentre(scmCellBPList,
								  scmCellBPCentre,
								  FF);

		ScmRemoveCellFromMask(*scheduler.sceneMasks[timePoint+1]);
		ScmRenderCellIntoMask(*scheduler.sceneMasks[timePoint+1]);

#ifdef GTGEN_WITH_NUCLEIMASKS
		//render both nuclei as single one, we still have not split so far
		RenderBPListIntoMask(*scheduler.sceneNuclei[timePoint+1],this->ID,
			duplicatedCell1->scmNucleusBPList,
			duplicatedCell1->scmNucleusBPCentre,
			duplicatedCell1->scmNucleusOuterBPNumber,
			slsFF);
		RenderBPListIntoMask(*scheduler.sceneNuclei[timePoint+1],this->ID,
			duplicatedCell2->scmNucleusBPList,
			duplicatedCell2->scmNucleusBPCentre,
			duplicatedCell2->scmNucleusOuterBPNumber,
			slsFF);
#endif

		if (T == noFrames-1) 
		{
			//changed request for synchronization
			DEBUG_REPORT("Synchronizing.");

			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);
	
			MoveBPListAndCentre(duplicatedCell2->scmNucleusBPList,
									  duplicatedCell2->scmNucleusBPCentre,
									  slsFF);
	
			MoveBPListAndCentre(duplicatedCell2->scmNucleoli1BPList,
									  duplicatedCell2->scmNucleoli1BPCentre,
									  slsFF);
	
			MoveBPListAndCentre(duplicatedCell2->scmNucleoli2BPList,
									  duplicatedCell2->scmNucleoli2BPCentre,
									  slsFF);
#endif
				
			DEBUG_REPORT("Rendering the updated nucleus and nucleoli");

			maskNucleus.Clear();
			RenderBPListIntoMask(maskNucleus,(MV)1,
									 duplicatedCell1->scmNucleusBPList,
									 duplicatedCell1->scmNucleusBPCentre,
									 duplicatedCell1->scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
			RenderBPListIntoMask(maskNucleus,(MV)2,
									 duplicatedCell1->scmNucleoli1BPList,
									 duplicatedCell1->scmNucleoli1BPCentre,
									 duplicatedCell1->scmNucleoli1OuterBPNumber);
			RenderBPListIntoMask(maskNucleus,(MV)3,
									 duplicatedCell1->scmNucleoli2BPList,
									 duplicatedCell1->scmNucleoli2BPCentre,
									 duplicatedCell1->scmNucleoli2OuterBPNumber);
#endif
		
			RenderBPListIntoMask(maskNucleus,(MV)4,
									 duplicatedCell2->scmNucleusBPList,
									 duplicatedCell2->scmNucleusBPCentre,
									 duplicatedCell2->scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
			RenderBPListIntoMask(maskNucleus,(MV)5, 
									 duplicatedCell2->scmNucleoli1BPList,
									 duplicatedCell2->scmNucleoli1BPCentre,
									 duplicatedCell2->scmNucleoli1OuterBPNumber);
			RenderBPListIntoMask(maskNucleus,(MV)6,
									 duplicatedCell2->scmNucleoli2BPList,
									 duplicatedCell2->scmNucleoli2BPCentre,
									 duplicatedCell2->scmNucleoli2OuterBPNumber);
#endif

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

			if (leftDistanceMap)
			{
				 delete leftDistanceMap; 
				 leftDistanceMap = NULL; // force recomputing distance map
			}
		
			if (rightDistanceMap)
			{
				 delete rightDistanceMap; 
				 rightDistanceMap = NULL; // force recomputing distance map
			}

			// update dots in the masks:
			// left mask
			for (size_t i=0; i < chrCount; i+=2) 
				ChrMoveByFlow(i, slsFF, maskNucleus, leftDistanceMap,(MV)1); 

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

			//the last 'false' render the leftDistanceMap parameter -- can be there anything instead of it
			ChrCentrosomesMoveByFlow(slsFF,maskNucleus,leftDistanceMap,(MV)1,false);

			ChrUpdateStats();

			//draw the phantom image
			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
			ChrRenderIntoPhantoms(*scheduler.scenePhantoms[timePoint+1],slsFF);
#ifdef GTGEN_WITH_TERRITORIES
			ChrRenderIntoTerritories(*scheduler.sceneTerritories[timePoint+1],slsFF);
#endif
		}

		++timePoint;
	}

	//the scmNucleusBPList as well the lists for nucleoli is not used anymore
	//the lists are replaced with those inside duplicatedCell1 and duplicatedCell2

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

	if (rightDistanceMap)
		 delete rightDistanceMap;
}

//----------------------------------------------------------------------
// 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>;
