/*
 * GBuffer.cpp
 *
 *  Created on: Nov 7, 2008
 *      Author: alexander
 */

#include "GBuffer.h"
#include "plates.h"

int GBuffer::count = 0;

void GBuffer::init(int h, int w)
{
	this->h = h;
	this->w = w;
	this->data = (Telement *) calloc(h*w, sizeof(Telement));
	count++;
}

GBuffer::GBuffer(int h, int w)
{
	init(h,w);
}



GBuffer::GBuffer(AVPicture *frame, int h, int w, int deinterlace)
{
	init(h,w);
	Telement *dstOffset = this->data;

        if (deinterlace)
	{
		for (int i = 0; i < h; i++)
		{
			int even = i & 0xFFFE;
			memcpy( dstOffset + i * sizeof(Telement) * w, frame->data[0] + frame->linesize[0] * even, w * sizeof(uint8_t));
		}
	}
	else
	{
		for (int i = 0; i < h; i++)
		{
			memcpy( dstOffset + i * sizeof(Telement) * w, frame->data[0] + frame->linesize[0] * i, w * sizeof(uint8_t));
		}
	}
}

GBuffer::GBuffer(SDL_Surface *frame)
{
	init(frame->h, frame->w);
	for (int i = 0; i < h; i++)
	{
		uint32_t *start = (uint32_t *)((uint8_t *)frame->pixels + frame->pitch * i);
		Telement *dst = data;
		for (int j = 0; j < w; j++)
		{
			uint32_t c = *start;
			(*dst) = (uint8_t)(((c & 0xFF) + ((c >> 8) & 0xFF) + ((c >> 16) & 0xFF)) / 3);
			start++;
			dst++;
		}
	}
}

GBuffer::GBuffer(int h, int w, uint8_t *_data)
{
	init(h,w);
	memcpy(this->data, data, sizeof(uint8_t) * h * w);
}

GBuffer::GBuffer(GBuffer *src, int x1, int y1, int x2, int y2)
{
	if (x1 > x2){ int tmp = x1; x1 = x2; x2 = tmp; };
	if (y1 > y2){ int tmp = y1; y1 = y2; y2 = tmp; };
	if (x1 < 0) {x2 += (-x1); x1 = 0;};
	if (y1 < 0) {y2 += (-y1); y1 = 0;};

	if (x2 >= src->w) {x1 -= (x2 - (src->w - 1)); x2 = src->w - 1;};
	if (y2 >= src->h) {y1 -= (y2 - (src->h - 1)); y2 = src->h - 1;};



	init (y2-y1, x2-x1);

	for (int i = 0; i < h; i++)
		memcpy(data + i * w, src->data + (i + y1) * src->w + x1, sizeof(Telement) * w);
}




double GBuffer::multiplyAt(GBuffer *that, int x, int y)
{
	int i,  j;
	int i1, j1;
	int count;
	double sum = 0;
	for (i = x, i1 = 0; i1 < that->h && i < h; i++, i1++)
		for (j = y, j1 = 0; j1 < that->w && j < w; j++, j1++)
		{
			sum += data[i * w + j] * that->data[i1 * that->w + j1];
			count++;
		}
	return sum / count;
}

GBuffer *GBuffer::filterGaussian()
{
	GBuffer *toReturn = new GBuffer(h,w);
	for (int i = 2; i < h - 1; i++)
	{
		for (int j = 1; j < w - 1; j++)
		{
			int result =
				      (int)data[(i-1) * w + (j-1)] +
				  2 * (int)data[(i  ) * w + (j-1)] +
				      (int)data[(i+1) * w + (j-1)] +

				  2 * (int)data[(i-1) * w + (j  )] +
				  4 * (int)data[(i  ) * w + (j  )] +
				  2 * (int)data[(i+1) * w + (j  )] +

				      (int)data[(i-1) * w + (j+1)] +
				  2 * (int)data[(i  ) * w + (j+1)] +
				      (int)data[(i+1) * w + (j+1)];


			//int result = (uint8_t)(result > 0 ? result : - result);
			//int result = sqrt(result1 * result1 + result2 * result2);
			toReturn->data[i * w + j] = (result >> 4);
		}
	}
	return toReturn;
}

Hystogramm *GBuffer::getColorHyst()
{
	Hystogramm *toReturn = new Hystogramm(255);
	Telement *src = data;
	for (int i = 0; i < h ; i++)
	{
		for (int j = 0; j < w ; j++)
		{
			toReturn->data[*src]++;
			src++;
		}
	}
	return toReturn;
}

Hystogramm *GBuffer::getHHyst()
{
	Hystogramm *toReturn = new Hystogramm(h);
	Telement *src = data;
	for (int i = 0; i < h ; i++)
	{
		for (int j = 0; j < w ; j++)
		{
			toReturn->data[i] += *src;
			src++;
		}
	}
	return toReturn;
}

Hystogramm *GBuffer::getWHyst()
{
	Hystogramm *toReturn = new Hystogramm(w);
	Telement *src = data;
	for (int i = 0; i < h ; i++)
	{
		for (int j = 0; j < w ; j++)
		{
			toReturn->data[j] += *src;
			src++;
		}
	}
	return toReturn;
}


GBuffer *GBuffer::filterSobel()
{
	GBuffer *toReturn = new GBuffer(h,w);
	for (int i = 1; i < h - 1; i++)
	{
		for (int j = 1; j < w - 1; j++)
		{
			int result1 =
				-     (int)data[(i-1) * w + (j-1)] +
				- 2 * (int)data[(i  ) * w + (j-1)] +
				-     (int)data[(i+1) * w + (j-1)] +

				      (int)data[(i-1) * w + (j+1)] +
				  2 * (int)data[(i  ) * w + (j+1)] +
				      (int)data[(i+1) * w + (j+1)];

			int result2 =
				-     (int)data[(i-1) * w + (j-1)] +
				- 2 * (int)data[(i-1) * w + (j  )] +
				-     (int)data[(i-1) * w + (j+1)] +

					  (int)data[(i+1) * w + (j-1)] +
				  2 * (int)data[(i+1) * w + (j  )] +
					  (int)data[(i+1) * w + (j+1)];



			//int result = (uint8_t)(result > 0 ? result : - result);
			int result = sqrt(result1 * result1 + result2 * result2);
			toReturn->data[i * w + j] = (result <= 255) ? result : 255;
		}
	}
	return toReturn;
}

/*rewrite this to make it fast*/
int GBuffer::getOrientation(int result1, int result2)
{
	double alpha = atan2(result1, result2);
	if (alpha < 0) alpha += (M_PI);

	if (alpha < M_PI / 8 || alpha > M_PI / 8 * 7)
		return 0;
	if (alpha < M_PI / 8 * 3)
		return 1;
	if (alpha < M_PI / 8 * 5)
		return 2;
	if (alpha < M_PI / 8 * 7)
		return 3;
}



GBuffer *GBuffer::filterSobelOrientation(GBuffer **orient)
{
	GBuffer *toReturn = new GBuffer(h,w);
	GBuffer *orientation = new GBuffer(h,w);
	for (int i = 1; i < h - 1; i++)
	{
		for (int j = 1; j < w - 1; j++)
		{
			int result1 =
				-     (int)data[(i-1) * w + (j-1)] +
				- 2 * (int)data[(i  ) * w + (j-1)] +
				-     (int)data[(i+1) * w + (j-1)] +

				      (int)data[(i-1) * w + (j+1)] +
				  2 * (int)data[(i  ) * w + (j+1)] +
				      (int)data[(i+1) * w + (j+1)];

			int result2 =
				-     (int)data[(i-1) * w + (j-1)] +
				- 2 * (int)data[(i-1) * w + (j  )] +
				-     (int)data[(i-1) * w + (j+1)] +

					  (int)data[(i+1) * w + (j-1)] +
				  2 * (int)data[(i+1) * w + (j  )] +
					  (int)data[(i+1) * w + (j+1)];



			//int result = (uint8_t)(result > 0 ? result : - result);
			int result = sqrt(1 * result1 * result1 + 0.0 * result2 * result2);
			int code = getOrientation(result1, result2);

			toReturn->data[i * w + j] = (result <= 255) ? result : 255;
			orientation->data[i*w + j] = code;
		}
	}
	if (orient != NULL)
		*orient = orientation;
	return toReturn;

}

GBuffer *GBuffer::suppressNonMaximum(int low, int high, GBuffer *orientation)
{
	static int shifts[4][2] = {{0,1},{1,1},{1,0},{1,-1}};

	double coef = 255.0 / (high - low);

	GBuffer *toReturn = new GBuffer(h,w);
	for (int i = 1; i < h - 1; i++)
	{
		for (int j = 1; j < w - 1; j++)
		{
			int code = orientation->data[ i* w + j];
			int dx = shifts[code][0];
			int dy = shifts[code][1];
			int first =   data[(i + dy) * w + (j + dx)];
			int second =  data[(i - dy) * w + (j - dx)];
			int current =  data[i * w + j];

			/*if (current > second && current > first && current > low && current < high)
				toReturn->data[i * w + j] = 0xFF;
			else
				toReturn->data[i * w + j] = 0x0;*/
			if (current > second && current > first)
			{
				int result = (current - low) * coef;
				if (result > 255) result = 255;
				if (result < 0) 	result = 0;
				toReturn->data[i * w + j] = result;
			}
			else
				toReturn->data[i * w + j] = 0x0;

		}
	}
	return toReturn;

}

GBuffer *GBuffer::threshold (int level)
{
	GBuffer *toReturn = new GBuffer(h,w);

	Telement *src = data;
	Telement *dst = toReturn->data;

	for (int i = 0; i < h ; i++)
	{
		for (int j = 0; j < w ; j++)
		{
			*dst = (*src > level) ? 0xFF : 0x00;
			dst++;
			src++;
		}
	}
	return toReturn;
}


GBuffer *GBuffer::britnessContrast (int levellow, int levelhigh)
{
	double coef = 255.0 / (levelhigh - levellow);
	GBuffer *toReturn = new GBuffer(h,w);

	Telement *src = data;
	Telement *dst = toReturn->data;

	for (int i = 0; i < h ; i++)
	{
		for (int j = 0; j < w ; j++)
		{
			int result = (*src - levellow) * coef;
			if (result > 255) result = 255;
			if (result < 0) 	result = 0;

			*dst = result;
			dst++;
			src++;
		}
	}
	return toReturn;
}



void GBuffer::drawInto(SDL_Surface *dst)
{
	SDL_LockSurface(dst);
	for (int i = 0; i < h; i++)
	{
		uint32_t *start = (uint32_t *)((uint8_t *)dst->pixels + dst->pitch * i);
		for (int j = 0; j < w; j++)
		{
			int result = data[i * w + j];
			result |= result << 8;
			result |= result << 8;
			start[j] = result;
		}
	}
	SDL_UnlockSurface(dst);
}

void GBuffer::drawInto(SDL_Surface *dst, int x, int y)
{
	SDL_LockSurface(dst);
	for (int i = 0; i < h; i++)
	{
		uint32_t *start = (uint32_t *)((uint8_t *)dst->pixels + dst->pitch * (i + y) + x);
		for (int j = 0; j < w; j++)
		{
			int result = data[i * w + j];
			result |= result << 8;
			result |= result << 8;
			start[j] = result;
		}
	}
	SDL_UnlockSurface(dst);
}


void GBuffer::drawRect(int x1, int y1, int x2, int y2)
{
	int i;
	if (x1 > x2){ int tmp = x1; x1 = x2; x2 = tmp; };
	if (y1 > y2){ int tmp = y1; y1 = y2; y2 = tmp; };

	for (i = x1; i < x2; i++)
	{
		data[y1 * w + i] = 0xFF;
		data[y2 * w + i] = 0xFF;
	}

	for (i = y1; i < y2; i++)
	{
		data[i * w + x1] = 0xFF;
		data[i * w + x2] = 0xFF;
	}
}

GBuffer::~GBuffer() {
	if (this->data != NULL)
		free(this->data);
	this->h = 0;
	this->w = 0;
	count--;
}


Hystogramm::Hystogramm(int _w)
{
	w = _w;
	data = (int *)calloc(w, sizeof(int));
}

Hystogramm::Hystogramm(int _w, int *_data)
{
	w = _w;
	data = (int *)calloc(w, sizeof(int));
	memcpy(data, _data, sizeof(int) * w );
}



Hystogramm::~Hystogramm(){
	if (data)
		free (data);
}


int Hystogramm::getMin()
{
	int min = 0xFFFF;
	for (int i = 0; i < w; i++)
	{
		if (data[i] < min)
			min = data[i];
	}
	return min;

}
int Hystogramm::getMax()
{
	int max = 0;
	for (int i = 0; i < w; i++)
	{
		if (data[i] > max)
			max = data[i];
	}
	return max;
}

void Hystogramm::drawHInto(SDL_Surface *dst, int size)
{
	int max = getMax();
	if (max <= 0) max = 1;

	double coef = (double)size / (double)max;
	int l;

	SDL_LockSurface(dst);
	for (int i = 0; i < w; i++)
	{
		uint8_t *start = ((uint8_t *)dst->pixels) + sizeof(uint32_t) * i;
		l = size - (int)(coef * data[i]);
		for (int j = 0; j < size; j++)
		{
			*((uint32_t *)start) = l < j ? 0x00FF00: 0x0;
			start+=dst->pitch;
		}
	}
	SDL_UnlockSurface(dst);
}

void Hystogramm::drawHInto(SDL_Surface *dst, int size, int width)
{
	int max = getMax();
	if (max <= 0) max = 1;

	double coef = (double)size / (double)max;
	int l;

	double x=0;
	double dx = (double)w / width;
	SDL_LockSurface(dst);
	for (int i = 0; i < width; i++)
	{
		uint8_t *start = ((uint8_t *)dst->pixels) + sizeof(uint32_t) * i;
		l = size - (int)(coef * data[((int)x)]);
		x += dx;
		for (int j = 0; j < size; j++)
		{
			*((uint32_t *)start) = l < j ? 0x00FF00: 0x0;
			start+=dst->pitch;
		}
	}
	SDL_UnlockSurface(dst);
}

void Hystogramm::drawHInto(SDL_Surface *dst, int size, int width, int xcoor, int ycoor)
{
	int max = getMax();
	if (max <= 0) max = 1;

	double coef = (double)size / (double)max;
	int l;
	int i;

	double x = 0;
	double dx = (double)w / width;
	SDL_LockSurface(dst);
	for (i = 0; i < width; i++)
	{
		uint8_t *start = ((uint8_t *)dst->pixels) + sizeof(uint32_t) * ( i + xcoor);
		start += ycoor * dst->pitch ;

		l = size - (int)(coef * data[((int)x)]);
		x += dx;
		for (int j = 0; j < size; j++)
		{
			*((uint32_t *)start) = l < j ? 0x00FF00: 0x0;
			start+=dst->pitch;
		}
	}
	//printf("Width is %d x is %f\n", i, x);
	SDL_UnlockSurface(dst);
}


GBuffer *Hystogramm::matchWith(Hystogramm *other, int *start, int *end)
{
	GBuffer *toReturn = new GBuffer(w, w);
	double result;
	double delta;
	double x;
	double dx;
	double maxresult = 0;
	double maxi = 0;
	double maxj = 0;

	double max = getMax();
	if (max <= 0) max = 1;

	for (int i = 0; i  < 40; i++)
		for (int j = w - 40; j  < w; j++)
		{
			result = 0;
			delta = j - i;
			dx = (double)(other->w) / (delta + 1);
			x = 0;
			for (int r = i; r <= j; r++)
			{
				result += ((double)data[r] * (double)other->data[(int)x]) / (max * 2);
				x += dx;
			}
			result /= (delta + 1);
			toReturn->data[i * toReturn->w + j] = result * 254.0;
			if (result > maxresult)
			{
				maxresult = result;
				maxi = i;
				maxj = j;
			}
		}
	if (start != NULL)
		*start = maxi;
	if (end != NULL)
		*end = maxj;

	return toReturn;
}

