/**********************************************************************
* 
* cell_07_SPhase.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 S-phase.
*
***********************************************************************/

#include <sstream>

#include "cell.h"
#include "toolbox/flowfields.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>::DoSPhase(	const size_t noFrames,
				const size_t fromFrame,
				const size_t toFrame)
{
	REPORT("\n   *****   *****   cell ID=" << ID << ", STARTING S-phase at timePoint="
		<< timePoint << ", only " << fromFrame << " to "
		<< toFrame-1 << " from total " << noFrames << " frames");

	if (fromFrame >= toFrame)
		throw ERROR_REPORT("invalid interval of frames");

	// ---------- local working masks ----------
	//this phase does not work with mask of cell directly;
	//hence, we won't even render it;
	//we will render only nucleus because of dot movements
	i3d::Image3d<MV> maskNucleus;

	//but the size of _nucleus_ mask image should contain the whole _cell_
	maskNucleus.SetResolution(scheduler.sceneRes);
	maskNucleus.SetOffset(scheduler.sceneMasks[timePoint]->GetOffset());
	AllocateAndZeroNewMask(maskNucleus,scmCellBPList,toFrame-fromFrame, cellLookDistance);

	RenderBPListIntoMask(maskNucleus,(MV)1,
	  scmNucleusBPList,scmNucleusBPCentre,scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
	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
#endif

   i3d::Image3d<float> *distanceMap = NULL;

	// ---------- helper flow fields ----------
	FlowField<float> FF;
	InitFlowField(maskNucleus,FF);

	// ---------- actions a phase needs to do before the frame generating cycle ----------
	//double chrDotsAnyBlock dots except in the last frame...
	size_t chrDotsAnyBlock=0;
	size_t chrDotsLastBlock=0;
	//current offset within the chromosome sub-sequence
	size_t chrDotsOffset;

	if (toFrame-fromFrame == 1) {
		//spesl one frame treatment:
		if (currentPhase == SPhase_p1) {
			//first block, chrDots is at 2/4 of the target size
			//we want to add 1/4 of the target size
			chrDotsLastBlock=chrDots/2;
			chrDotsOffset=0;
		} else {
			//second block, chrDots is at 3/4 of the target size
			chrDotsLastBlock=chrDots/3;
			chrDotsOffset=chrDots/3;
		}
	} else {
		//greater frame budget:
		//note: the phase is split into two blocks
		//note: the original chrDots is guaranteed to be divisible by four
		if (currentPhase == SPhase_p1) {
			chrDotsAnyBlock=(chrDots/2) / (toFrame-fromFrame -1);
			chrDotsLastBlock=(chrDots/2) - chrDotsAnyBlock*(toFrame-fromFrame -1);
			chrDotsOffset=0;
		} else {
			chrDotsAnyBlock=(chrDots/3) / (toFrame-fromFrame -1);
			chrDotsLastBlock=(chrDots/3) - chrDotsAnyBlock*(toFrame-fromFrame -1);
			chrDotsOffset=(chrDots/3);
		}

		//try to further balance the last block
		size_t will_remove=(chrDotsAnyBlock-chrDotsLastBlock)/(toFrame-fromFrame -1);
		chrDotsAnyBlock-=will_remove;
		chrDotsLastBlock+=(toFrame-fromFrame -1)*will_remove;
	}
#ifdef GTGEN_DEBUG
	if (chrDotsLastBlock == 0) {
	  REPORT("going to add " << chrDotsAnyBlock << " dots in every frame");
	} else {
	  REPORT("going to add " << chrDotsAnyBlock << " dots in every frame, "
		<< chrDotsLastBlock << " dots in the last one");
	}
#endif

	//the main cycle
	for (size_t T=fromFrame; T < toFrame; ++T) {
		REPORT("\n   *****   *****   cell ID=" << ID << ", doing S-phase at timePoint=" << timePoint \
		       << " with sync");

		//
		// internals
		//

		//no change of cell shape here

		//what block size shall we use this time?
		const size_t chrDotsNewBlock=
			((T < toFrame-1) || (chrDotsLastBlock == 0))?
					chrDotsAnyBlock : chrDotsLastBlock;

		//make backup of the current dot list, prepare a new one
		std::vector<Molecule> chrDotListBACKUP(chrDotList);
		chrDotList.clear();
		chrDotList.reserve((chrDots+chrDotsNewBlock)*chrCount);

		//add some dots to chrDotList
		for (size_t i=0; i < chrCount; ++i) {
			//copy original portion of every chromosome
			for (size_t j=0; j < chrDots; ++j)
				chrDotList.push_back(chrDotListBACKUP[i*chrDots+j]);

			//double certain sub-portion of DNA strand
			//
			//(note for photobleaching: the inital time of newly generated
			//molecules is set to the current timePoint)
			for (size_t j=0; j < chrDotsNewBlock; ++j)
				//tracking-aware copy
				chrDotList.push_back(Molecule(
					chrDotListBACKUP[i*chrDots+j+chrDotsOffset].pos, 
					chrDotListBACKUP[i*chrDots+j+chrDotsOffset].ID,
					timePoint
					));
		}
		DEBUG_REPORT("added " << chrDotsNewBlock << " dots from offset " << chrDotsOffset
			<< ", new chrDots will be " << chrDots+chrDotsNewBlock);

		//updates...
		chrDots+=chrDotsNewBlock;
		chrDotsOffset+=chrDotsNewBlock;

		//just movement of dots:
		//over all chromosomes
		for (size_t i=0; i < chrCount; ++i) {
			ChrMoveWithBrown(i,0.02f,maskNucleus,distanceMap,1);
			//ChrMoveWithGravity(i,0.25f,maskNucleus,distanceMap,1);
		}


		//
		// externals
		//

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

		// bool collapse = (timePoint == scmPnNextStrongUsage) ? true : false;

		ScmUniversalMovement(FF);


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

		//move the boundary points and draw cell into sceneMasks
		MoveBPListAndCentre(scmCellBPList,scmCellBPCentre,FF);
		MoveBPListAndCentre(scmNucleusBPList,scmNucleusBPCentre,FF);

#ifdef ENABLE_NUCLEOLI
		MoveBPListAndCentre(scmNucleoli1BPList,scmNucleoli1BPCentre,FF);
		MoveBPListAndCentre(scmNucleoli2BPList,scmNucleoli2BPCentre,FF);
#endif

		ScmRemoveCellFromMask(*scheduler.sceneMasks[timePoint+1]);
		ScmRenderCellIntoMask(*scheduler.sceneMasks[timePoint+1]
#ifdef GTGEN_WITH_NUCLEIMASKS
			,scheduler.sceneNuclei[timePoint+1]
#endif
		);

		//update also the local masks
		//nucleous with nucleoli:
		maskNucleus.Clear();
		RenderBPListIntoMask(maskNucleus,(MV)1,
		  scmNucleusBPList,scmNucleusBPCentre,scmNucleusOuterBPNumber);

#ifdef ENABLE_NUCLEOLI
		RenderBPListIntoMask(maskNucleus,(MV)2,
		  scmNucleoli1BPList,scmNucleoli1BPCentre,scmNucleoli1OuterBPNumber);
		RenderBPListIntoMask(maskNucleus,(MV)3,
		  scmNucleoli2BPList,scmNucleoli2BPCentre,scmNucleoli2OuterBPNumber);
#endif

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

		//update dots in the masks
		ChrMoveByFlow(FF, maskNucleus, distanceMap,(MV)1);

		ChrUpdateStats();

		// if strong deformation happened, call the repair function
		// if (collapse)
		// call 'repair' function each RESTART_PERIOD-th frame
		if ((this->timePoint % RESTART_PERIOD) == RESTART_PERIOD-1)
		{
			 // smazme - begin
			 /*
			 i3d::Image3d<PV> kesmazani;
			 kesmazani.CopyMetaData(*scheduler.scenePhantoms[timePoint+1]);
			 char fn[50];
			 sprintf(fn, "ID%.2u_T%.3lu_predKorekci.ics", this->ID, this->timePoint);
			 DEBUG_REPORT("Saving: " << fn);
			 ChrRenderIntoPhantoms(kesmazani);
			 kesmazani.SaveImage(fn);
			 */
			 // smazme - end

			 /*
			 // select appropriate points 
			 std::vector<bool> overcrowded(chrCount);
			 for (size_t i=0; i<chrCount; i++)
			 {
				  overcrowded[i] = chrDensities[i] > 160.0f;
			 }

			 ChrRepairDistrLocally(maskNucleus, overcrowded, distanceMap, MV(1));
			 */
			 ChrRepairDistrWeighted(maskNucleus, distanceMap, MV(1));
			 ChrUpdateStats();
		}

#ifdef GTGEN_DEBUG
		ChrReportStats();
		ChrReportClosests();
#endif

		//here, one may possibly add UnevenDistributionCorrection...
		//problematic chromosomes are likely those with chrDensities[]>25

		//draw the phantom image
		ChrRenderIntoPhantoms(*scheduler.scenePhantoms[timePoint+1]);
#ifdef GTGEN_WITH_TERRITORIES
		ChrRenderIntoTerritories(*scheduler.sceneTerritories[timePoint+1]);
#endif
		++timePoint;
	}

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

	//since cell is doing non-rigid, boundary points move appart
	//we rather re-sample them again
	scmCellOuterBPNumber=
	  ResetBoundaryPoints(scmCellBPList,scmCellBPCentre,*scheduler.sceneMasks[timePoint],ID);
	//note that here we must use current timePoint, the one already finished

	//changing boundary points, we need to re-init the hints image
	scheduler.PnGetMeNonrigidHints(scmCellBPList,scmCellOuterBPNumber,scmPnHints);

	scmNucleusOuterBPNumber=
	  ResetBoundaryPoints(scmNucleusBPList,scmNucleusBPCentre,maskNucleus);

#ifdef ENABLE_NUCLEOLI
	scmNucleoli1OuterBPNumber=
	  ResetBoundaryPoints(scmNucleoli1BPList,scmNucleoli1BPCentre,maskNucleus,(MV)2);
	scmNucleoli2OuterBPNumber=
	  ResetBoundaryPoints(scmNucleoli2BPList,scmNucleoli2BPCentre,maskNucleus,(MV)3);
#endif
}

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