/**********************************************************************
* 
* cell_08_G2Phase.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 G2-phase.
*
***********************************************************************/

#include <sstream>

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

#ifdef GTGEN_WITH_FLOWFIELDS
#include "toolbox/importexport.h"
#endif

//
// 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>::DoG2Phase(const size_t noFrames)
{
	 REPORT("\n   *****   *****   cell ID=" << ID << ", STARTING G2-phase at timePoint=" << 
			  timePoint); 

#ifndef GTGEN_WITH_SOFA
	// ---------- local working masks ----------
	//local working copy of the cell mask - not needed
	//only nucleus
	i3d::Image3d<MV> maskNucleus;

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

	//prepare working copy of cell nucleus with nucleoli
	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 ----------
	//prepare the activity plan to spread the phase duties over the given number of frames

#else //after SOFA

	//init of the accumulated translation and rotation
	if (timePoint == 0)
	{
		slsTranslation=i3d::Vector3d<float>(0.f);
		slsRotation=0.f;
	}
#endif //after SOFA

	//the main cycle
	for (size_t T=0; T < noFrames; ++T) {
		REPORT("\n   *****   *****   cell ID=" << ID << ", doing G2-phase at timePoint=" << timePoint
#ifndef GTGEN_WITH_SOFA
		       << " with sync");
#else
		       << " without sync, with SOFA");
#endif

		//
		// internals
		//

#ifndef GTGEN_WITH_SOFA
		//no change of cell shape here

		//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
#else //after SOFA
		//read the comming cell shape, driven now by the SOFA:
		SOFA_NextImport(timePoint+1);
		scmCellBPList=scmNucleusBPList;
		scmCellOuterBPNumber=scmNucleusOuterBPNumber;
		scmCellBPCentre=scmNucleusBPCentre;

#ifdef GTGEN_WITH_RIGIDMOTION
		if ( !configIni["testing"].present("disable rigid movement") )
		{
			//try to move the cell...
			i3d::Vector3d<float> sugTranslation(0.f);	//suggested transl.
			float sugRotation=0.f;							//suggested rotation

			//has the cell decided to move on (quite independently and intentionally)?
			//should move on average on every 3rd frame
			const bool wantMove=(GetRandomUniform(0.f,1.f) < 0.33f)? true : false;

			//is the current cell position in collision so that it has to move anyway?
			const bool mustMove=ScmNewCellPositionCollide(SOFA_InitialPosition,
																 slsRotation,slsTranslation);

			DEBUG_REPORT("wantMove=" << wantMove << ", mustMove=" << mustMove);

			int tries=0;									//attempts counter
			bool collision=true;							//suggested configuration flag

			//while not many unsuccessful attempts...
			while ((mustMove || wantMove) && (collision) && (tries < 20))
			{
				//suggest the magnitude of the movement
				float MoveStep=GetRandomUniform(0.f,cellMagnitudeOfMovementPerFrame);
				scmLastTranslationVector.y=MoveStep;

				/*
				//MW = Move Weight
				float OldMW=scmLastTranslationVector.x/cellMagnitudeOfMovementPerFrame;
				float NewMW=scmLastTranslationVector.y/cellMagnitudeOfMovementPerFrame;
				*/

				//suggests rotation from the current state
				/*
				sugRotation=GetRandomGauss(
						MoveStep/cellMagnitudeOfMovementPerFrame *scmLastRotationAngle,
						0.08f * (1.f + float(tries)/200.0)
					);
				*/

				/*
				note from wikipedia:

				For the normal distribution, the values less than one standard deviation
				away from the mean account for 68.27% of the set; while two standard
				deviations from the mean account for 95.45%; and three standard deviations
				account for 99.73%.

				Hence, setting mean to +- sigma gives disproportion 15:85 in the set.
				The code should very likely keep the sign/orientation of the rotation.
				*/
				float rotSigma=0.13f; //initial value, should correspond with the max allowed movement
				float rotMean=0.f;	 //if no movement before, rotMean will stay with 0.f
				if (scmLastRotationAngle > 0.f) rotMean=rotSigma;
				if (scmLastRotationAngle < 0.f) rotMean=-rotSigma;
				//narrows down the sigma if large movement has been done recently
				rotSigma*=sqrtf(1.f - 0.95f*scmLastTranslationVector.x/cellMagnitudeOfMovementPerFrame);
				sugRotation=GetRandomGauss(rotMean,rotSigma);
				DEBUG_REPORT("# " << tries << " : mean= " << rotMean << " , sigma= "
								 << rotSigma << " , rotate= " << sugRotation << " , dumped rotate= "
								 << sugRotation*std::min(1.25f*sqrtf(MoveStep/cellMagnitudeOfMovementPerFrame),1.f));

				//eventually, dump the rotation disproportionally to the velocity
				//sugRotation*=sqrtf(MoveStep/cellMagnitudeOfMovementPerFrame);
				sugRotation*=std::min(1.25f*sqrtf(MoveStep/cellMagnitudeOfMovementPerFrame),1.f);

				//the current main orientation is given with Cell::scmCellPolarityVector,
				//final translation is this vector rotated by the sugRotation
				//and of the final length MoveStep
				sugTranslation.x=cos(sugRotation)*scmCellPolarityVector.x
									 -sin(sugRotation)*scmCellPolarityVector.y;

				sugTranslation.y=sin(sugRotation)*scmCellPolarityVector.x
									 +cos(sugRotation)*scmCellPolarityVector.y;

				//scmCellPolarityVector should have unit length, but rather...
				sugTranslation*=MoveStep/i3d::Norm(sugTranslation);

				++tries;

				//check the new suggestion...
				collision=ScmNewCellPositionCollide(SOFA_InitialPosition,
																slsRotation+sugRotation,
																slsTranslation+sugTranslation);
			}

			//for whatever reason, if new position was discovered, keep it:
			if (!collision)
			{
				//accept the new position
				REPORT("translating by " << scmLastTranslationVector.y
							<< " microns and rotating by "
							<< sugRotation*180.f/3.14159f
							<< " degrees after " << tries << " tries");

				//remember the linking transformation
				slsRotation+=sugRotation;
				slsTranslation+=sugTranslation;

				//update the polarity vector
				sugTranslation.x=cos(sugRotation)*scmCellPolarityVector.x
									 -sin(sugRotation)*scmCellPolarityVector.y;
				sugTranslation.y=sin(sugRotation)*scmCellPolarityVector.x
									 +cos(sugRotation)*scmCellPolarityVector.y;
				scmCellPolarityVector.x=sugTranslation.x;
				scmCellPolarityVector.y=sugTranslation.y;

				//remember the last movement
				//scmLastTranslationVector=sugTranslation;
				scmLastTranslationVector.x=scmLastTranslationVector.y;
				scmLastRotationAngle=sugRotation;
			}
			else
			{
				//unsuccessful in obtaining new position...
				//(or wantMove == false)
				if (mustMove)
				{
					REPORT("Warning: looking for escape translation...");

					//radius of searched region...
					const float maxDist=1.f;

					//... which is scanned with this step
					//(should be probably the size of 1px in the given resolution)
					const float step=0.2f; 

					//sweep around in increasing distance from the current position
					for (int d=1; (d <= (int)ceilf(maxDist/step)) && (collision); ++d)
					for (int dx=-d; (dx <= +d) && (collision); ++dx)
					for (int dy=-d; (dy <= +d) && (collision); ++dy)
						//are we at the current perimeter?
						if ((abs(dx)+abs(dy)) == d)
						{
							//TODO VLADO remove
							DEBUG_REPORT("trying at perimeter d=" << d
											<< ": dx=" << dx << ", dy=" << dy);

							//yes, check the new suggestion then...
							sugTranslation.x=float(dx)*step;
							sugTranslation.y=float(dy)*step;
							collision=ScmNewCellPositionCollide(SOFA_InitialPosition,
																slsRotation,
																slsTranslation+sugTranslation);
						}

					if (!collision)
					{
						//accept the new position
						REPORT("translating by " << sugTranslation << " microns, no rotation");

						//remember the linking transformation
						slsRotation+=0.f;
						slsTranslation+=sugTranslation;

						//update the polarity vector: not required due to no rotation at all

						//remember the last movement (with a little bit of pretending... ;-)
						scmLastTranslationVector.x=0.f;
						scmLastRotationAngle=0.f;
					}
					else throw ERROR_REPORT("Unable to find non-colliding movement.");
				}
				else
				{
					REPORT("no movement this round...");
					scmLastTranslationVector.x=0.f;
					scmLastRotationAngle=0.f;
				}
			}

			//finally, commit the agreed position to the cell representation
			SOFA_TransformCellBPs(SOFA_InitialPosition,
							slsRotation,SOFA_InitialPosition+slsTranslation);
			SOFA_TransformNuclStarBPs(SOFA_InitialPosition,
							slsRotation,SOFA_InitialPosition+slsTranslation);
			SOFA_TransformChromatinPs(SOFA_InitialPosition,
							slsRotation,SOFA_InitialPosition+slsTranslation);
		}
		else
		REPORT("TESTING MODE: rigid cell movement is disabled");
#endif
#endif //after SOFA


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


		//update also the local masks
		//no need to update cell now, it is not used in the cycle

#ifndef GTGEN_WITH_SOFA
		//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);
#endif //after SOFA

		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] > 170.0f; //was: 160.0f;
			 }

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

#ifdef GTGEN_DEBUG
		//SOFA// ChrReportStats();
		//SOFA// 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

#ifdef GTGEN_WITH_FLOWFIELDS
		char fn[100];
		sprintf(fn,"ID%02u_T%03lu_chromatinPoints.txt", ID, timePoint+1 );
		ExportPointList(chrDotList,chrDotList.size(),fn);
#endif

		++timePoint;
	}

	//cleaning here if necessary
	
#ifndef GTGEN_WITH_SOFA
	if (distanceMap)
		 delete distanceMap;
#endif //after SOFA

	if (centrosomeA) delete centrosomeA;
	if (centrosomeB) delete centrosomeB;

	// create centrosomes
	centrosomeA = new Dot;
	centrosomeB = new Dot;

	//render cell mask for the GenerateCentrosomes() function
	//into a new fresh only-this purpose image
	i3d::Image3d<bool> maskCell;
	maskCell.SetResolution(scheduler.sceneRes);
	maskCell.SetOffset(scheduler.sceneMasks[timePoint]->GetOffset());
	RenderBPListIntoNewMask(maskCell,true,
		scmCellBPList,scmCellBPCentre,scmCellOuterBPNumber,0, cellLookDistance);

	GenerateCentrosomes(maskCell, centrosomeA->pos, centrosomeB->pos);

#ifndef GTGEN_WITH_SOFA
	//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
#endif //after SOFA
}


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