/**********************************************************************
* 
* cell_06_G1Phase.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 G1-phase.
*
***********************************************************************/

#include <sstream>

#include "cell.h"
#include "toolbox/flowfields.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>::DoG1Phase(	const size_t noFrames,
				const size_t fromFrame,
				const size_t toFrame)
{
	 REPORT("\n   *****   *****   cell ID=" << ID << ", STARTING G1-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, 0.5f,0.5f,0.5f);
	//note: increase boundaries as the cell will _grow_
	// (will grow by 1/3 as this phase is called 3 times)

	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 ----------
	//compute the current cell size
	const MV *scPtr=scheduler.sceneMasks[timePoint]->GetFirstVoxelAddr();
	const MV *scPtrLast=scPtr + scheduler.sceneMasks[timePoint]->GetImageSize();
	float mySum=0.f;
	for (; scPtr != scPtrLast; ++scPtr) if (*scPtr == ID) mySum+=1.f;

	//compute volume of sphere with given diameter in microns:
	//should be 4/3 pi radius^3
	float desiredSum=cellDiameter*cellDiameter*cellDiameter/6.f * 3.14159f;
	//now convert it to cubic pixels
	desiredSum*=scheduler.GetSceneRes().x 
		  * scheduler.GetSceneRes().y
		  * scheduler.GetSceneRes().z;

	//grow a little bit less in the first two phases to provide some room for variability,
	//make grow a little random among cells if we are in the last G1 block
	if (toFrame==noFrames) {
		//last phase:
		desiredSum*=0.82f;
		desiredSum=std::max( GetRandomGauss(desiredSum,0.13f*desiredSum),
					mySum ); //but do not shrink!
	}
	else desiredSum*=0.80f;
	//note: the 0.8x are magic fine-tunning constants to arrive at expected volumes

	DEBUG_REPORT("remains to grow " << desiredSum/mySum -1.f
		<< " times more in volume in the next "
		<< noFrames-fromFrame << " frames");

	//estimate current radius of this cell
	const float r0=0.5f*cellDiameter*cbrtf(mySum/desiredSum);
	DEBUG_REPORT("i.e. should change volume from " << mySum
			<< " to " << desiredSum << ", r0=" << r0);

	//compute radius grow factor
	const float GF=(desiredSum/mySum -1.f) / (float)(noFrames-fromFrame);

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

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

		//the cell growth
		float r=cbrtf(GF*(float)(T+1) +1.f) - cbrtf(GF*(float)T +1.f);
		r*=r0;
		DEBUG_REPORT("going to grow by " << r << " microns in radius (T="
				<< T << ", ID=" << ID << ")");
		//note: 'r' must be positive to make the cell grow

		//describe growth flow field
		DescribeRadialFlow(FF,
				scmCellBPList,scmCellBPCentre,scmCellOuterBPNumber,
				&r,0);

		//apply it to boundary points
		MoveBPListAndCentre(scmCellBPList,scmCellBPCentre,FF);
		MoveBPListAndCentre(scmNucleusBPList,scmNucleusBPCentre,FF);

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

		//re-render only nucleus as this one is only considered for the chrDotList movements
		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
	
		//apply the growth flow field to dots as well
		if (distanceMap)
		{
			 // mask has changed, force recomputing of distance map
			 delete distanceMap; 
			 distanceMap = NULL;
		}

		//let the dots grow with the cell...
		ChrMoveByFlow(FF, maskNucleus, distanceMap,(MV)1);

		//individual growth-independent movement of dots:
		//over all chromosomes
		if (toFrame == noFrames) {
			//the last block:
			//add small Brown vector to irregularly spread chrCentres all over the place
			for (size_t i=0; i < chrCount; ++i) 
			{
				ChrMoveWithBrown(i,0.02f,maskNucleus,distanceMap,1);
			}
		} else {
			//the remaining first two blocks, "normal" Brown here
			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 again
		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;
	}
	
	if (distanceMap)
		 delete distanceMap;

	//since cell is growing, 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>;
