/**********************************************************************
* 
* cell_03_Anaphase.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 anaphase.
*
***********************************************************************/

#include <sstream>
#include <i3d/descriptors/shape.h>
#include <i3d/DistanceTransform.h>
#include <i3d/FastLevelSet.h>
#include <i3d/morphology.h>
#include <i3d/transform.h>
#include <i3d/toolbox.h>

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

//-------------------------------------------------------------------
typedef std::vector<
				std::pair<i3d::Vector3d<float>,
							 i3d::Vector3d<float>/**/ >/**/> ArrayOfCentrosomes;

//-------------------------------------------------------------------
// This function accepts the cell mask and finds the major axes. Then
// it locates the position of both centrosomes (opposite poles along
// major axis). Finally it stretches the mask and in parallel it shift
// the centrosomes from each other.
// 
// The output is the sequence of masks, i.e. the history of stretching
// the mask. The last argument gives the positions of centrosomes during
// the stretching. Both output sequences have the same length.
//-------------------------------------------------------------------
void StretchCellMask( const i3d::Image3d<bool> &cellMask,
							 const i3d::Image3d<bool> *domain,
							 ArrayOfMasks &masks,
							 ArrayOfCentrosomes &centrosomes);

//-------------------------------------------------------------------
// drag chromocenters to centrosomes
//-------------------------------------------------------------------
void ShiftChromosomesToCentrosomes
	(const std::vector< Dot > &oldChrCenters,
	 std::vector< Dot > &newChrCenters,
	 const i3d::Vector3d<float>& centrosomeA,
	 const i3d::Vector3d<float>& centrosomeB,
	 const float fraction);

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

	char doingSynchronization=0; //0 = no; 1 = yes, do sync

	// ---------- 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,1.2f,1.2f,1.2f);

	//no nucleus and nucleoli in this phase
   
	i3d::Image3d<float> *distanceMap = NULL;

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

	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

	// first of all - collect the masks of all the neighbouring cell
	// and project them into one binary mask image in order to define domain where
	// the cell can live (move, deform)
	i3d::Image3d<bool> domain;
	ShowDomainForThisPhase(maskCell, domain, noFrames);

	// take the cell mask and stretch in within the domain
	ArrayOfCentrosomes aoc;
	ArrayOfMasks aom;

	StretchCellMask(maskCell, &domain, aom, aoc);

	// resample the sequence of time frames into required number of samples
	ResampleArrayNN(aom, noFrames);
	ResampleArrayNN(aoc, noFrames);

	centrosomeA->pos = aoc.back().first;
	centrosomeB->pos = aoc.back().second;

	// chop the chromosomes (each one into two parts)
	chrCount *= 2;
	chrDots /= 2;
	ChrUpdateStats();

	// a new variable needed in the following loop
	std::vector<Dot> updatedChrCentres;

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

		//
		// internals
		//
		maskCell = aom[T]; //cell shape change!

		scmCellOuterBPNumber=ResetBoundaryPoints(scmCellBPList,
							scmCellBPCentre,
							maskCell);

		ShiftChromosomesToCentrosomes(chrCentres,
						updatedChrCentres,
						aoc[T].first,
						aoc[T].second,
						2*noFrames); // TODO: testing
		if (!doingSynchronization) 
		{
			MoveBPListAndCentre(scmCellBPList,scmCellBPCentre,slsFF);
		}

		if (distanceMap)
		{
			 // mask has changed, force recomputing of distance map
			 delete distanceMap; 
			 distanceMap = NULL;
		}

		//just movement of dots:
		//over all chromosomes
		for (size_t i=0; i < chrCount; ++i) 
		{
			ChrMoveWithBrownBM(i,0.05f,maskCell,distanceMap,1);
		
			i3d::Vector3d<float> shift = 
					  updatedChrCentres[i].pos - chrCentres[i].pos;

			ChrMoveByVectorBM(i,shift,maskCell,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;

		ScmUniversalMovement(FF);


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

		//move the boundary points and draw cell into sceneMasks
		MoveBPListAndCentre(scmCellBPList,scmCellBPCentre,FF);
		ScmRemoveCellFromMask(*scheduler.sceneMasks[timePoint+1]);
		ScmRenderCellIntoMask(*scheduler.sceneMasks[timePoint+1]);
		  //no nucleus in this phase, no 2nd parameter here

		//after the last frame, metadata must be synchronized with the scene
		if ((T == noFrames-1) && (doingSynchronization == 0)) {
			doingSynchronization=1;

			//changed request for synchronization
			DEBUG_REPORT("Synchronization got turned ON.");

			ConcatenateFlowFields(slsFF,FF);

			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;
			//there will be no change back to async mode,
			//hence the slsFF will never be used again
			//slsFF.x->GetVoxelData()=0.f;
			//slsFF.y->GetVoxelData()=0.f;
			//slsFF.z->GetVoxelData()=0.f;
		}


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

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

			//update dots in the masks
			ChrMoveByFlowBM(FF, maskCell, distanceMap);
			ChrCentrosomesMoveByFlowBM(FF, maskCell, distanceMap);

			ChrUpdateStats();

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

			ChrUpdateStats();

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

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

	//this phase was changing boundary points, we need to re-init the hints image
	//note: non-rigid was disabled in ScmUniversalMovement() for this phase
	scheduler.PnGetMeNonrigidHints(scmCellBPList,scmCellOuterBPNumber,scmPnHints);
}

//-------------------------------------------------------------------
// drag chromocenters to centrosomes
//-------------------------------------------------------------------
void ShiftChromosomesToCentrosomes
	(const std::vector< Dot > &oldChrCenters,
	 std::vector< Dot > &newChrCenters,
	 const i3d::Vector3d<float>& centrosomeA,
	 const i3d::Vector3d<float>& centrosomeB,
	 const float fraction)
{
	 size_t sz = oldChrCenters.size();

	 // allocate
	 newChrCenters.resize(sz);

	 // one half (even) of chromocenters is dragged to centrosomeA
	 for (size_t i=0; i<sz; i+=2)
	 {
		  i3d::Vector3d<float> currPos = oldChrCenters[i].pos;

		  // inspect all the neighbouring chromocenters
		  i3d::Vector3d<float> interactions(0), nbh;
		  for (size_t k=0; k<sz; k+=2)
		  {
				if (k != i)
				{
					 nbh = currPos - oldChrCenters[k].pos;
					 interactions += nbh/i3d::Norm2(nbh);
				}
		  }

		  i3d::Vector3d<float> vec = centrosomeA - currPos;
		  vec *= (1.0f/(fraction));

		  interactions *= i3d::Norm(vec)/i3d::Norm(interactions);

		  newChrCenters[i].pos = currPos + vec + interactions/5.0f;
	 }

	 // one half (odd) of chromocenters is dragged to centrosomeB
	 for (size_t i=1; i<sz; i+=2)
	 {
		  i3d::Vector3d<float> currPos = oldChrCenters[i].pos;

		  // inspect all the neighbouring chromocenters
		  i3d::Vector3d<float> interactions(0), nbh;
		  for (size_t k=1; k<sz; k+=2)
		  {
				if (k != i)
				{
					 nbh = currPos - oldChrCenters[k].pos;
					 interactions += nbh/i3d::Norm2(nbh);
				}
		  }

		  i3d::Vector3d<float> vec = centrosomeB - currPos;
		  vec *= (1.0f/(fraction));

		  interactions *= i3d::Norm(vec)/i3d::Norm(interactions);

		  newChrCenters[i].pos = currPos + vec + interactions/5.0f;
	 }
}

//-------------------------------------------------------------------
// Resample array with nearest neighbour strategy with right alignment!
// We need right alignment as the array contains time sequence and we are
// interested in the most recent (the right most) states of the objects.
//
// Type <T> might be an image, i.e. the upsampling will replicate some images,
// while downsampling deletes some images.
//-------------------------------------------------------------------
template <class T>
void ResampleArrayNN(std::vector<T> &array, size_t samples)
{
	 if (samples == 0)
	 {
		  throw "Why do you want to resample to zero samples?";
	 }

	 size_t sz = array.size();

	 if (samples == sz) // nothing to do
		  return;

	 std::vector<T> auxArray = array;

	 array.clear();
	 array.resize(samples);

	 for (size_t i=0; i<samples; i++)
	 {
		  // this will cause right alignment
		  size_t index = sz - ((samples-i-1)*sz)/samples - 1;
		  array[i] = auxArray[index];
	 }
}

//-------------------------------------------------------------------
// This function accepts the cell mask and finds the major axes. Then
// it locates the position of both centrosomes (opposite poles along
// major axis). Finally it stretches the mask and in parallel it shift
// the centrosomes from each other.
// 
// The output is the sequence of masks, i.e. the history of stretching
// the mask. The last argument gives the positions of centrosomes during
// the stretching. Both output sequences have the same length.
//-------------------------------------------------------------------
void StretchCellMask( const i3d::Image3d<bool> &cellMask,
							 const i3d::Image3d<bool> *domain,
							 ArrayOfMasks &masks,
							 ArrayOfCentrosomes &centrosomes)
{
	 if (domain && (domain->GetSize() != cellMask.GetSize()))
		  throw ERROR_REPORT("different image size!?");

	 i3d::Shape<bool> shape;

	 shape.SetImage(&cellMask);
	 shape.SetLabel(true);
	 size_t newVolume, originalVolume = shape.Volume();

	 // initial position of centrosomes
	 i3d::Vector3d<float> cA, cB;

	 // find both centrosomes in the given mask
	 GenerateCentrosomes(cellMask, cA, cB);

	 // generate a speed image the will cause the cell mask
	 // to be stretch when submitted to the fast level set deformations
	 i3d::Image3d<float> speedImage;
	 i3d::Image3d<bool> mask(cellMask);

	 // some predefined constants suitable for deformation
	 // (values provided by Martin Maska)
	 const float inflation_impact = 1.5f;
	 const float curvature_impact = 0.2f;
	 const float radius = 7.0f;

	 speedImage.CopyMetaData(cellMask);
	 i3d::Resolution res = speedImage.GetResolution();
	 
	 // check each pixel - if it's between centrosomes. If so,
	 // set the values to "-" otherwise to "+". The value are computed
	 // via dot product, i.e. the domain si smooth and hence suitable
	 // to be a speed function
	 i3d::Vector3d<float> off = cellMask.GetOffset();

	 for (size_t i=0; i<speedImage.GetImageSize(); i++)
	 {
		  i3d::Vector3d<size_t> i_pos = speedImage.GetPos(i);
		  i3d::Vector3d<float> f_pos = i3d::PixelsToMicrons(i_pos, res) + off;

		  float val = 
					 i3d::Dot(f_pos - cA, cB - cA) * 
					 i3d::Dot(f_pos - cB, cA - cB);

		  speedImage.SetVoxel(i, -val);
	 }
	 
	 // normalize the speed image
	 float max_value = speedImage.GetVoxelData().max();
	 float min_value = speedImage.GetVoxelData().min();
	 float abs_limit = std::max(abs(max_value), abs(min_value));

	 for (size_t i=0; i<speedImage.GetImageSize(); i++)
	 {
		  float value = speedImage.GetVoxel(i);
		  float modified = (value - min_value)/abs_limit;
		  speedImage.SetVoxel(i, modified);
	 }

	 // extend the speed image to be suitable input for
	 // fast level sets
	 i3d::Image3d<float> speedImageExt;
    i3d::Mirror(speedImage, speedImageExt);

	 // upper limit of iterations of deformation
	 // (empirically measured value)
	 const size_t maxDuration = 15;

	 i3d::fls::Routine<int> *stretcher, *smoother; 

	 size_t counter = 0;
	
	 do 
	 {
		  // deformation according to the speed image
	 	  stretcher = i3d::fls::DefRoutine(speedImageExt, 
													  mask, 
													  domain, 
													  radius, 
													  inflation_impact, 
													  curvature_impact); 
		  stretcher->Initialize();
		  stretcher->Iterate(stretcher->InterfaceSize()); 
		  stretcher->Mask(mask.begin(), 
								i3d::fls::ResultCreator::InteriorAndInterface);
		  delete stretcher;

		  // deformation according to the curvature
		  smoother = i3d::fls::MCMRoutine(mask, 
													 domain, 
													 radius);
		  smoother->Initialize();
	 	  smoother->Iterate(smoother->InterfaceSize()/3);
	 	  smoother->Mask(mask.begin(), 
							  i3d::fls::ResultCreator::InteriorAndInterface);
		  delete smoother;

		  masks.push_back(mask);

		  counter++;

		  i3d::Vector3d<float> cA_new, cB_new;
		  GenerateCentrosomes(mask, cA_new, cB_new);

		  // centrosomes flipped :-/
		  if (i3d::Norm2(cA_new - cA) > i3d::Norm2(cA_new - cB))
		  {
				centrosomes.push_back(std::make_pair(cB_new, cA_new));
		  }
		  else
		  {
				centrosomes.push_back(std::make_pair(cA_new, cB_new));
		  }

		  shape.SetImage(&mask);
		  shape.SetLabel(true);
		  newVolume = shape.Volume();

		  DEBUG_REPORT("old: " << cA << cB << " -- new: " << cA_new << cB_new);
		  DEBUG_REPORT("volume: " << newVolume);

	 } while ((newVolume < 1.2*originalVolume) && (counter < maxDuration));

	 DEBUG_REPORT("finished with counter=" << counter);
}

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

template void ResampleArrayNN< i3d::Image3d<bool> /**/ >
	(std::vector< i3d::Image3d<bool> /**/ > &array, size_t samples);

template void ResampleArrayNN< i3d::Vector3d<float> /**/ >
	(std::vector< i3d::Vector3d<float> /**/ > &array, size_t samples);

template void ResampleArrayNN< size_t >
	(std::vector< size_t > &array, size_t samples);
