#include "universalbmp.h"

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

/**
 *	@def DetectFromLog
 *	@brief if defined, the images are assumed to be in log-space
 */
//#define DetectFromLog

/**
 *	@brief helper tables
 */
class CInFocusTables {
protected:
	uint8_t FalseColours[256 * 3];
	uint8_t DeGamma[256];

public:
	CInFocusTables()
	{
		for(int i = 0; i<256; i++)
			DeGamma[i] = uint8_t(255 * pow(float(i) / 255.0, 1.0 / 2.2)); // gamma 2.2

		for(int i = 0; i<256; i++) {
			int Indx = i*3;
			if(i < 128) {            // Blue to Green
				int Val = i*2;
				FalseColours[Indx+0] = DeGamma[(255 - Val)/2]; // b
				FalseColours[Indx+1] = DeGamma[Val / 4  ];     // g
				FalseColours[Indx+2] = 0;                      // r
			} else {
				int Val = (i-128)*2;
				FalseColours[Indx+0] = 0;                      // b
				FalseColours[Indx+1] = DeGamma[(255-Val) / 4]; // g
				FalseColours[Indx+2] = DeGamma[Val          ]; // r
			}
		}
	}

	const uint8_t *p_FalseColours() const
	{
		return FalseColours;
	}

	const uint8_t *p_DeGamma() const
	{
		return DeGamma;
	}
};

static const CInFocusTables infocus_tables; // staci na zacatku programu

/**
 *	@brief evaluates in-focus-ness of an image
 *
 *	@param[out] DestImg is 8-bit destination image
 *	@param[in] SourceImg is 8-bit original image
 *
 *	@return Returns the measure of in-focus-ness.
 */
float ComputeSharpness(TUniversalBmpByte &DestImg, const TUniversalBmpByte &SourceImg)
{
	const uint8_t *FalseColours = infocus_tables.p_FalseColours();
	const uint8_t *DeGamma = infocus_tables.p_DeGamma();
	// tabulky

	// NASTAVENI

	const float Gamma = 2.2f; // lukas - konverze z double na float!
	const float DynamicRange = 100;
	const float MinVyska = 0.08f;            // Minimalni vyska rozdilu b okoli aby melo smysl hodnotit ostrost // lukas - konverze na float
	                                  // Zatim je relativni k celemu rozsahu, pozdej bude pevne dane jako pomeru jasu
	
	const bool  ClusterComputing = 1;       // Pocitani shluku
	const bool  ClusterVisual = 1;          // Vizualizace se shluky
	const int   ClusterRadius = 10;         // Polovina hrany ctverce ve kterem se pocitaji shluky
	const int   ClusterCountThreshold = 30; // Priblizne odpovida poct platnych detekci s max odezvou
                                    // ve stejnem smeru nutnych k tomu aby cely shluk mel max odezvu
	const float RespThresh = 0.75f;          // Nutna odezva aby se zapocitavala do shluku // lukas - konverze na float

	// promenne

	int BorderNepocitatX = 40;
	int BorderNepocitatY = 40;
	const int BorderKreslitSumuX = 20;
	const int BorderKreslitSumuY = 20; // promenne?

	int X1;
	int Y1;
	int X2;
	int Y2;

	float InvRGBMaxDiff;
	float InvMaxLogValue;

	TUniversalBmpFloat ComputeImg;
	TUniversalBmpFloat DetectionImg;
	TUniversalBmpInt   DetectedCountImg;

	TUniversalBmpFloat Result;

	// NACIST do 
	
	//SourceImg.New(640,480,3);  //LoadJpgToUnivBmp(Edit1->Text , SourceImg);

	ComputeImg.New(SourceImg.MaxX,SourceImg.MaxY,1);
	DetectionImg.New(SourceImg.MaxX,SourceImg.MaxY,3);
	DestImg.New(SourceImg.MaxX,SourceImg.MaxY,3);
	DetectedCountImg.New(SourceImg.MaxX,SourceImg.MaxY,5);

	BorderNepocitatX = SourceImg.MaxX / 5;
	BorderNepocitatY = SourceImg.MaxY / 5;

	///////////

	float MaxLogValue = log
		(DynamicRange + 1.0f); // lukas - konverze na float
	InvMaxLogValue = 1.0f / MaxLogValue; // lukas - konverze na float
	//printf("still ... (%d x %d x %d)\n", SourceImg.MaxX, SourceImg.MaxY, SourceImg  .ValuesPerPixel); // l_ukas - tady to jeste bezi
	float         *Comp = ComputeImg.Pixel(0,0);
	for (int y = 0; y < SourceImg.MaxY; y++)
	{
		const unsigned char *Src  = SourceImg     .Pixel(0,y);
		float         *Det  = DetectionImg  .Pixel(0,y);
		for (int x = 0; x < SourceImg.MaxX; x++,Src +=SourceImg  .ValuesPerPixel,
			Comp+=ComputeImg .ValuesPerPixel,
			Det +=DetectionImg.ValuesPerPixel)//.Result     .ValuesPerPixel) // lukas - Result jeste nefrci! Det ukazuje do DetectionImg
		{
			float Grey = (Src[0] + Src[1] + Src[2]) / 3.0f; // tady jde o detekci, ne o vjem. Lepsi je pak delat per kanal // lukas - konverze na float
			Grey /= 255.0;
			Grey = pow(Grey,Gamma);
			Grey *= DynamicRange;
			Grey += 1.0;
			Grey = log(Grey);

			Comp[0] = Grey;

			Det[0] = 0.0;
			Det[1] = 0.0;
			Det[2] = 0.0;
		}
	}

	// compute
	///////////////////////////////////////////////////////////////
	//printf("not anymore ....\n"); // l_ukas - tady uz se to nedostane

	X1 = BorderNepocitatX;
	Y1 = BorderNepocitatY;
	X2 = SourceImg.MaxX - BorderNepocitatX;
	Y2 = SourceImg.MaxY - BorderNepocitatY;

	//const int SearchOkoli = 6;
	//const int SearchOkoliByte = SearchOkoli * 3;

	int RGBMin = 255;
	int RGBMax = 0;

	for (int y = 0; y < SourceImg.MaxY; y++)
	{
		const unsigned char *Src = SourceImg.Pixel(0,y);
		for (int x = 0; x < SourceImg.MaxX; x++,Src+=3)
		{
			int Grey = (Src[0] + Src[1] + Src[2]) / 3;
			//Src[0] = Grey;
			//Src[1] = Grey; // Greyscale
			//Src[2] = Grey;

			if (Grey < RGBMin) RGBMin = Grey;
			if (Grey > RGBMax) RGBMax = Grey;
		}
	}

	long Sum = 0; // lukas - chyba, neinicializovano
	//long Pixels = (Y2-Y1)*(X2-X1);
	for (int y = Y1; y < Y2; y++)
	{
		const unsigned char *Src = SourceImg.Pixel(X1,y);
		for (int x = X1; x < X2; x++,Src+=3)
		{
			Sum += long(Src[0]);
		}
	}
	//double Mean = float(Sum) / float(Pixels);
	int RGBMaxDiff = RGBMax - RGBMin + 1;
	InvRGBMaxDiff = 1.0f / float(RGBMaxDiff); // lukas - konverze na float

	//ButtonVisualizeClick(Sender); // no, you don't!

	float MaxResponse = 0;

#ifdef DetectFromLog

	const int CmpX = ComputeImg.ValuesPerPixel;
	const int CmpY = CmpX*ComputeImg.MaxX;
#define DetX           CmpX
#define DetY           CmpY
#define DetI           Cmp
#define InvDetectRange InvMaxLogValue

#else // DetectFromLog

	const int SrcX = SourceImg.ValuesPerPixel;
	const int SrcY = SrcX*SourceImg.MaxX;
#define DetX           SrcX
#define DetY           SrcY
#define DetI           Src
#define InvDetectRange InvRGBMaxDiff

#endif // DetectFromLog

	float         *Det = DetectionImg.Pixel(0,0);
	unsigned char *Dst = DestImg.Pixel(0,0);
	const unsigned char *Src = SourceImg.Pixel(0,0);
	int           *Cnt = DetectedCountImg.Pixel(0,0);


	for (int y = 0; y < SourceImg.MaxY; y++)
	{
		for (int x = 0; x < SourceImg.MaxX; x++, Det += DetectionImg.ValuesPerPixel,
			Dst += DestImg.ValuesPerPixel,
			Src += SourceImg.ValuesPerPixel,
			Cnt += DetectedCountImg.ValuesPerPixel)
		{
			Dst[0] = Src[0];
			Dst[1] = Src[1];
			Dst[2] = Src[2];

			Det[0] = 0;
			Det[1] = 0;
			Det[2] = 0;

			Cnt[0] = 0;
			Cnt[1] = 0;
			Cnt[2] = 0;
			Cnt[3] = 0;
			Cnt[4] = 0;
		}
	}


	for (int y = Y1; y < Y2; y++)
	{
		const unsigned char *Src = SourceImg.Pixel(X1,y);
		unsigned char *Dst = DestImg.Pixel(X1,y);
		float         *Det = DetectionImg.Pixel(X1,y);
		float         *Cmp = ComputeImg.Pixel(X1,y);

		for (int x = X1; x < X2; x++,Src += SourceImg.ValuesPerPixel,
			Dst += DestImg.ValuesPerPixel,
			Det += DetectionImg.ValuesPerPixel,
			Cmp += ComputeImg.ValuesPerPixel)
		{
			////////////////////////////////////////////////////////////////////////////////


			float Dif1;
			float Dif10;
			float DifTmp;
			float Resp;
			float AbsRespX = 0;
			float AbsRespY = 0;

			////////////////////////////////////////////////////////////////////////////////   Vodorovne

			Dif1 = float(DetI[1*DetX]-DetI[0]);   // Rozdil mezi +1 pixel // lukas - conversion to float
			Dif10 = 0;                    // Maximalni rozdil na vetsi vzdalenost (hledame zrychlene)
			DifTmp = fabs(float(DetI[ 10*DetX]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[ 15*DetX]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[-10*DetX]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[-15*DetX]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[-5*DetX]-DetI[5*DetX])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			if (fabs(Dif1) > Dif10) Dif10 = fabs(Dif1);
			if (Dif10*InvDetectRange > MinVyska)
			{
				Resp = Dif1 / Dif10;
				//Resp = 0;
				//Resp = Resp*Resp*Resp;
				AbsRespX = fabs(Resp);
				if (AbsRespX > MaxResponse) MaxResponse = AbsRespX;
				Det[0] = Resp;
			}
			else
				Det[0] = 0;

			////////////////////////////////////////////////////////////////////////////////   Svisle

			Dif1 = float(DetI[1*DetY]-DetI[0]);   // Rozdil mezi +1 pixel // lukas - conversion to float
			Dif10 = 0;                    // Maximalni rozdil na vetsi vzdalenost (hledame zrychlene)
			DifTmp = fabs(float(DetI[ 10*DetY]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[ 15*DetY]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[-10*DetY]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[-15*DetY]-DetI[0])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			DifTmp = fabs(float(DetI[-5*DetY]-DetI[5*DetY])); if (DifTmp > Dif10) Dif10 = DifTmp; // lukas - ambiguous fabs
			if (fabs(Dif1) > Dif10) Dif10 = fabs(Dif1);
			if (Dif10*InvDetectRange > MinVyska)
			{
				Resp = Dif1 / Dif10;
				//Resp = 0.99;
				//Resp = Resp*Resp*Resp;
				AbsRespY = fabs(Resp);
				if (AbsRespY > MaxResponse) MaxResponse = AbsRespY;
				Det[1] = Resp;
			}
			else
				Det[1] = 0;

		}
	}


	//////////////////////////  tady zacinala vizualizace
	/////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////

	if (ClusterComputing)
	{
		const int ClusterDiameter = 2*ClusterRadius;

		int x,y;

		// Nejdriv vodorovne

		for (y = 0; y < SourceImg.MaxY; y++)
		{
			int Count0 = 0;
			int Count1 = 0;
			int Count2 = 0;
			int Count3 = 0;

			float *DtA = DetectionImg.Pixel(0,y);
			for (x = 0; x < ClusterDiameter; x++,DtA+=DetectionImg.ValuesPerPixel)    // prvni cast, na vzdalenost prumeru, jen pripocitavame
			{
				if (DtA[0] >=  RespThresh) Count0 += int(256.0 * DtA[0]);
				if (DtA[1] >=  RespThresh) Count1 += int(256.0 * DtA[1]);
				if (DtA[0] <= -RespThresh) Count2 -= int(256.0 * DtA[0]);
				if (DtA[1] <= -RespThresh) Count3 -= int(256.0 * DtA[1]);
				// lukas - this makes a lot of warnings! Count0-3 is int, should be ?float?, the result of the above is double! the same repeats twice below ...
			}

			int   *Det  = DetectedCountImg.Pixel(ClusterRadius,y); // B je uprostred okna
			float *DtC  = DetectionImg.Pixel(0,y);

			for (; x < SourceImg.MaxX; x++,DtA+=DetectionImg.ValuesPerPixel,      // pak i odpocitavame od zadni casti okna
				DtC+=DetectionImg.ValuesPerPixel,
				Det+=DetectedCountImg.ValuesPerPixel)
			{
				if (DtA[0] >=  RespThresh) Count0 += int(256.0 * DtA[0]);
				if (DtA[1] >=  RespThresh) Count1 += int(256.0 * DtA[1]);
				if (DtA[0] <= -RespThresh) Count2 -= int(256.0 * DtA[0]);
				if (DtA[1] <= -RespThresh) Count3 -= int(256.0 * DtA[1]);

				Det[0] = Count0;
				Det[1] = Count1;
				Det[2] = Count2;                       // konvoluce s hranatym oknem
				Det[3] = Count3;

				if (DtC[0] >=  RespThresh) Count0 -= int(256.0 * DtC[0]);
				if (DtC[1] >=  RespThresh) Count1 -= int(256.0 * DtC[1]);
				if (DtC[0] <= -RespThresh) Count2 += int(256.0 * DtC[0]);
				if (DtC[1] <= -RespThresh) Count3 += int(256.0 * DtC[1]);
			}
		}

		// a ted svisle. Zkonvolvujem pocty a nastavime flag podle nich
		////////////////////////////////////////////////////////////////////////////

		const int DtY = DetectionImg.MaxX * DetectionImg.ValuesPerPixel;
		const int CntY = DetectedCountImg.MaxX * DetectedCountImg.ValuesPerPixel;


		for (x=0; x < SourceImg.MaxX; x++)
		{
			int Count0 = 0;
			int Count1 = 0;
			int Count2 = 0;
			int Count3 = 0;

			int *DtA = DetectedCountImg.Pixel(x,0);
			for (y = 0; y < ClusterDiameter; y++,DtA+=CntY)    // prvni cast, na vzdalenost prumeru, jen pripocitavame
			{
				Count0 += DtA[0];
				Count1 += DtA[1];
				Count2 += DtA[2];
				Count3 += DtA[3];
			}

			int   *Det     = DetectedCountImg.Pixel(x,ClusterRadius); // B je uprostred okna
			int   *DtC     = DetectedCountImg.Pixel(x,0);
			float *DetOut  = DetectionImg.Pixel(x,ClusterRadius);

			for (; y < SourceImg.MaxY; y++,DtA+=CntY,      // pak i odpocitavame od zadni casti okna
				DtC+=CntY,
				Det+=CntY,
				DetOut+=DtY)
			{
				Count0 += DtA[0];
				Count1 += DtA[1];
				Count2 += DtA[2];
				Count3 += DtA[3];

				int MaxC = Count0;
				if (Count1 > MaxC) MaxC = Count1;
				if (Count2 > MaxC) MaxC = Count2;
				if (Count3 > MaxC) MaxC = Count3;
				// konvoluce s hranatym oknem
				Det[4] = MaxC;

				//  vizualizace zatim do stejneho vystupu
				//

				if (ClusterVisual)
				{
					DetOut[0] = float(MaxC) / float(256 * ClusterCountThreshold);
					//DetOut[0] = DetOut[0] * DetOut[0];
					DetOut[1] = 0;
				}

				Count0 -= DtC[0];
				Count1 -= DtC[1];
				Count2 -= DtC[2];
				Count3 -= DtC[3];
			}
		}
	} // pocitani shluku

	/// Vykresleni barvicek
	//////////////////////////////////////

	float MaxPixelMaxResponse = 0.0;
	
	for (int y = Y1; y < Y2; y++)
	{
		unsigned char *Dst = DestImg.Pixel(X1,y);
		float         *Det = DetectionImg.Pixel(X1,y);




		for (int x = X1; x < X2; x++,//Src += SourceImg.ValuesPerPixel,
			Dst += DestImg.ValuesPerPixel,
			Det += DetectionImg.ValuesPerPixel
			//Cmp += ComputeImg.ValuesPerPixel
			)
		{
			float AbsRespX = fabs(Det[0]);
			float AbsRespY = fabs(Det[1]);
			float PixelMaxResp = (AbsRespX > AbsRespY) ? AbsRespX : AbsRespY;

			//PixelMaxResp = float(x) / float(SourceImg.MaxX);
			
			if (PixelMaxResp > MaxPixelMaxResponse) MaxPixelMaxResponse = PixelMaxResp;

			int FalseColourIndex = int(255.0 * PixelMaxResp); // lukas - int, float, double?
			if (FalseColourIndex > 255)
			{
				FalseColourIndex = 255;
			}
			if (FalseColourIndex < 0)
			{
				FalseColourIndex = 0;
			}

			FalseColourIndex *= 3;

			int DivOrigColours = 9;
			int DivFalseColors = 12; // na lehke ztmaveni, muzeme doladit pozdeji podle potreby
			int Grey = (Dst[0] + Dst[1] + Dst[2]) / DivOrigColours; // ztmaveni

			Dst[0] = 10* FalseColours[FalseColourIndex + 0] / DivFalseColors;
			Dst[1] = 10* FalseColours[FalseColourIndex + 1] / DivFalseColors;
			Dst[2] = 10* FalseColours[FalseColourIndex + 2] / DivFalseColors; 


			//if (MaxFalseColours)  // Pricitani barev se libilo vic
			//{
			//  if (Grey > Dst[0]) Dst[0] = Grey;
			//  if (Grey > Dst[1]) Dst[1] = Grey;
			//  if (Grey > Dst[2]) Dst[2] = Grey;
			//}
			//else
			{
				if (Grey + int(Dst[0])<256) Dst[0] += Grey; else Dst[2] = 255;
				if (Grey + int(Dst[1])<256) Dst[1] += Grey; else Dst[2] = 255;
				if (Grey + int(Dst[2])<256) Dst[2] += Grey; else Dst[2] = 255;
			}
		}
	}

	// Kresleni okraju

	const int KrX1 = BorderKreslitSumuX;
	const int KrY1 = BorderKreslitSumuY;
	const int KrX2 = DestImg.MaxX - BorderKreslitSumuX;
	const int KrY2 = DestImg.MaxY - BorderKreslitSumuY;

	for (int y = Y1; y < Y2; y++)
	{
		unsigned char *Dst;
		float DetMax = 0;

		float         *Det = DetectionImg.Pixel(X1,y);
		for (int x = X1; x < X2; x++,Det += DetectionImg.ValuesPerPixel)
		{
			if (fabs(Det[0]) > DetMax) DetMax = fabs(Det[0]);
			if (fabs(Det[1]) > DetMax) DetMax = fabs(Det[1]);
		}

		int FalseColourIndex = int(255.0 * DetMax); // lukas - int, float, double?
		if (FalseColourIndex > 255) FalseColourIndex = 255;
		if (FalseColourIndex < 0)   FalseColourIndex = 0;
		FalseColourIndex *= 3;
		unsigned char FC0 = FalseColours[FalseColourIndex + 0];
		unsigned char FC1 = FalseColours[FalseColourIndex + 1];
		unsigned char FC2 = FalseColours[FalseColourIndex + 2];

		Dst = DestImg.Pixel(0,y);
		for (int x = 0; x < KrX1; x++, Dst += DestImg.ValuesPerPixel)
		{
			Dst[0] = FC0;
			Dst[1] = FC1;
			Dst[2] = FC2;
		}
		Dst = DestImg.Pixel(KrX2,y);
		for (int x = KrX2; x < DestImg.MaxX; x++, Dst += DestImg.ValuesPerPixel)
		{
			Dst[0] = FC0;
			Dst[1] = FC1;
			Dst[2] = FC2;
		}
	}

	for (int x = X1; x < X2; x++)
	{
		unsigned char *Dst;
		float DetMax = 0;

		float      *Det = DetectionImg.Pixel(x,Y1);
		int         DetY = DetectionImg.ValuesPerPixel * DetectionImg.MaxX;
		for (int y = Y1; y < Y2; y++,Det += DetY)
		{
			if (fabs(Det[0]) > DetMax) DetMax = fabs(Det[0]);
			if (fabs(Det[1]) > DetMax) DetMax = fabs(Det[1]);
		}

		int FalseColourIndex = int(255.0 * DetMax); // lukas - int, float, double?
		if (FalseColourIndex > 255) FalseColourIndex = 255;
		if (FalseColourIndex < 0)   FalseColourIndex = 0;
		FalseColourIndex *= 3;
		unsigned char FC0 = FalseColours[FalseColourIndex + 0];
		unsigned char FC1 = FalseColours[FalseColourIndex + 1];
		unsigned char FC2 = FalseColours[FalseColourIndex + 2];

		int   DstY = DestImg.ValuesPerPixel * DestImg.MaxX;
		Dst = DestImg.Pixel(x,0);
		for (int y = 0; y < KrY1; y++, Dst += DstY)
		{
			Dst[0] = FC0;
			Dst[1] = FC1;
			Dst[2] = FC2;
		}
		Dst = DestImg.Pixel(x,KrY2);
		for (int y = KrY2; y < DestImg.MaxY; y++, Dst += DstY)
		{
			Dst[0] = FC0;
			Dst[1] = FC1;
			Dst[2] = FC2;
		}
	}

	//StoreToWinBmp(DestImg, Image1->Picture->Bitmap);

	return MaxPixelMaxResponse;
}

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