#include <i3d/image3d.h>
#include <i3d/filters.h>
#include <i3d/morphology.h>
#include <i3d/transform.h>

#include <ini/iniparser.h>
#include <cytopacq/optigen.h>
#include <cytopacq/acquigen.h>

#include "../settings.h"
#include "rnd_generators.h"
#include "finalpreview.h"

//-------------------------------------------------------------------
template <class MV, class PV>
void PrepareFinalImage(i3d::Image3d<PV> &phantom, 
							  i3d::Image3d<MV> &mask,
							  i3d::Image3d<PV> &final,
							  const IniHandler &configIni,
							  const float scale)
{
	if (phantom.GetSize() != mask.GetSize())
		throw ERROR_REPORT("Phantom and mask images do not match in size.");

	//make a copy of the phantom image into which we will prepare/draw the final image
	final=phantom;

	// If GTGEN_WITH_CHRCENTRESINPHANTHOMS is enabled,
	// remove chromocentres from the phantom
#ifdef GTGEN_WITH_CHRCENTRESINPHANTHOMS
	const PV centreColour=std::numeric_limits<PV>::max()/2;

	for (size_t i=0; i<final.GetImageSize(); i++)
	{
		 const PV value = final.GetVoxel(i);

		 if (value >= centreColour)
			  final.SetVoxel(i, value - centreColour);
	}
#endif

	// A hack to emphasize the cell shape: this introduces
	// some level of molecules to be present everywhere in the cell, the phantom
	// image may increase this number anywhere in the cell in addition
	PV *pV=final.GetFirstVoxelAddr();
	const MV *pM=mask.GetFirstVoxelAddr();

	// intensity of nonspecific staining of cytoplasm
	PV cytoplasm = (int) configIni["cell"]["intensity of nonspecific staining"]; 
	DEBUG_REPORT("nonspecific staining level: " << cytoplasm);

	for (size_t i=0; i<final.GetImageSize(); i++, pV++, pM++)
	{
		 if (*pM)
		 {
			  (*pV) += cytoplasm; // cytoplasm intensity
		 }
	}

	// stage II
	REPORT("scaling factor is " << scale);
	CallOptigen(configIni, final, scale);

	REPORT("maximum of blurred image: " << final.GetVoxelData().max());
	
	// stage III
	i3d::Image3d<PV> imgFinal;
	CallAcquigen<PV, size_t, PV> (configIni, final, imgFinal, i3d::NEAREST_NEIGHBOUR);

	final = imgFinal;
	REPORT("maximum of final image: " << final.GetVoxelData().max());

#ifdef GTGEN_WITH_ANISOTROPIC_GT
   // resample also the phantom and mask images to the desired anisotropic resolution
	REPORT("Resampling from resolution: " << mask.GetResolution().GetRes());
	REPORT("Resampling to resolution: " << final.GetResolution().GetRes());

	i3d::ResampleToDesiredResolution(phantom, final.GetResolution(), i3d::NEAREST_NEIGHBOUR);
	i3d::ResampleToDesiredResolution(mask,    final.GetResolution(), i3d::NEAREST_NEIGHBOUR);
#endif
}

//-------------------------------------------------------------------

template <class MV, class PV>
void PrepareFinalPreviewImage(i3d::Image3d<PV> &phantom,
										i3d::Image3d<MV> &mask,
										i3d::Image3d<PV> &final,
										const IniHandler &configIni)
{
	if (phantom.GetSize() != mask.GetSize())
		throw ERROR_REPORT("Phantom and mask images do not match in size.");

	//make the final image, into which we will prepare/draw, similar to the phantom image
	//note: also allocates memory
	final.CopyMetaData(phantom);

	//should somewhat enlarge histogram spread and shift it
#ifdef GTGEN_WITH_HIGHSNR
	#define SIGNAL_SHIFT	700.0f
	#define RON_VARIANCE	90.0f
	#define PSF_MULT	1.f
#else
	#define SIGNAL_SHIFT	80.f
	#define RON_VARIANCE	3.3f
	#define PSF_MULT	2.2f
#endif

	//local float copy of input image
	i3d::Image3d<float> tmp;
	tmp.CopyMetaData(phantom);

	//create it
	//
	//and hack to emphasize the cell shape: this introduces
	//at least 1 molecule to be present everywhere in the cell, the phantom
	//image may increase this number anywhere in the cell in addition
	//
	//and if GTGEN_WITH_CHRCENTRESINPHANTHOMS is enabled,
	//remove chromocentres from the phantom
#ifdef GTGEN_WITH_CHRCENTRESINPHANTHOMS
	const PV centreColour=std::numeric_limits<PV>::max()/2;
#endif
	float* pF=tmp.GetFirstVoxelAddr();
	PV* pV=phantom.GetFirstVoxelAddr();
	PV* const LastpV=phantom.GetFirstVoxelAddr() + phantom.GetImageSize();
	const MV* pM=mask.GetFirstVoxelAddr();

	for (; pV != LastpV; ++pF, ++pV, ++pM) {
#ifdef GTGEN_WITH_CHRCENTRESINPHANTHOMS
		if (*pV >= centreColour) *pV-=centreColour;
#endif
		*pF=(float)*pV; //the copy
		if (*pM) 
		{
		//	 *pF+=1.f; //the hack
			// *pF += 4.8f;//maxValue * 0.8f;
			*pF += 0.2f;//maxValue * 0.8f;
			*pF *= 600.0f; // TODO: scaling - ted je to hloupa magic konstanta
		}
	}

	//simulate PSF:
	//PSF from "psf/2012-10-24_0_0_10_1_0_1_0_4_0_0_0_0_4_7.ics" was
	//observed to have sigma 1.6px @ 65nm/px in x,y -> 0.104um
	//observed to have sigma 4.5px @ 100nm/px in z  -> 0.45um
	i3d::GaussIIR(tmp,
		PSF_MULT*0.13f*tmp.GetResolution().GetX(),
		PSF_MULT*0.13f*tmp.GetResolution().GetY(),
		//PSF_MULT*0.15f*tmp.GetResolution().GetZ());	//2D
		PSF_MULT*0.26f*tmp.GetResolution().GetZ());	//3D
	//note: params were adjusted a little bit to just look better :(
	//
	//this typically returns values in <0,1> interval,
	//which thus can't be directly converted into integers
	

	//running pointers
	pF=tmp.GetFirstVoxelAddr();
	pV=final.GetFirstVoxelAddr();
	PV* const LastpVf=final.GetFirstVoxelAddr() + final.GetImageSize();
	pM=mask.GetFirstVoxelAddr();

	for (; pV != LastpVf; ++pF, ++pV, ++pM) 
	{
		//uncertainty in the number of incoming photons
		float noiseMean = sqrt(*pF), // from statistics: shot noise = sqrt(signal) 
				noiseVar = noiseMean; // for Poisson distribution E(X) = D(X)

		*pV=static_cast<PV>(ceilf(*pF + GetRandomPoisson(noiseMean) - noiseVar));

		//constants are parameters of Andor iXon camera provided from vendor:
		//photon shot noise: dark current
		*pV+=static_cast<PV>( GetRandomPoisson(0.06f) );

		//read-out noise:
		//  variance up to 25.f (old camera on ILBIT)
		//  variance about 1.f (for the new camera on ILBIT)
		*pV+=static_cast<PV>( GetRandomGauss(SIGNAL_SHIFT,RON_VARIANCE) );
		//note: this is the dominating noise, this one will be calculated with
	}

#ifdef GTGEN_WITH_ANISOTROPIC_GT
	// resample all images to the final desired anisotropic resolution
	i3d::Resolution finalRes;
	GetFinalResolution(configIni, finalRes);

	REPORT("Resampling from resolution: " << mask.GetResolution().GetRes());
	REPORT("Resampling to resolution: " << finalRes.GetRes());

	i3d::ResampleToDesiredResolution(phantom, finalRes, i3d::NEAREST_NEIGHBOUR);
	i3d::ResampleToDesiredResolution(mask,    finalRes, i3d::NEAREST_NEIGHBOUR);
	i3d::ResampleToDesiredResolution(final,   finalRes, i3d::NEAREST_NEIGHBOUR);
#endif
}

//-------------------------------------------------------------------

template <class MV, class PV>
float ComputeSNR(i3d::Image3d<PV> const &img,
					  i3d::Image3d<MV> const &mask)
{
	if (img.GetSize() != mask.GetSize())
		throw ERROR_REPORT("Phantom and mask images do not match in size.");

	//helper sums for the fg and bg regions
	float sumFg=0.f, compFg=0.f;
	float sumBg=0.f, compBg=0.f;

	//sizes
	unsigned long sizeFg=0;
	unsigned long sizeBg=0;

	//running pointers
	const PV* pI=img.GetFirstVoxelAddr();
	const MV* pM=mask.GetFirstVoxelAddr();
	PV const *LastpI=img.GetFirstVoxelAddr() + img.GetImageSize();

	//the first pass, computation of the means:
	//'sum' will accumulate for mean, 'comp' will do the compensation job

	for (; pI != LastpI; ++pI, ++pM) {
		//first, convert...
		const float tmp=static_cast<float>(*pI);

		if (*pM) {
			//foreground
			const float tmpC=tmp - compFg;
			const float tmpS=sumFg + tmpC;
			compFg=(tmpS-sumFg) - tmpC;
			sumFg=tmpS;

			++sizeFg;
		} else {
			//background
			const float tmpC=tmp - compBg;
			const float tmpS=sumBg + tmpC;
			compBg=(tmpS-sumBg) - tmpC;
			sumBg=tmpS;

			++sizeBg;
		}
	}

	const float meanFg=sumFg / (float)sizeFg;
	const float meanBg=sumBg / (float)sizeBg;

	//the second pass, computation of the variances:
	//reset vars for first-order accumulations
	sumFg=0.f; compFg=0.f;
	sumBg=0.f; compBg=0.f;

	//prepare vars for squared (second-order) accumulations
	float sum2Fg=0.f, comp2Fg=0.f;
	float sum2Bg=0.f, comp2Bg=0.f;

	//reset the running pointers
	pI=img.GetFirstVoxelAddr();
	pM=mask.GetFirstVoxelAddr();

	for (; pI != LastpI; ++pI, ++pM) {
		//first, convert...
		const float tmp=static_cast<float>(*pI);

		if (*pM) {
			//foreground
			/* naive version, btw: shouldn't we do Kahan here as well?
			sumFg+=(tmp-meanFg);
			sum2Fg+=(tmp-meanFg) * (tmp-meanFg);
			*/
			float tmpC=(tmp-meanFg) - compFg;
			float tmpS=sumFg + tmpC;
			compFg=(tmpS-sumFg) - tmpC;
			sumFg=tmpS;

			tmpC=(tmp-meanFg)*(tmp-meanFg) - comp2Fg;
			tmpS=sum2Fg + tmpC;
			comp2Fg=(tmpS-sum2Fg) - tmpC;
			sum2Fg=tmpS;
		} else {
			//background
			/* naive version, btw: shouldn't we do Kahan here as well?
			sumBg+=(tmp-meanBg);
			sum2Bg+=(tmp-meanBg) * (tmp-meanBg);
			*/
			float tmpC=(tmp-meanBg) - compBg;
			float tmpS=sumBg + tmpC;
			compBg=(tmpS-sumBg) - tmpC;
			sumBg=tmpS;

			tmpC=(tmp-meanBg)*(tmp-meanBg) - comp2Bg;
			tmpS=sum2Bg + tmpC;
			comp2Bg=(tmpS-sum2Bg) - tmpC;
			sum2Bg=tmpS;
		}
	}

	const float devFg=sqrtf( (sum2Fg - sumFg*sumFg/(float)sizeFg)
								/ (float)sizeFg );
	const float devBg=sqrtf( (sum2Bg - sumBg*sumBg/(float)sizeBg)
								/ (float)sizeBg );

	const float SNR=(meanFg - meanBg) / devFg;

	DEBUG_REPORT("fg / bg sizes ratio: " << (float)sizeFg / (float)sizeBg
			<< ", fg constitues " << (float)sizeFg/(float)mask.GetImageSize()*100.f
			<< "% of the image volume");
	DEBUG_REPORT("fg signal is " << meanFg << " +- " << devFg
			<< ", bg signal is " << meanBg << " +- " << devBg);
	DEBUG_REPORT("SNR=" << SNR);

	//tagged and formated for machine processing of the SNR related data...
	REPORT("SNR " << SNR << " " << meanFg << " "
		<< meanBg << " " << devFg << " " << devBg);

	return (SNR);
}

//-------------------------------------------------------------------

template <class MV, class PV>
float ComputeSNR_Naive(i3d::Image3d<PV> const &img,
							  i3d::Image3d<MV> const &mask)
{
	if (img.GetSize() != mask.GetSize())
		throw ERROR_REPORT("Phantom and mask images do not match in size.");

	//sums and squared sums for the fg and bg regions
	float sumFg=0.f, sum2Fg=0.f;
	float sumBg=0.f, sum2Bg=0.f;

	//sizes
	unsigned long sizeFg=0;
	unsigned long sizeBg=0;

	//running pointers
	const PV* pI=img.GetFirstVoxelAddr();
	const MV* pM=mask.GetFirstVoxelAddr();
	PV const *LastpI=img.GetFirstVoxelAddr() + img.GetImageSize();

	for (; pI != LastpI; ++pI, ++pM) {
		//first convert to prevent from overflows...
		const float tmp=static_cast<float>(*pI);

		if (*pM) {
			//foreground
			sumFg+=tmp;
			sum2Fg+=tmp*tmp;
			++sizeFg;
		} else {
			//background
			sumBg+=tmp;
			sum2Bg+=tmp*tmp;
			++sizeBg;
		}
	}

	//compute the stats:
	const float meanFg=sumFg / (float)sizeFg;
	const float devFg=sqrtf( (sum2Fg - (sumFg*sumFg/(float)sizeFg))
								/ (float)sizeFg );

	const float meanBg=sumBg / (float)sizeBg;
	const float devBg=sqrtf( (sum2Bg - (sumBg*sumBg/(float)sizeBg))
								/ (float)sizeBg );

	const float SNR=(meanFg - meanBg) / devFg;

	DEBUG_REPORT("fg / bg sizes ratio: " << (float)sizeFg / (float)sizeBg
			<< ", fg constitues " << (float)sizeFg/(float)mask.GetImageSize()*100.f
			<< "% of the image volume");
	DEBUG_REPORT("fg signal is " << meanFg << " +- " << devFg
			<< ", bg signal is " << meanBg << " +- " << devBg);
	DEBUG_REPORT("(naive) SNR=" << SNR);

	//tagged and formated for machine processing of the SNR related data...
	REPORT("SNR " << SNR << " " << meanFg << " "
		<< meanBg << " " << devFg << " " << devBg);

	return (SNR);
}


template <class MV, class PV>
float ComputeSNR_Naive_NoStepZone(i3d::Image3d<PV> const &img,
											 i3d::Image3d<MV> const &mask,
											 const int NoStepWidth)
{
	if (img.GetSize() != mask.GetSize())
		throw ERROR_REPORT("Phantom and mask images do not match in size.");

	//prepare here the special masks for the fg and bg
	const int A=NoStepWidth / 2;
	i3d::Image3d<MV> maskFg,maskBg;
	i3d::ErosionO(mask,maskFg,A);
	i3d::DilationO(mask,maskBg,NoStepWidth-A);
	DEBUG_REPORT("Width of the no-step zone is " << NoStepWidth);

	//sums and squared sums for the fg and bg regions
	float sumFg=0.f, sum2Fg=0.f;
	float sumBg=0.f, sum2Bg=0.f;

	//sizes
	unsigned long sizeFg=0;
	unsigned long sizeBg=0;

	//running pointers
	const PV* pI=img.GetFirstVoxelAddr();
	const MV* pMfg=maskFg.GetFirstVoxelAddr();
	const MV* pMbg=maskBg.GetFirstVoxelAddr();
	PV const *LastpI=img.GetFirstVoxelAddr() + img.GetImageSize();

	for (; pI != LastpI; ++pI, ++pMfg, ++pMbg) {
		//first convert to prevent from overflows...
		const float tmp=static_cast<float>(*pI);

		if (*pMfg) {
			//foreground
			sumFg+=tmp;
			sum2Fg+=tmp*tmp;
			++sizeFg;
		} else if (! *pMbg) {
			//background
			sumBg+=tmp;
			sum2Bg+=tmp*tmp;
			++sizeBg;
		}
	}

	//compute the stats:
	const float meanFg=sumFg / (float)sizeFg;
	const float devFg=sqrtf( (sum2Fg - (sumFg*sumFg/(float)sizeFg))
								/ (float)sizeFg );

	const float meanBg=sumBg / (float)sizeBg;
	const float devBg=sqrtf( (sum2Bg - (sumBg*sumBg/(float)sizeBg))
								/ (float)sizeBg );

	const float SNR=(meanFg - meanBg) / devFg;

	DEBUG_REPORT("fg / bg sizes ratio: " << (float)sizeFg / (float)sizeBg
			<< ", fg constitues " << (float)sizeFg/(float)mask.GetImageSize()*100.f
			<< "% of the image volume");
	DEBUG_REPORT("fg signal is " << meanFg << " +- " << devFg
			<< ", bg signal is " << meanBg << " +- " << devBg);
	DEBUG_REPORT("(naive and NoStepZone) SNR=" << SNR);

	//tagged and formated for machine processing of the SNR related data...
	REPORT("SNR " << SNR << " " << meanFg << " "
		<< meanBg << " " << devFg << " " << devBg);

	return (SNR);
}



//
// explicit instantiations
//
template void PrepareFinalPreviewImage(i3d::Image3d<i3d::GRAY8>  &phantom, i3d::Image3d<bool> &mask,
													i3d::Image3d<i3d::GRAY8>  &final,   const IniHandler &);
template void PrepareFinalPreviewImage(i3d::Image3d<i3d::GRAY16> &phantom, i3d::Image3d<bool> &mask,
													i3d::Image3d<i3d::GRAY16> &final,   const IniHandler &);
template void PrepareFinalPreviewImage(i3d::Image3d<i3d::GRAY8>  &phantom, i3d::Image3d<i3d::GRAY8> &mask,
													i3d::Image3d<i3d::GRAY8>  &final,   const IniHandler &);
template void PrepareFinalPreviewImage(i3d::Image3d<i3d::GRAY16> &phantom, i3d::Image3d<i3d::GRAY8> &mask,
													i3d::Image3d<i3d::GRAY16> &final,   const IniHandler &);
template void PrepareFinalPreviewImage(i3d::Image3d<i3d::GRAY8>  &phantom, i3d::Image3d<i3d::GRAY16> &mask,
													i3d::Image3d<i3d::GRAY8>  &final,   const IniHandler &);
template void PrepareFinalPreviewImage(i3d::Image3d<i3d::GRAY16> &phantom, i3d::Image3d<i3d::GRAY16> &mask,
													i3d::Image3d<i3d::GRAY16> &final,   const IniHandler &);

template void PrepareFinalImage(i3d::Image3d<i3d::GRAY8>  &phantom, i3d::Image3d<bool> &mask,
										  i3d::Image3d<i3d::GRAY8>  &final,   const IniHandler &, const float);
template void PrepareFinalImage(i3d::Image3d<i3d::GRAY16> &phantom, i3d::Image3d<bool> &mask,
										  i3d::Image3d<i3d::GRAY16> &final,   const IniHandler &, const float);
template void PrepareFinalImage(i3d::Image3d<i3d::GRAY8>  &phantom, i3d::Image3d<i3d::GRAY8> &mask,
										  i3d::Image3d<i3d::GRAY8>  &final,   const IniHandler &, const float);
template void PrepareFinalImage(i3d::Image3d<i3d::GRAY16> &phantom, i3d::Image3d<i3d::GRAY8> &mask,
										  i3d::Image3d<i3d::GRAY16> &final,   const IniHandler &, const float);
template void PrepareFinalImage(i3d::Image3d<i3d::GRAY8>  &phantom, i3d::Image3d<i3d::GRAY16> &mask,
										  i3d::Image3d<i3d::GRAY8>  &final,   const IniHandler &, const float);
template void PrepareFinalImage(i3d::Image3d<i3d::GRAY16> &phantom, i3d::Image3d<i3d::GRAY16> &mask,
										  i3d::Image3d<i3d::GRAY16> &final,   const IniHandler &, const float);

template float ComputeSNR_Naive(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<bool> const &mask);
template float ComputeSNR_Naive(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<bool> const &mask);
template float ComputeSNR_Naive(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<i3d::GRAY8> const &mask);
template float ComputeSNR_Naive(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<i3d::GRAY8> const &mask);
template float ComputeSNR_Naive(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<i3d::GRAY16> const &mask);
template float ComputeSNR_Naive(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<i3d::GRAY16> const &mask);

template float ComputeSNR(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<bool> const &mask);
template float ComputeSNR(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<bool> const &mask);
template float ComputeSNR(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<i3d::GRAY8> const &mask);
template float ComputeSNR(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<i3d::GRAY8> const &mask);
template float ComputeSNR(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<i3d::GRAY16> const &mask);
template float ComputeSNR(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<i3d::GRAY16> const &mask);

template float ComputeSNR_Naive_NoStepZone(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<bool> const &mask, const int NoStepWidth);
template float ComputeSNR_Naive_NoStepZone(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<bool> const &mask, const int NoStepWidth);
template float ComputeSNR_Naive_NoStepZone(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<i3d::GRAY8> const &mask, const int NoStepWidth);
template float ComputeSNR_Naive_NoStepZone(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<i3d::GRAY8> const &mask, const int NoStepWidth);
template float ComputeSNR_Naive_NoStepZone(i3d::Image3d<i3d::GRAY8>  const &img, i3d::Image3d<i3d::GRAY16> const &mask, const int NoStepWidth);
template float ComputeSNR_Naive_NoStepZone(i3d::Image3d<i3d::GRAY16> const &img, i3d::Image3d<i3d::GRAY16> const &mask, const int NoStepWidth);

