#ifndef __PREPROCFUNCS__
#define __PREPROCFUNCS__

#include <math.h>
#include <vector>
#include "IplImage.h"
#include <algorithm>
using namespace std;

#include "ChipMonkTypes.h"

extern BOOL g_bCancelProcessing;


#define RINT(x) (((x) > 0) ? int((x) + 0.5) : int ((x) - 0.5))

void BlurImage(unsigned char *pImg, int nWidth, int nHeight, long nLength, UCHAR *pMemBuff = NULL);

char *AdaptiveThreshold(const unsigned char *pImg, int nWidth, int nHeight, 
						int nWindow, int nThreshold/* threshold * 256*/,
						UCHAR *pMemBuff0 = NULL, UCHAR *pMemBuff1 = NULL);

char *AdaptiveThreshold_pow2Xpow2(const unsigned char *pImg, int nWidth, int nHeight, 
								  int pow2, int nThreshold/* threshold * 256*/,
								  UCHAR *pMemBuff0 = NULL, UCHAR *pMemBuff1 = NULL, char *pDest = NULL);

void FlipVerticaly(char *pBuf, int nWidth, int nHeight);

int EstimateFocus(const unsigned char *pImg, int nWidth, int nHeight, 
				  int nWindow,
				  UCHAR *pMemBuff0 = NULL, UCHAR *pMemBuff1 = NULL);
int EstimateColorAlliasing(const unsigned char *pImg, int nWidth, int nHeight, 
						   int nWindow,
						   UCHAR *pMemBuff0 = NULL, UCHAR *pMemBuff1 = NULL);
int EstimateColorAlliasing(const UCHAR *pImg, int nWidth, int nHeight);

UCHAR *CreateBWBuffer(const UCHAR* pBGR, int nWidth, int nHeight, UCHAR *pDest = NULL);
UCHAR *GetGreenChannel(const UCHAR* pBGR, int nWidth, int nHeight, UCHAR *pDest = NULL);

void Erode1BitImage(UCHAR *pImg, int nKernelWidth, int nKernelHeight,
					int nWidth, int nHeight);
void Dilate1BitImage(UCHAR *pImg, int nKernelWidth, int nKernelHeight,
					 int nWidth, int nHeight);

void Open1BitImage(UCHAR *pImg, int nKernelWidth, int nKernelHeight,
				   int nWidth, int nHeight);
void Close1BitImage(UCHAR *pImg, int nKernelWidth, int nKernelHeight,
					int nWidth, int nHeight);

int FindFirstAutoCorrelationPeak(const int *pData, int nLength);

inline BOOL IsBitUp(const UCHAR *pImg, int nWidth, int x, int y){
	return pImg[(nWidth * y + x) >> 3] & (0x80 >> (x & 7));
}

inline void SetBitUp(UCHAR *pImg, int nWidth, int x, int y){
	pImg[(nWidth * y + x) >> 3] |= (0x80 >> (x & 7));
}

inline void SetBitDown(UCHAR *pImg, int nWidth, int x, int y){
	pImg[(nWidth * y + x) >> 3] &= ~(0x80 >> (x & 7));
}

template <class T>
void BlurRowHorizontaly(const T *pImg, T *pRes, int nWidth, long nLength){
	const T *pI0, *pI1, *pILineEnd;
	long nHalfLength = nLength / 2;
	long nSum = 0;
	pILineEnd = pImg + nWidth;
	int j;
	for(j = 0, pI1 = pImg; j < nHalfLength; ++j, ++pI1) {
		nSum += *pI1;
	}
	for(; j < nLength; ++j, ++pI1) {
		nSum += *pI1;
		*pRes++ = T(nSum / nLength);
	}
	for(pI0 = pImg;pI1 < pILineEnd;++pI0, ++pI1){
		nSum += *pI1;
		nSum -= *pI0;
		*pRes++ = T(nSum / nLength);
	}
	for(j = 0; j < nHalfLength; ++j, ++pI0) {
		nSum -= *pI0;
		*pRes++ = T(nSum / nLength);
	}
}

template <class TSRC, class TDEST>
static void SumRowHorizontaly(const TSRC *pRow, TDEST *pRes, int nWidth, int nLength){
	int i;
	const TSRC *pI0, *pI1, *pILineEnd;
	int nHalfLength = nLength >> 1;
	int nSum;
	pILineEnd = pRow + nWidth;
	nSum = 0;   
	for(i = 0, pI1 = pRow; i < nHalfLength; ++i, ++pI1) {
		nSum += *pI1;
	}
	for(; i < nLength; ++i, ++pI1) {
		nSum += *pI1;
		*pRes++ = nSum;
	}
	for(pI0 = pRow;pI1 < pILineEnd;++pI0, ++pI1){
		nSum += (TDEST(*pI1) - TDEST(*pI0));
		*pRes++ = nSum;
	}
	for(i = 0; i < nHalfLength; ++i, ++pI0) {
		nSum -= *pI0;
		*pRes++ = nSum;
	}
}

template <class TSRC, class TDEST>
static void SumRowsVerticaly(const TSRC *pCBRows, int nCBLines, TDEST *pSum, int nCurrCBIndx, int nWidth){
	TSRC pCurrCBRow = pCBRows[nCurrCBIndx];
	TSRC pLastCBRow = pCBRows[(nCurrCBIndx + 1) % nCBLines];
	for(int *pSumEnd = pSum + nWidth; pSum < pSumEnd; ++pSum, ++pCurrCBRow, ++pLastCBRow){
		*pSum += (TDEST(*pCurrCBRow) - TDEST(*pLastCBRow));
	}
}

template <class T>
CPoint FindMaximum(const T *pImg, int nWidth, int nHeight){
	const T *pBeg = pImg, *pEnd = pImg + nWidth * nHeight, *pMax = pImg;
	for(; pBeg < pEnd; ++pBeg){
		if(*pBeg > *pMax){
			pMax = pBeg;
		}
	}
	UINT nIndx = (UINT)(((UINT)(pMax) - (UINT)(pImg)) / sizeof(T));
	return CPoint(nIndx % nWidth, nIndx / nWidth);
}

int FindSum(const int *pData, int nLength);

int CountNewLines(const char *str);
int CountNewLines(const WCHAR* str);


UCHAR *AdaptiveThreshold_pow2Xpow2CB(const UCHAR *pImg, int nWidth, int nHeight, 
									 int pow2, int nThreshold/* threshold * 256*/);
UCHAR *AdaptiveThresholdCB(const UCHAR *pImg, int nWidth, int nHeight, 
						   int pow2, int nThreshold/* threshold * 256*/, int nMinBrightness = 0, UCHAR *pDest = NULL);

USHORT *Sum1BppImage(BYTE *pImg, int nWidth, int nHeight, int nKernelLength);

int SobelVertical(const UCHAR *pSrc, int nWidth, int nHeight, UCHAR *pDest);
int SobelHorizontal(const UCHAR *pSrc, int nWidth, int nHeight, UCHAR *pDest);

template <class T>
void FindLocalExtrmsRow(const T *pData, int nLength, UCHAR *pDest, int nThreshold){
	int nBeg = -1, nPeak;
	for(int i=1; i<nLength - 1; ++i){
		int nVal = pData[i];
		if(nVal >= nThreshold && nVal > pData[i-1]) nBeg = i;
		if(nBeg >=0 && nVal > pData[i + 1]){
			nPeak = (i + nBeg)/2;
			nBeg = -1;
			pDest[nPeak >> 3] |= (0x80 >> (nPeak & 0x7));
		}
	}
}

template <class T>
void FindLocalExtrmsVertical(const T *pData, int nWidth, int nHeight, UCHAR *pDest, int nThreshold){
	int nDestWidthStep = (nWidth >> 3);
	if(nWidth & 7)++nDestWidthStep;
	memset(pDest, 0, nDestWidthStep * nHeight);
	for(const T *pDataEnd = pData + nWidth * nHeight; pData<pDataEnd; pData+=nWidth, pDest+=nDestWidthStep){
		FindLocalExtrmsRow(pData, nWidth, pDest, nThreshold);
	}
}

template <class T>
void FindLocalExtrmsColumn(const T *pData, int nWidth, int nHeight, UCHAR *pDest, UCHAR mask, int nThreshold){
	int nBeg = -1, nPeak;
	int nDestWidthStep = nWidth >> 3;
	pData += nWidth;
	const T *pDataEnd = pData + nWidth * (nHeight - 1);
	for(int i = 1; pData < pDataEnd; pData += nWidth, ++i){
		int nVal = *pData;
		if(nVal >= nThreshold && nVal > pData[-nWidth]) nBeg = i;
		if(nBeg >=0 && nVal > pData[nWidth]){
			nPeak = (i + nBeg)/2;
			nBeg = -1;
			pDest[nDestWidthStep * nPeak] |= mask;
		}
	}
}

template <class T>
void FindLocalExtrmsHorizontal(const T *pData, int nWidth, int nHeight, UCHAR *pDest, int nThreshold){
	int nDestWidthStep = (nWidth >> 3);
	if(nWidth & 7)++nDestWidthStep;
	memset(pDest, 0, nDestWidthStep * nHeight);
	int nCol = 0;
	for(const T *pDataEnd = pData + nWidth; pData < pDataEnd; ++pData, ++nCol){
		FindLocalExtrmsColumn(pData, nWidth, nHeight, pDest + (nCol >> 3), (0x80 >> (nCol & 0x7)), nThreshold);
	}
}
inline BOOL IsBitUp(const UCHAR *pImg, int nWidth, int x, int y);
inline void SetBitUp(UCHAR *pImg, int nWidth, int x, int y);
int MarkCC(int x, int y, const UCHAR *pSrc, UCHAR *pDest, int nWidth, int nHeight, vector<CPoint>* pCcPoints = NULL, 
		   int nPoints = 0, int nMaxPoints = -1);

int MarkCC_NoRecursion(int x, int y, const UCHAR *pSrc, UCHAR *pDest, int nWidth, int nHeight, vector<CPoint> * pCcPoints, int nMaxPoints);

float FindLine(const vector<CPoint> & ccPoints, float & a, float & b, BOOL bTranspose = FALSE); // y = a + bx;

BOOL FindEdges(const UCHAR *pData, int nWidth, int nHeight, const UCHAR *pE, UCHAR *pDest, int nThreshold, int nMinSize,
			   vector<float> & vEdges, BOOL bHorizontal = TRUE);

template <class T>
void ApplyHisteresys(const T *pData, int nWidth, int nHeight, const UCHAR *pE, UCHAR *pDest, int nThreshold, int nMinSize = 0){
	memset(pDest, 0, nWidth * nHeight / 8);
	vector<CPoint> vSeeds;
	int nMaxPoints =  200;//max(nWidth, nHeight) * 2;
	int i;
	for(i = 0; i < nHeight; ++i){
		for(int j = 0; j < nWidth; ++j, ++pData){
			if(*pData >= nThreshold){
				//if(MarkCC(j, i, pE, pDest, nWidth, nHeight, 0, NULL, nMaxPoints) >= nMinSize){
				if(MarkCC_NoRecursion(j, i, pE, pDest, nWidth, nHeight, NULL, nMaxPoints) >= nMinSize){
					vSeeds.push_back(CPoint(j, i));
				}
			}
		}
	}
	memset(pDest, 0, nWidth * nHeight / 8);
	for(i=0; i<(int)vSeeds.size(); ++i){
		//MarkCC(vSeeds[i].x, vSeeds[i].y , pE, pDest, nWidth, nHeight, 0, NULL, nMaxPoints);
		MarkCC_NoRecursion(vSeeds[i].x, vSeeds[i].y , pE, pDest, nWidth, nHeight, NULL, nMaxPoints);
	}
}

void DilateGrayImage(UCHAR *pImg, int nWidth, int nHeight);
void CloseGrayImage(UCHAR *pImg, int nWidth, int nHeight);

template <class T> 
void SetRegionToZero(T *pData, int nWidth, int nHeight, CPoint center, int nRadius){
	int sx = (center.x > nRadius) ? center.x - nRadius : 0;
	int sy = (center.y > nRadius) ? center.y - nRadius : 0;
	int ex = (center.x < nWidth - nRadius) ? center.x + nRadius : nWidth - 1;
	int ey = (center.y < nHeight - nRadius) ? center.y + nRadius : nHeight - 1;
	T *pDR = pData + sy * nWidth + sx;
	T *pDRe = pDR + (ey - sy) * nWidth;
	int nW = (ex - sx);
	for(; pDR < pDRe; pDR += nWidth){
		for(T* pDC = pDR, *pDCe = pDR + nW; pDC < pDCe; ++pDC){
			*pDC = 0;
		}
	}
}

template <class T> 
T GetLocalMaximum(T *pData, int nWidth, int nHeight, CPoint start, CPoint & localMax, T & globalMax,  UCHAR *pDone = NULL){
	BOOL bAllocated = FALSE;
	if(!pDone){
		pDone = new UCHAR[nWidth*nHeight];
		bAllocated = TRUE;
		memset(pDone, 0, nWidth * nHeight);
	}
	T nMax = 0;
	if(pDone[start.y * nWidth + start.x] > 0){
		if(bAllocated) delete [] pDone;
		return -1;
	}
	pDone[start.y * nWidth + start.x] = 1;
	int sx = (start.x > 0) ? start.x - 1 : 0;
	int sy = (start.y > 0) ? start.y -1 : 0;
	int ex = (start.x < nWidth - 1) ? start.x + 1 : nWidth - 1;
	int ey = (start.y < nHeight - 1) ? start.y + 1 : nHeight - 1;
	int nW = (ex - sx);
	T *pDR = pData + sy * nWidth + sx;
	T *pDRe = pDR + (ey - sy) * nWidth;
	for(; pDR <= pDRe; pDR += nWidth){
		for(T* pDC = pDR, *pDCe = pDR + nW; pDC <= pDCe; ++pDC){
			if(*pDC > nMax){
				nMax = *pDC;
				if(nMax > globalMax) globalMax = nMax;
			}
		}
	}
	localMax = CPoint(-1, -1);
	if(nMax > 0){
		T nM;
		CPoint mp;
		for(int i=sy; i <= ey; ++i){ 
			for(int j=sx; j <= ex; ++j){
				if(pData[i * nWidth + j] == nMax && (i!=start.y || j!=start.x)){
					if((nM = GetLocalMaximum(pData, nWidth, nHeight, CPoint(j, i), mp, globalMax, pDone)) >= nMax){
						nMax = nM;
						if(nMax > globalMax) globalMax = nMax;
						localMax = mp;
					}
					else if(nM < 0 && start.x >= 0 && pData[start.y * nWidth + start.x] < globalMax){
						start = CPoint(-1, -1);
					}
				}
			}
		}
		if(localMax.x < 0){
			localMax = start;
		}
	}
	if(bAllocated) delete [] pDone;
	return (localMax.x >= 0) ? nMax : -1;
}

void MarkWhiteColumns(const UCHAR *pImg, // 1 bit image
					  int nWidth, int nHeight, UCHAR *pDest, int nMaxBlack, int nMinWhite, vector<pair<int, int> > *pWhiteColumns = NULL);

void MarkWhiteRows(const UCHAR *pImg, // 1 bit image
				   int nWidth, int nHeight, UCHAR *pDest, int nMaxBlack, int nMinWhite);

void Rotate1bppImgUpsideDown(UCHAR *pBuf, int nWidth, int nHeight);
void FlippBuffer(UCHAR *pBuf, int nWidth, int nHeight);

template <class T>
void DumpBuffer(const char *fileName, const T *pBuff, int nSize){
	FILE *fp = fopen(fileName, "wb");
	fwrite(pBuff, sizeof(T), nSize, fp);
	fclose(fp);
}

bool gaussj(float **a, int n, float **b, int m);

void bandec(float **a, unsigned long n, int m1, int m2, float **al, unsigned long indx[], float *d);

void banbks(float **a, unsigned long n, int m1, int m2, float **al, unsigned long indx[], float b[]);

float **fmatrix(int n, int m);

void free_fmatrix(float **ppM, int n);

float **transpose(float **ppSrc, int n, int m);
float **mat_mat_mult(float **ppA, int n, int m, float **ppB, int mb);
float IntPower(float x, int n);
void GetXYPowers(float x, float y, int nOrderX, int nOrderY, vector<float> & vPowers);

typedef struct{
	int nWidth, nHeight, nDestWidth, nWindowSize, nCBLines, nCBSize, nSrcBLines, nSrcBSize, nHalfLength, nCurrCBIndx, nCurrSrcBIndx, nNorm;
	int *pSum;
	short *pCBuff;
	PSHORT *pCBRows;
	UCHAR *pDest, *pSrcBuff;
	PUCHAR *pSRCRows;
	int nLineCount;
	int nThreshold, nMinBrightness;
}AdaptiveThresholdParams;

AdaptiveThresholdParams *CreateAdaptiveThresholdParams(int nWidth, int nHeight, int nWindowSize, int nThreshold, int nMinBrightness, UCHAR *pDest);
void AdaptiveThresholdNextRow(AdaptiveThresholdParams *pParams);
UCHAR *AdaptiveThresholdGetCurrSrcRow(AdaptiveThresholdParams *pParams);
void AdaptiveThresholdSummarize(AdaptiveThresholdParams **ppParams);

struct CLinePointCoefs{
	int S, Sx, Sy, Sxx, Sxy, Syy;
	CRect bb;
	void Add(int x, int y);
	CLinePointCoefs();
	float FindLine(BOOL bVertical, float & a, float & b)const;
};

inline void UpdateBB(CRect & bb, const CPoint & p) {
	if(bb.IsRectNull()){
		bb.left = p.x;
		bb.right = p.x + 1;
		bb.top = p.y;
		bb.bottom = p.y + 1;
	}
	if(p.x < bb.left) bb.left = p.x; else if(p.x >= bb.right) bb.right = p.x + 1;
	if(p.y < bb.top) bb.top = p.y; else if(p.y >= bb.bottom) bb.bottom = p.y + 1;
}

void FindCurves(UCHAR *p1bppImg, int nWidth, int nHeight, int nMinSize, vector<vector<CPoint> > & vCCs);

UCHAR *Rotate8BitBuffrer180Degrees(const UCHAR *pSrc, int nWidth, int nHeight, UCHAR *pDest = NULL);
UCHAR *Rotate8BitBuffrer90Degrees(const UCHAR *pSrc, int nWidth, int nHeight, UCHAR *pDest = NULL);
IplImage *Rotate8BitBuffrer90Degrees(const IplImage *pSrc,  IplImage *pDest = NULL);
UCHAR *Rotate8BitBuffrer270Degrees(const UCHAR *pSrc, int nWidth, int nHeight, UCHAR *pDest = NULL);
IplImage *Rotate8BitBuffrer270Degrees(const IplImage *pSrc,  IplImage *pDest = NULL);
IplImage *Rotate8BitBuffrer180Degrees(const IplImage *pSrc,  IplImage *pDest = NULL);
IplImage * CreateHalfImage(const IplImage *pImg, IplImage *pDest = NULL);
IplImage * CreateHalfImage(const IplImage *pImg, char *pPreallocatedBuffer, char **ppBuffEnd);
IplImage *Create8bppPreAllocatedImage(int nWidth, int nHeight, char *pBuff, char **ppBuffEnd);

int DotProd(CPoint v1, CPoint v2);
int CrossProd(CPoint v1, CPoint v2);
int CrossProdSign(CPoint v1, CPoint v2);
bool VectorsIntersect(CPoint v1a, CPoint v1b, CPoint v2a, CPoint v2b);
CPoint operator *(CPoint p, int n);
CPoint operator /(CPoint p, int n);
bool VectorsIntersect(CPoint v1a, CPoint v1b, CPoint v2a, CPoint v2b, CPoint &pIntr);
void AdaptiveThresholdCB(const IplImage *pImg, 
						 int nLength, int nMinDiff,
						 UCHAR *pBlackOnWhite, UCHAR *pWhiteOnBlack, int & nBoW, int & nWoB);
int BuildImageHistogram(const IplImage *pImg, int nStep, int *pHist);

void ReplaceZerosByOnes(IplImage *pImg);

void ReplaceZerosBy255(IplImage *pImg);
float GetAverageBrightness(UCHAR *pImg, int nWidth, int nHeight, int nLowThreshold);

void ConvertToNegative(IplImage *pImg);
void ConvertToGreenOnBlack(IplImage *pImg);
void ConvertToYellowOnBlack(IplImage *pImg);
void ConvertToYellowOnBlue(IplImage *pImg);
void ConvertToWhiteOnBlue(IplImage *pImg);
void Conrast(IplImage *pImg);
void Sharpen(IplImage *pImg,  float fSharpCoef = 1.0f, int nWindow = 21);

void ContrastLookupTableInit();
void ContrastChangeLinear( int nZeroes, int nMax );
void ContrastblackAndWhite( IplImage *pImg);

UCHAR *Add4BytePaddingTo1bppBitmap(const UCHAR *pSrc, int nWidth, int nHeight, int & nWidthStep);

IplImage *Buf1bpp2IplImage(const UCHAR *pBuf, int nWidth, int nHeight);
IplImage *Buffer2IplImage(const char *pBuf, int nWidth, int nHeight, int nChannels, BOOL bFlip);

void Rotate8BitIplImage180DegreesInPlace(IplImage *pImg);

#endif
