/**********************************************************************
* 
* cellSOFA.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: 
* This file contains implementation of Cell::SOFA_* methods which handle
* the interconnection between the simulator and the SOFA framework.
* The framework is used to provide a nice non-rigid deformations.
*
***********************************************************************/

#ifdef GTGEN_DEBUG
  #include <sys/time.h>
#endif
#include <fstream>

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

template <class MV, class PV>
void Cell<MV, PV>::SOFA_InitialExport(void)
{
	DEBUG_REPORT("starting initial export for cell ID=" << this->ID);

	//for keeping file names
	char fn[100];

	//should we export the mask image, list of its voxels, or list of grid points?
	if (  (configIni["sofa"].present("export name nucleus mask img"))
		|| (configIni["sofa"].present("export name nucleus mask voxels"))
		|| (configIni["sofa"].present("export name nucleus mask grid")) )
	{
		//first, create the local small mask image
		i3d::Image3d<MV> maskNucleus;
		maskNucleus.SetResolution(scheduler.sceneRes);
		maskNucleus.SetOffset(scheduler.sceneMasks[timePoint]->GetOffset());
		AllocateAndZeroNewMask(maskNucleus, scmCellBPList, 2, cellLookDistance);

		//now render the cell into it
		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
#else
		throw ERROR_REPORT("There are no nucleoli in the nucleus.");
#endif

		//save/export the mask image with the 3 labels ?
		if (configIni["sofa"].present("export name nucleus mask img"))
		{
			sprintf(fn,
				(const char*)configIni["sofa"]["export name nucleus mask img"], this->ID);
			maskNucleus.SaveImage(fn);
		}

		//export the list of mask coordinates ?
		if (configIni["sofa"].present("export name nucleus mask voxels"))
		{
			sprintf(fn,
				(const char*)configIni["sofa"]["export name nucleus mask voxels"], this->ID);
			ExportAllMaskVoxelCoordinates(maskNucleus,(MV)0,fn);
		}

		//export the list of grid coordinates within the mask ?
		if (configIni["sofa"].present("export name nucleus mask grid"))
		{
			sprintf(fn,
				(const char*)configIni["sofa"]["export name nucleus mask grid"], this->ID);
			ExportGridMaskVoxelCoordinates(maskNucleus,(MV)1,
								(int)configIni["sofa"]["export mask grid stepping"],fn);
		}
	}

	//export the "outer" list of nucleus boundary/countour points ?
	if (configIni["sofa"].present("export name nucleus BP"))
	{
		sprintf(fn,
			(const char*)configIni["sofa"]["export name nucleus BP"], this->ID);
		ExportPointList(scmNucleusBPList,scmNucleusOuterBPNumber,fn);
	}

#ifdef ENABLE_NUCLEOLI
	//export the "outer" list of the first nucleoli boundary/countour points ?
	if (configIni["sofa"].present("export name nucleoli1 BP"))
	{
		sprintf(fn, 
			(const char*)configIni["sofa"]["export name nucleoli1 BP"], this->ID);
		ExportPointList(scmNucleoli1BPList,scmNucleoli1OuterBPNumber,fn);
	}

	//export the "outer" list of the first nucleoli boundary/countour points ?
	if (configIni["sofa"].present("export name nucleoli2 BP"))
	{
		sprintf(fn, 
			(const char*)configIni["sofa"]["export name nucleoli2 BP"], this->ID);
		ExportPointList(scmNucleoli2BPList,scmNucleoli2OuterBPNumber,fn);
	}
#endif

	//export the list chromatin points ?
	if (configIni["sofa"].present("export name chromatin molecules"))
	{
		sprintf(fn, 
			(const char*)configIni["sofa"]["export name chromatin molecules"], this->ID);
		ExportPointList(chrDotList,chrDotList.size(),fn);
	}


	//prepare non-rigid deformation plan for export
	//but only if really needed..
	if (  (configIni["sofa"].present("export name nucleus deform img"))
		|| (configIni["sofa"].present("export name nucleus deformation")) )
					scheduler.PnGetMeNonrigidHints(scmNucleusBPList,
											scmNucleusOuterBPNumber,scmPnHints);
	//note that the previous function makes sure:
	//		scmPnHints.GetSizeX() == scmNucleusOuterBPNumber

	// save the deformation hints image ?
	if (configIni["sofa"].present("export name nucleus deform img"))
	{
		sprintf(fn, 
			(const char*)configIni["sofa"]["export name nucleus deform img"], this->ID);
		scmPnHints.SaveImage(fn);
	}

	// save the sequence of deformation recipies ?
	if (configIni["sofa"].present("export name nucleus deformation"))
	{
		const float *myHintPtr=scmPnHints.GetFirstVoxelAddr();
		for (size_t curTime=0; curTime < scmPnHints.GetSizeY(); ++curTime)
		{
			sprintf(fn,
				(const char*)configIni["sofa"]["export name nucleus deformation"],
				this->ID,curTime);
			std::ofstream Plist(fn);

			if (!Plist.is_open()) {
				std::ostringstream err;
				err << "Can't open the output file " << fn << ".";
				throw ERROR_REPORT(err.str());
			}

			float mult=1.f;
			if (curTime == scmPnNextStrongUsage)
			{
				//the code is a copy from Cell::ScmSuggestNonrigidDeformation()
				float PnStrongInitDelay=(float)cellCycleLength/4.f;

				if (configIni["testing"].present("strong deformation delay"))
				{
					REPORT("TESTING MODE: enforcing average strong deformation delay to "
						<< (int)configIni["testing"]["strong deformation delay"] << " frames.");
					PnStrongInitDelay=(float)configIni["testing"]["strong deformation delay"];
				}

				scmPnNextStrongUsage+=(size_t)std::max(GetRandomGauss(PnStrongInitDelay,PnStrongInitDelay/4.f),1.f);

				DEBUG_REPORT("using stronger hints this time; next such event will be at " << scmPnNextStrongUsage);

				mult=cellNonRigidStrongDeformation/cellNonRigidDeformation;
			}

			for ( size_t BPindex=0;
					(BPindex < scmPnHints.GetSizeX()) && (Plist.good());
					++BPindex, ++myHintPtr )
				Plist << *myHintPtr *mult << "\n";

			if (!Plist.good())
			{
				Plist.close();
				std::ostringstream err;
				err << "Error while writing file " << fn << ".";
				throw ERROR_REPORT(err.str());
			}
			Plist.close();
		}
	}
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_NextImport(const size_t T)
{
	DEBUG_REPORT("starting next import for cell ID=" << this->ID
						<< " at time " << T);

	//for keeping file names
	char fn[100];

	//"cache" the skipStep
	const size_t skipStep = (int)configIni["sofa"]["frame skip step"];

	//nucleus import
	sprintf(fn,
			(const char*)configIni["sofa"]["import name nucleus BP"],
			this->ID,skipStep*T);
	ImportPointList(scmNucleusBPList,&scmNucleusBPCentre,scmNucleusOuterBPNumber,fn);

#ifdef ENABLE_NUCLEOLI
	//nucleoli1 import
	sprintf(fn,
			(const char*)configIni["sofa"]["import name nucleoli1 BP"],
			this->ID,skipStep*T);
	ImportPointList(scmNucleoli1BPList,&scmNucleoli1BPCentre,scmNucleoli1OuterBPNumber,fn);

	//nucleoli2 import
	sprintf(fn,
			(const char*)configIni["sofa"]["import name nucleoli2 BP"],
			this->ID,skipStep*T);
	ImportPointList(scmNucleoli2BPList,&scmNucleoli2BPCentre,scmNucleoli2OuterBPNumber,fn);
#endif

	//just movement of dots:
	//new positions are given by the SOFA, import the list
	sprintf(fn,
			(const char*)configIni["sofa"]["import name chromatin molecules"],
			this->ID,skipStep*T);
	ImportPointList(chrDotList,NULL,chrDotList.size(),fn);

	//now, move the points to the reference position
	SOFA_MoveImportedToReferencePosition();
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_TransformCellBPs(const i3d::Vector3d<float> preRotTrans,
										 const float rotAngle,
										 const i3d::Vector3d<float> postRotTrans)
{
	//cached results of "expensive" functions...
	const float ccooss=cos(rotAngle);
	const float ssiinn=sin(rotAngle);

	//iterators to sweep the lists...
	std::vector< i3d::Vector3d<float> >::iterator Fiter;
	
	//process cell BPs
	Fiter=scmCellBPList.begin();
	while (Fiter != scmCellBPList.end())
	{
		*Fiter -= preRotTrans;
		float nx=ccooss*Fiter->x - ssiinn*Fiter->y;
		float ny=ssiinn*Fiter->x + ccooss*Fiter->y;
		Fiter->x = nx + postRotTrans.x;
		Fiter->y = ny + postRotTrans.y;
		Fiter->z +=     postRotTrans.z;

		++Fiter;
	}
	scmCellBPCentre=postRotTrans;
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_TransformNuclStarBPs(const i3d::Vector3d<float> preRotTrans,
										 const float rotAngle,
										 const i3d::Vector3d<float> postRotTrans)
{
	//cached results of "expensive" functions...
	const float ccooss=cos(rotAngle);
	const float ssiinn=sin(rotAngle);

	//iterators to sweep the lists...
	std::vector< i3d::Vector3d<float> >::iterator Fiter;
	
	//process nucleus:
	Fiter=scmNucleusBPList.begin();
	while (Fiter != scmNucleusBPList.end())
	{
		*Fiter -= preRotTrans;
		float nx=ccooss*Fiter->x - ssiinn*Fiter->y;
		float ny=ssiinn*Fiter->x + ccooss*Fiter->y;
		Fiter->x = nx + postRotTrans.x;
		Fiter->y = ny + postRotTrans.y;
		Fiter->z +=     postRotTrans.z;

		++Fiter;
	}
	scmNucleusBPCentre=postRotTrans;

#ifdef ENABLE_NUCLEOLI
	//process nucleoli1:
	Fiter=scmNucleoli1BPList.begin();
	while (Fiter != scmNucleoli1BPList.end())
	{
		*Fiter -= preRotTrans;
		float nx=ccooss*Fiter->x - ssiinn*Fiter->y;
		float ny=ssiinn*Fiter->x + ccooss*Fiter->y;
		Fiter->x = nx + postRotTrans.x;
		Fiter->y = ny + postRotTrans.y;
		Fiter->z +=     postRotTrans.z;

		++Fiter;
	}
	scmNucleoli1BPCentre-=preRotTrans;
	float nx=ccooss*scmNucleoli1BPCentre.x - ssiinn*scmNucleoli1BPCentre.y;
	float ny=ssiinn*scmNucleoli1BPCentre.x + ccooss*scmNucleoli1BPCentre.y;
	scmNucleoli1BPCentre.x = nx + postRotTrans.x;
	scmNucleoli1BPCentre.y = ny + postRotTrans.y;
	scmNucleoli1BPCentre.z +=     postRotTrans.z;

	//process nucleoli2:
	Fiter=scmNucleoli2BPList.begin();
	while (Fiter != scmNucleoli2BPList.end())
	{
		*Fiter -= preRotTrans;
		float nx=ccooss*Fiter->x - ssiinn*Fiter->y;
		float ny=ssiinn*Fiter->x + ccooss*Fiter->y;
		Fiter->x = nx + postRotTrans.x;
		Fiter->y = ny + postRotTrans.y;
		Fiter->z +=     postRotTrans.z;

		++Fiter;
	}
	scmNucleoli2BPCentre-=preRotTrans;
	nx=ccooss*scmNucleoli2BPCentre.x - ssiinn*scmNucleoli2BPCentre.y;
	ny=ssiinn*scmNucleoli2BPCentre.x + ccooss*scmNucleoli2BPCentre.y;
	scmNucleoli2BPCentre.x = nx + postRotTrans.x;
	scmNucleoli2BPCentre.y = ny + postRotTrans.y;
	scmNucleoli2BPCentre.z +=     postRotTrans.z;
#endif
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_TransformChromatinPs(const i3d::Vector3d<float> preRotTrans,
										 const float rotAngle,
										 const i3d::Vector3d<float> postRotTrans)
{
	//cached results of "expensive" functions...
	const float ccooss=cos(rotAngle);
	const float ssiinn=sin(rotAngle);

	//iterator...
	std::vector< Molecule >::iterator Miter;

	//process chromatin:
	Miter=chrDotList.begin();
	while (Miter != chrDotList.end())
	{
		Miter->pos -= preRotTrans;
		float nx=ccooss*Miter->pos.x - ssiinn*Miter->pos.y;
		float ny=ssiinn*Miter->pos.x + ccooss*Miter->pos.y;
		Miter->pos.x = nx + postRotTrans.x;
		Miter->pos.y = ny + postRotTrans.y;
		Miter->pos.z +=     postRotTrans.z;

		++Miter;
	}
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_MoveImportedToReferencePosition(void)
{
	SOFA_TransformChromatinPs(scmNucleusBPCentre,
				SOFA_InitialRotation,SOFA_InitialPosition);

	SOFA_TransformNuclStarBPs(scmNucleusBPCentre,
				SOFA_InitialRotation,SOFA_InitialPosition);
	//
	//note1: Chromatin must be transformed first as transforming Nucl* points also modifies
	//their centre scmNucleusBPCentre...
	//
	//note2: parameters are handed intentionally by value (copies are made)
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_MoveInitialToReferencePosition(void)
{
	SOFA_TransformCellBPs(scmCellBPCentre,
				SOFA_InitialRotation,SOFA_InitialPosition);
}

//-----------------------------------------------------------------------------
template <class MV, class PV>
void Cell<MV, PV>::SOFA_InitReferencePosition(void)
{
	//this is the default reference position
	SOFA_InitialPosition=scmCellBPCentre;
	SOFA_InitialRotation=0.f;

	//now construct the record to ask for in the configuration file
	char pos[25],rot[25];
	sprintf(pos,"ID%02u initial position",this->ID);
	sprintf(rot,"ID%02u initial rotation",this->ID);

	if (configIni["sofa"].present(pos))
	{
		float nx,ny,nz; //temp new positions

		if (sscanf((const char*)configIni["sofa"][pos],
				"%f %f %f", &nx,&ny,&nz) == 3)
		{
			SOFA_InitialPosition.x=nx;
			SOFA_InitialPosition.y=ny;
			SOFA_InitialPosition.z=nz;
		}
		else
			REPORT("Can't parse the config file for the record \""
						<< pos << "\", keeping default position.");
	}

	if (configIni["sofa"].present(rot))
	{
		SOFA_InitialRotation=(float)configIni["sofa"][rot];
		//convert to radians
		SOFA_InitialRotation*=3.14159/180.0;
	}
}



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