#include <sstream>

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

//
// NOT NECCESARILY ALL PARTS OF THIS "TEMPLATE" PHASE MUST BE DONE, FOR
// INSTANCE, SKIP THE NUCLEOLUS PARTS IF NO NUCLEOLUS SHOULD EXIST IN THE PHASE
//

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

	//helper information flags:
	//whether to keep local images updated (=synchronized) with the Scheduler::scene images
	char doingSynchronization=1; //0 = no; 1 = yes, do sync
	//whether the status of doingSynchronization was changed
	char changedSynchronizationState=0; //0 = no change; 1 = changed

	// ---------- local working masks ----------
	//based on it, especially on the shape-changing and motion-related activities, obtain
	//local copies of the mask images (cell, nucleus with both nucleoli; whatever will be needed)
	//with ! large borders such that it is able to contain the cell during the whole phase
	i3d::Image3d<bool> maskCell;
	maskCell.SetResolution(scheduler.sceneRes);
	maskCell.SetOffset(scheduler.sceneMasks[timePoint]->GetOffset());
	RenderBPListIntoNewMask(maskCell,true,
	  scmCellBPList,scmCellBPCentre,scmCellOuterBPNumber,
	  noFrames, cellLookDistance);
	  //or tell explicitly how much to extend:
	  //scmCellBPList,scmCellBPCentre,scmCellOuterBPNumber,2.f,2.f,1.f);
	//note: the local copy of cell mask is not necessarily important to exist,
	//the moving of cells is driven only with Cell::scmCellBPList and current
	//Scheduler::sceneMasks[this->timePoint+1]

	//if other structures/masks are needed create them here as well;
	//for nucleus, the nucleus image will of the same size:
	i3d::Image3d<MV> maskNucleus;
	maskNucleus.CopyMetaData(maskCell);
	maskNucleus.Clear(); //inits with zeros
	RenderBPListIntoMask(maskNucleus,(MV)1,
	  scmNucleusBPList,scmNucleusBPCentre,scmNucleusOuterBPNumber);

	//render both nucleoli with different colour into the nucleus mask
	RenderBPListIntoMask(maskNucleus,(MV)2,
	  scmNucleoli1BPList,scmNucleoli1BPCentre,scmNucleoli1OuterBPNumber);
	RenderBPListIntoMask(maskNucleus,(MV)3,
	  scmNucleoli2BPList,scmNucleoli2BPCentre,scmNucleoli2OuterBPNumber);
	//now in the maskNucleus image: 0 means outside,
	//1 nucleus, 2 and 3 each nucleolus

	// ---------- helper flow fields ----------
	//the "sync last sync Flow Field" (aka slsTOK), the one that
	//always binds local mask situation to the real situation in the scene
	FlowField<float> slsFF;
	InitFlowField(maskCell,slsFF);

	//the current in_between_the_last_two_frames Flow Field (aka TOK),
	//describes new changes that emerged in the current timePoint
	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

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

		//if we need to change the requirement on synchronization, we do anywhere
		//in the internals and externals sections but the change will be noticed
		//only in the end of this frame generating cycle:
		//doingSynchronization=new state;
		//changedSynchronizationState=1; //indication of the new state, important!


		//
		// internally-induced stuff:
		//

		//changes the shape of the cell, if required, e.g. due to internal cell needs,
		//	e.g. elongation or mitosis split
		//(this changes the local mask immediatelly)
		//(for the shape changes: consider *scheduler.sceneMasks[this->timePoint+1] image)


		// START if mask shape changed
		//
		//if cell shape has changed, update the boundary pixel lists:
		scmCellOuterBPNumber=ResetBoundaryPoints(scmCellBPList,scmCellBPCentre,maskCell);
		//
		//if shape of other masks, e.g. nucleolus with nuclei, were changed as well,
		//update their lists too:
		scmNucleusOuterBPNumber=
		  ResetBoundaryPoints(scmNucleusBPList,scmNucleusBPCentre,maskNucleus);
		scmNucleoli1OuterBPNumber=
		  ResetBoundaryPoints(scmNucleoli1BPList,scmNucleoli1BPCentre,maskNucleus,(MV)2);
		scmNucleoli2OuterBPNumber=
		  ResetBoundaryPoints(scmNucleoli2BPList,scmNucleoli2BPCentre,maskNucleus,(MV)3);

		//but boundary points will now represent situation in the local masks, which is okay if
		//we're syncing; otherwise we need to project this situation back to the sceneMasks perspective
		if (!doingSynchronization) {
			MoveBPListAndCentre(scmCellBPList,scmCellBPCentre,slsFF);
			MoveBPListAndCentre(scmNucleusBPList,scmNucleusBPCentre,slsFF);
			MoveBPListAndCentre(scmNucleoli1BPList,scmNucleoli1BPCentre,slsFF);
			MoveBPListAndCentre(scmNucleoli2BPList,scmNucleoli2BPCentre,slsFF);
		}
		//
		// END if mask shape changed


		//changes the content of the cell (in the new masks!),
		//	e.g. Cell::chrDotList, that would be LOCAL independent movement of molecules
		//(Cell::chrDotList and company is always consistent/synchronized with the local mask)
		//(ChrUpdateStats() is called in the end of the for(T) cycle, no need to call it here)

		//over all chromosomes
		for (size_t i=0; i < chrCount; ++i) {
			//do "significant" Brownian motion within the mask
			ChrMoveWithBrown(i,0.25f,maskNucleus,1);
			//do "very little" Gravity-induced motion within the mask
			ChrMoveWithGravity(i,0.25f,maskNucleus,1);
		}


#ifdef GTGEN_DEBUG_
		//helper debug in case something goes wrong
		std::ostringstream fn;
		fn.fill('0');
		fn << "cell";
		fn.width(2); fn << ID << "_T";
		fn.width(3); fn << timePoint+1 << "_localmaskAfterINTERNAL.ics";
		//mask.SaveImage(fn.str().c_str());
#endif

		//
		// externally-induced stuff:
		//

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

		//changes the shape of the cell, if required, due to external influences,
		//	e.g. cell floating in liquid medium, cell deforming a bit
		//(this changes the local mask immediatelly if doingSynchronization==1;
		// this changes the local mask some time later if doingSynchronization==0)
		//
		//now, describe the desired current motion using the flow field FF;
		//or if you just don't care, use this one:
		ScmUniversalMovement(FF);


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

		//in this section we gonna do the movement and render it into the
		//scene* images; if synchronizing, we will render it even into the local
		//masks and move chrDotList

		//move the boundary points, which is, in other words: update
		//representation of the cell mask in the sceneMasks image
		MoveBPListAndCentre(scmCellBPList,scmCellBPCentre,FF);
		//note: we need to do it here before the FF field may change...

		//if the nucleus and nucleoli lists are valid, move them as well
		MoveBPListAndCentre(scmNucleusBPList,scmNucleusBPCentre,FF);
		MoveBPListAndCentre(scmNucleoli1BPList,scmNucleoli1BPCentre,FF);
		MoveBPListAndCentre(scmNucleoli2BPList,scmNucleoli2BPCentre,FF);

#ifdef GTGEN_DEBUG_
		//helper debug in case something goes wrong:
		//used to study development of boundary point positions in the scene image;
		//when debuging flood fill leaks, Render....DEBUG shows points only for the failing frame
		i3d::Image3d<MV> test;
		test.CopyMetaData(*scheduler.sceneMasks[timePoint]);
		test.Clear();
		RenderBPListIntoMaskForDEBUG(test,(MV)200,scmCellBPList,scmCellBPCentre);
		static int fr=0;
		char mn[]="testX.ics";
		mn[4]=fr+48; ++fr;
		test.SaveImage(mn);
#endif
		//draw the cell mask into the currently processed scene frame 
		ScmRemoveCellFromMask(*scheduler.sceneMasks[timePoint+1]);
		ScmRenderCellIntoMask(*scheduler.sceneMasks[timePoint+1]);


		if ((T == noFrames-1) && (doingSynchronization == 0)) {
			//we're in the last frame and we are not synchronizing, we must
			//start synchronizing right now to be synchronized after the phase ends
			doingSynchronization=1; //new state
			changedSynchronizationState=1; //indication of the new state
		}

		//note: the FF is the flow field that will "commit" changes
		if (changedSynchronizationState) {
			//change in the synchronization demand
			if (doingSynchronization == 1) {
				DEBUG_REPORT("Synchronization got turned ON.");
				//transition: 0->1
				//prepend the accumulated flow field to the current one...
				ConcatenateFlowFields(slsFF,FF);
				//FF=slsFF; -- this is what we want to do

				//... and pretend like we were always been synchronizing
				//slsFF=0; -- this is what we want to do

				//to conclude: FF can be lost
				//we can afford short-cut copying...
				i3d::Image3d<float> *tmp;
				tmp=FF.x; FF.x=slsFF.x; slsFF.x=tmp;
				tmp=FF.y; FF.y=slsFF.y; slsFF.y=tmp;
				tmp=FF.z; FF.z=slsFF.z; slsFF.z=tmp;
				slsFF.x->GetVoxelData()=0.f;
				slsFF.y->GetVoxelData()=0.f;
				slsFF.z->GetVoxelData()=0.f;
			} else {
				//transition: 1->0
				DEBUG_REPORT("Synchronization got turned OFF.");
			}

			//the change is acknowledged
			changedSynchronizationState=0;
		}


		if (doingSynchronization) {
			//update also the local masks
			//cell:
			maskCell.GetVoxelData()=0;
			RenderBPListIntoMask(maskCell,true,
				scmCellBPList,scmCellBPCentre,scmCellOuterBPNumber);

			//nucleous with nucleoli:
			maskNucleus.Clear(); //inits with zeros
			RenderBPListIntoMask(maskNucleus,(MV)1,
			  scmNucleusBPList,scmNucleusBPCentre,scmNucleusOuterBPNumber);
			RenderBPListIntoMask(maskNucleus,(MV)2,
			  scmNucleoli1BPList,scmNucleoli1BPCentre,scmNucleoli1OuterBPNumber);
			RenderBPListIntoMask(maskNucleus,(MV)3,
			  scmNucleoli2BPList,scmNucleoli2BPCentre,scmNucleoli2OuterBPNumber);

			//masks are done, move on to the phantom image:
			//synchronize chrDotList to the new situation
			ChrMoveByFlow(FF,*scheduler.sceneMasks[timePoint+1],this->ID);
			//this function, as a side effect, makes sure points
			//will not appear outside the ground-truth mask, that's good ;-)
			//
			//alternatively, may be a bit faster (as smaller image is considered)
			ChrMoveByFlowBM(FF,maskCell);
			ChrCentrosomesMoveByFlowBM(FF,maskCell);

			//draw the phantom image
			ChrRenderIntoPhantoms(*scheduler.scenePhantoms[timePoint+1]);
#ifdef GTGEN_WITH_TERRITORIES
			ChrRenderIntoTerritories(*scheduler.sceneTerritories[timePoint+1]);
#endif
		} else {
			//do not touch the local mask
			//but rather remember the new changes
			ConcatenateFlowFields(slsFF,FF);

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

		//anyway, the stats about chrDotList should be updated
		ChrUpdateStats();

#ifdef GTGEN_DEBUG_
		//helper debug in case something goes wrong:
		//draw chromosome territories by drawing all dots with value representing
		//their chromosome "ID", should be viewed with labeling turned on in viewer3d
		i3d::Image3d<i3d::GRAY8> imgT;
		imgT.CopyMetaData(maskCell);
		imgT.Clear();

		//time savers:
		const float xRes=imgT.GetResolution().GetX();
		const float yRes=imgT.GetResolution().GetY();
		const float zRes=imgT.GetResolution().GetZ();
		const float xOff=imgT.GetOffset().x;
		const float yOff=imgT.GetOffset().y;
		const float zOff=imgT.GetOffset().z;

		size_t i=0;
		for (size_t j=0; j < chrCount; ++j)
			for (k=0; k < chrDots; ++k, ++i) {
				//turn micron position to pixel one
				const int X=(int)roundf( (chrDotList[i].pos.x-xOff) *xRes);
				const int Y=(int)roundf( (chrDotList[i].pos.y-yOff) *yRes);
				const int Z=(int)roundf( (chrDotList[i].pos.z-zOff) *zRes);

				//points should fit inside the image
				imgT.SetVoxel(X,Y,Z,j);
			}

		char fnn[50];
		sprintf(fnn,"cell%.2lu_T%.3lu_territories.ics",ID,timePoint+1);
		imgT.SaveImage(fnn);
#endif

#ifdef GTGEN_DEBUG_
		//helper debug in case something goes wrong:
		fn.seekp(0);
		fn << "cell";
		fn.width(2); fn << ID << "_T";
		fn.width(3); fn << timePoint+1 << "_localmaskAfterEXTERNAL.ics";
		if (doingSynchronization) mask.SaveImage(fn.str().c_str());
		else {
			i3d::Image3d<i3d::GRAY8> maskCopy,maskUpdated;
			maskCopy.CopyMetaData(mask);
			maskCopy.MakeRoom(mask.GetSize());
			for (size_t q=0; q < mask.GetImageSize(); ++q)
				maskCopy.SetVoxel(q, (mask.GetVoxel(q))? 100:0 );
			//fn.seekp(34); fn << "_s.ics";
			//maskCopy.SaveImage(fn.str().c_str());
			ImageForwardTransformation(maskCopy,maskUpdated,slsFF);
			fn.seekp(34); fn << "_t.ics";
			maskUpdated.SaveImage(fn.str().c_str());
		}
#endif

		//signify that the cell has finished the current (timePoint+1) frame
		++timePoint;
	}

	//cleaning here if necessary
}

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