#include "sompcompress.h"

#include "OBitStream.h"
#include "IBitStream.h"

CSOMPCompress::CSOMPCompress(void){
	image_bmp = 0;
	m_pPattern = 0;

	m_dDistPFactor = .8;
	m_dLearnRFactor = .99;
	m_fRetrainFactor = 1.5;			// when to retrain
	m_iClusterSize = 2;
	m_iCodeBookSize = 1024;

	m_iPatternQuality = 5;

	m_bLinear = true;

	m_dErrorAll = 0;
}

CSOMPCompress::~CSOMPCompress(void){
	if(image_bmp)
		free(image_bmp->data);
	delete m_pPattern;
}

int CSOMPCompress::loadBMP(const char *szName){
	FILE *pFile=NULL;									// File Handle

	strcpy(m_szBMPSource,szName);

	if (!szName)										// Make Sure A Filename Was Given
	{
		return NULL;									// If Not Return NULL
	}

	pFile=fopen(szName,"r");							// Check To See If The File Exists

	if (pFile)											// Does The File Exist?
	{
		fclose(pFile);									// Close The Handle
		if(image_bmp){
			free(image_bmp->data);
			delete image_bmp;
		}
		image_bmp = auxDIBImageLoad(szName);				// Load The Bitmap And Return A Pointer
	}
	return 1;
}

int CSOMPCompress::loadCPicture(const char *szName){
	FILE *fhd = fopen(szName,"rb");
	if(!fhd)
		return 0;

	long lx,ly,lSize;
	unsigned char *usData,*usTempData;

	fread(&lx,sizeof(long),1,fhd);
	fread(&ly,sizeof(long),1,fhd);
	fread(&m_iCodeBookSize,sizeof(int),1,fhd);
	fread(&m_iClusterSize,sizeof(int),1,fhd);

	lSize = m_iCodeBookSize*m_iClusterSize*m_iClusterSize*3;
	usData = new unsigned char[lSize];
	fread(usData,sizeof(unsigned char),lSize,fhd);
	/*for(long l=0; l < m_iCodeBookSize; l++){
		fread(&(usData[l * m_iClusterSize * m_iClusterSize * 3]),1,m_iClusterSize*m_iClusterSize*3,fhd);
	}*/

	if(image_bmp){
		free(image_bmp->data);
		delete image_bmp;
	}
	image_bmp = new AUX_RGBImageRec;
	image_bmp->data = (unsigned char *)malloc(sizeof(unsigned char)*lx*ly*3);
	memset(image_bmp->data,0,sizeof(unsigned char)*lx*ly*3);
	image_bmp->sizeX = lx;
	image_bmp->sizeY = ly;

	CIBitStream ibs;
	ibs.m_fhd = fhd;	// hackhack

	int x,y,xo,yo,i;
	int iBits = int(log(m_iCodeBookSize)/log(2));
	long lIndex;
	unsigned char *pTempData;

	for(x=0; x < lx+1-m_iClusterSize; x+=m_iClusterSize){
		for(y=0; y < ly+1-m_iClusterSize; y+=m_iClusterSize){
			lIndex = ibs.read(iBits);
			/*if(lIndex >= m_iCodeBookSize || lIndex < 0)
				continue;*/

			usTempData = &(usData[lIndex * m_iClusterSize * m_iClusterSize * 3]);
			pTempData = &(image_bmp->data[( x + y*lx )*3]);
			for(xo=0; xo < m_iClusterSize; xo ++){
				for(yo=0; yo < m_iClusterSize; yo ++){
					for(i=0; i<3; i++){
						pTempData[( xo + yo*lx )*3 + i] = usData[lIndex * m_iClusterSize * m_iClusterSize * 3 + (yo * m_iClusterSize + xo) * 3 + i];

						/*if(xo == 0||yo == 0 || x==y && i==0)
							pTempData[( xo+ yo*lx )*3 +i] = 255-pTempData[( xo + yo*lx )*3 +i];*/
					}
				}
			}
		}
	}

	delete []usData;

	// fclose is done by ~CIBitStream

	return 0;
}

int CSOMPCompress::saveCPicture(const char *szName){
	// save header
	FILE *fhd = fopen(szName,"wb");
	if(!fhd)
		return 0;
	long lx,ly,l,lSize;
	unsigned char *usData;
	double dError=0;

	lx = image_bmp->sizeX;
	ly = image_bmp->sizeY;

	fwrite(&lx,sizeof(long),1,fhd);
	fwrite(&ly,sizeof(long),1,fhd);
	fwrite(&m_iCodeBookSize,sizeof(int),1,fhd);
	fwrite(&m_iClusterSize,sizeof(int),1,fhd);

	// store codebook vectors as unsigned chars each ( 8 bits per component )
	lSize = m_iCodeBookSize*m_iClusterSize*m_iClusterSize*3;
	usData = new unsigned char[lSize];
	for(l=0; l < lSize; l++){
		usData[l] = (unsigned char)(dp3Weights[l] * 255.);
	}
	fwrite(usData,sizeof(unsigned char),lSize,fhd);

	fclose(fhd);
	delete []usData;

	// save indices
	COBitStream obs;
	obs.fopen(szName,"ab");

	int x,y,xo,yo,i;
	long lWx,lWy,lIndex;
	int iBits = int(log(getCodebookSize())/log(2));
	nVec_var *dpPattern = new nVec_var[m_iClusterSize * m_iClusterSize * 3];
	for(x=0; x < image_bmp->sizeX+1-m_iClusterSize; x+=m_iClusterSize){
		for(y=0; y < image_bmp->sizeY+1-m_iClusterSize; y+=m_iClusterSize){
			for(xo=0; xo < m_iClusterSize; xo ++){
				for(yo=0; yo < m_iClusterSize; yo ++){
					for(i=0; i<3; i++){
						dpPattern[i + (yo * m_iClusterSize + xo)*3] = ((nVec_var)(((unsigned char *)(image_bmp->data))[((x+xo) + (y+yo)*image_bmp->sizeX)*3 + i]))/256.;
					}
				}
			}

			SetInput(dpPattern);
			GetWinner(lWx,lWy);

			dError += pVDiff->SLength();

			lIndex = lWy *lXSize + lWx;
			obs.write(lIndex,iBits);
		}
	}
	m_dErrorAll = dError;

	delete[]dpPattern;
	return 1;
}

void CSOMPCompress::initSOM(void){
	if(m_bLinear)
		SetSize(m_iClusterSize * m_iClusterSize * 3,m_iCodeBookSize,1);
	else{
		SetSize(m_iClusterSize * m_iClusterSize * 3,sqrt(m_iCodeBookSize),sqrt(m_iCodeBookSize));
		// maybe the codebooksize gotta be adjusted

		m_iCodeBookSize = lXSize * lYSize;
	}

	SetDistP(lYSize>lXSize?lYSize:lXSize*2/3);
	SetLRate(.01);
	InitWeights(Init_Random);
	SetNKFunction(fNKcone);
}

void CSOMPCompress::createPattern(void){
	delete m_pPattern;
	m_pPattern = new SOMPattern(m_iClusterSize * m_iClusterSize * 3);

	m_pPattern->SetSOM(this);
	m_pPattern->SetMaxPatternNum(image_bmp->sizeX * image_bmp->sizeY / m_iClusterSize / m_iClusterSize + 1);

	nVec_var *dpPattern = new nVec_var[m_iClusterSize * m_iClusterSize * 3];
	int x,y,iStep = m_iClusterSize * m_iPatternQuality,
		xo,yo,i;
	for(x=0; x < image_bmp->sizeX + 1 - m_iClusterSize; x+=iStep){
		for(y=0; y < image_bmp->sizeY + 1 - m_iClusterSize; y+=iStep){
			for(xo=0; xo < m_iClusterSize; xo ++){
				for(yo=0; yo < m_iClusterSize; yo ++){
					for(i=0; i < 3; i++){
						dpPattern[i + (yo * m_iClusterSize + xo)*3] = ((nVec_var)(((unsigned char *)(image_bmp->data))[(x+xo)*3 + (y+yo)*3*image_bmp->sizeX + i]))/256.;
					}
				}
			}

			m_pPattern->AddPattern(dpPattern);
		}
	}
	delete [] dpPattern;
}

void CSOMPCompress::iterate(void){
	m_pPattern->TeachEpochFast(GetLRate(),GetDistP(),true,m_fRetrainFactor);

	// retrain pattern with big errors
	/*long l,lECount = 0;
	double fError = 0;
	double fThreshold;
	for(l=0; l < m_pPattern->lActPPos; l++){
		fError += (m_pPattern->pFPattern[l].dLastError);
	}
	fError /= double(m_pPattern->lActPPos);
	fThreshold = fError * m_fRetrainFactor;

	for(l=0; l < m_pPattern->lActPPos; l++){
		if(m_pPattern->pFPattern[l].dLastError > fThreshold){
			m_pPattern->pFPattern[l].dLastError = CategorizeFast(m_pPattern->pFPattern[l].ppdDim);
			lECount ++;
		}
	}
	cout << lECount <<"/"<<m_pPattern->lActPPos << " : " << fError << " - ";*/

	// decrease lernrate and stiffness param
	SetDistP(GetDistP() * m_dDistPFactor);
	SetLRate(GetLRate() * m_dLearnRFactor);
}

void CSOMPCompress::compressFull(float fMinDistP){
	while(GetDistP() > fMinDistP){
		cout << GetDistP() << " to " << fMinDistP << endl;
		iterate();
	}
}

int CSOMPCompress::saveBMP(const char *szName,unsigned char *ucpData,int ixSize,int iySize){
	if(!ucpData){
		ucpData = image_bmp->data;
		ixSize = image_bmp->sizeX;
		iySize = image_bmp->sizeY;
	}
	if(!ucpData)
		return 0;

	int bmp_width;
	int bmp_height,x;
	
	bmp_width = ixSize;
	bmp_height = iySize;

	GLubyte *bitmap_bits = new GLubyte[ ( bmp_width * bmp_height ) * 3 ];
	
	BITMAPFILEHEADER header;
	BITMAPINFOHEADER info;

	for(x=0; x < ixSize*iySize * 3; x+=3){
		// rgb to bgr
		bitmap_bits[x+0] = ucpData[x+2];
		bitmap_bits[x+1] = ucpData[x+1];
		bitmap_bits[x+2] = ucpData[x+0];
	}
	
	header.bfType = 'MB';
	header.bfOffBits = sizeof( header ) + sizeof( info );
//	cout << header.bfOffBits << endl;
	header.bfSize = sizeof( header ) + sizeof( info ) + ( bmp_width * bmp_height * 3 );
	header.bfReserved1 = 0;
	header.bfReserved2 = 0;
	
	info.biBitCount = 24;
	info.biClrImportant = 0;
	info.biClrUsed = 0;
	info.biCompression = 0;
	info.biHeight = bmp_height;
	info.biWidth = bmp_width;
	info.biPlanes = 1;
	info.biSize = sizeof( info );
	info.biSizeImage = bmp_width * bmp_height * 3;
	info.biXPelsPerMeter = 2952;
	info.biYPelsPerMeter = 2952;
	
	OFSTRUCT file_data;
	int file_handle;
	
	if( ( file_handle = OpenFile( szName, &file_data, OF_WRITE | OF_CREATE ) ) == -1 )
	{
		//Error( "ERROR: cannot create new screenshot file." );
		return -1;
	}
	
	// write data to bitmap
	_lwrite( file_handle, (char*)&header, sizeof( header ) );
	_lwrite( file_handle, (char*)&info, sizeof( info ) );
	_lwrite( file_handle, (char*)bitmap_bits, bmp_width * bmp_height * 3 );
	
	_lclose( file_handle );

	delete [] bitmap_bits;
	
	// now update current_screenie_path for the next time this function is called
	//screenie_number++;
	//sprintf( current_screenie_path, "screenshots/shot%d.bmp", screenie_number );
	//last_screenie_time = GetTickCount();
	//LogInit( "Screenshot taken.\n" );
	
	return 1;
}

void CSOMPCompress::calcStats(void){
	int x,y,i;
	unsigned short usTemp,usAv[3];

	// reset some variables
	m_dAv[0] = m_dAv[1] = m_dAv[2] = 0.;
	m_iCMax[0] = m_iCMax[1] = m_iCMax[2] = 0;
	m_iCMin[0] = m_iCMin[1] = m_iCMin[2] = 255;

	// search for minima and maxima and calc average values
	for(x=0; x < image_bmp->sizeX; x++){
		for(y=0; y < image_bmp->sizeY; y++){
			for(i=0; i < 3 ; i++){
				usTemp = image_bmp->data[x*3 + y*3*image_bmp->sizeX + i];
				m_dAv[i] += usTemp;
				if(m_iCMax[i] < usTemp){
					m_iCMax[i] = usTemp;
				}
				if(m_iCMin[i] > usTemp){
					m_iCMin[i] = usTemp;
				}
			}
		}
	}
	for(i=0; i < 3 ; i++){
		m_dAv[i] /= double(image_bmp->sizeX*image_bmp->sizeY);
		usAv[i] = (unsigned short)m_dAv[i];
	}
	// calc sigma^2
	m_dSigma = 0;
	for(x=0; x < image_bmp->sizeX; x++){
		for(y=0; y < image_bmp->sizeY; y++){
			for(i=0; i < 3 ; i++){
				m_dSigma += (image_bmp->data[x*3 + y*3*image_bmp->sizeX + i] - usAv[i])*(image_bmp->data[x*3 + y*3*image_bmp->sizeX + i] - usAv[i]);
			}
		}
	}
	m_dSigma /= double(image_bmp->sizeX*image_bmp->sizeY);
	m_dSigma = sqrt(m_dSigma);

	// calculate entropy
	int iCount,*ipColors;
	double dProp;
	ipColors = new int[256*256*256];
	memset(ipColors,0,sizeof(int)*256*256*256);

	for(x=0; x < image_bmp->sizeX; x++){
		for(y=0; y < image_bmp->sizeY; y++){
			ipColors[
			image_bmp->data[x*3 + y*3*image_bmp->sizeX + 0]*256*256
			+image_bmp->data[x*3 + y*3*image_bmp->sizeX + 1]*256
			+image_bmp->data[x*3 + y*3*image_bmp->sizeX + 2]]++;
		}
	}
	m_dEntropy = 0;
	for(x=0; x < 256; x++){
		for(y=0; y < 256; y++){
			for(i=0; i < 256; i++){
				iCount = ipColors[x*256*256+y*256+i];
				if(iCount){
					dProp = double(iCount) / (image_bmp->sizeX*image_bmp->sizeY);
					m_dEntropy += (dProp * log(dProp));
				}
			}
		}
	}
	m_dEntropy /= -log(2);
	delete [] ipColors;
}
