/**
 * \file g12buffer.c
 * \brief This file helps to handle the grayscale 12bit depth buffer
 *
 *
 *  \ingroup corefiles
 *  \date May 13, 2009
 *  \author Alexander Pimenov
 */
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "global.h"
#include "g12buffer.h"
#include "projective.h"

#ifdef ASSERT
int g12BufferCounter = 0;
#endif

/**
 *
 * \brief This function creates a gray scale 12 bit buffer for the given width and height.
 *
 * In fact in the PC implementation a 16bit buffer is used. The created image is filled with 0 values
 *
 * You should not forget to destroy the created buffer with the call to g12BufferDelete(toDelete)
 *
 * \param h The height of the image to be created.
 * \param w The width of the image to be created.
 *
 * \return a newly created buffer.
 */

G12Buffer *g12BufferCreate(int h, int w)
{
	G12Buffer *toReturn = (G12Buffer *) dv_malloc (sizeof (G12Buffer));
	toReturn->h = h;
	toReturn->w = w;
	toReturn->data = (uint16_t *) dv_calloc(h * w , sizeof(uint16_t));

#ifdef ASSERT
	g12BufferCounter++;
#endif

	return toReturn;
}

/**
 * This function deletes a grey scale 12 bit buffer
 *
 *  \param toDelete the buffer to be deleted
 */
void g12BufferDelete(G12Buffer *toDelete)
{
	if (toDelete->data != NULL)
		dv_free(toDelete->data);
	dv_free(toDelete);


#ifdef ASSERT
	g12BufferCounter--;
#endif

}

/**
 *  This function clones the buffer.
 *
 *  \param input a buffer to be cloned
 *
 *  \return a newly created buffer with a cloned contence
 **/
G12Buffer *g12BufferClone(G12Buffer *input)
{
	G12Buffer *toReturn = g12BufferCreate(input->h, input->w);
	memcpy(toReturn->data, input->data, input->h * input->w * sizeof(uint16_t));
	return toReturn;
}



/**
 * This function creates a new gray scale 12 bit buffer for a rectangle of the other buffer
 */

G12Buffer* g12BufferCreateFromRegion(G12Buffer *src, int x1, int y1, int x2, int y2)
{
    G12Buffer *dst;
	// Swap the rectangle borders
	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;};



	dst = g12BufferCreate(y2-y1, x2-x1);

	int i;
	// Use memcpy for this
	for (i = 0; i < dst->h; i++)
	{
		memcpy(dst->data + i * dst->w, src->data + (i + y1) * src->w + x1, sizeof(uint16_t) * dst->w);
	}

	return dst;
}

/**
 * \brief Transforms the greyscale buffer
 *
 *   Creates a new 12bit grey scale buffer that represents the input 12bit
 * buffer after the application the 2D affine transform. And having the area
 * [0..neww]x[0..newh]
 *
 */
G12Buffer* applyAffineTransformBilinear (projectiveTransform2D inverseTransform, G12Buffer *input, int newh, int neww)
{
	G12Buffer* toReturn = g12BufferCreate(newh, neww);
	int h = newh;
	int w = neww;
	int i,j;
	double x, y, z;

	uint16_t* start = toReturn->data;

	for (i = 0; i < h; i++)
	{
		for (j = 0; j < w; j++)
		{
			// This seems to be wrong...
			x = inverseTransform.data[0] * j + inverseTransform.data[1] * i + inverseTransform.data[2];
			y = inverseTransform.data[3] * j + inverseTransform.data[4] * i + inverseTransform.data[5];
			z = inverseTransform.data[6] * j + inverseTransform.data[7] * i + inverseTransform.data[8];
			if (z == 0)
			{
				start[i * w + j] = 0x80 << 4;
				continue;
			}

			x /= z;
			y /= z;

			if (x < 0 || x > input->w - 2  || y < 0 || y > input->h - 2)
			{
				start[i * w + j] = 0x80 << 4;
				continue;
			}
			uint16_t a = input->data[(int)(y) * input->w  +     (int)(x)];
			uint16_t b = input->data[(int)(y) * input->w  +     (int)(x + 1)];
			uint16_t c = input->data[(int)(y + 1) * input->w  + (int)(x)];
			uint16_t d = input->data[(int)(y + 1) * input->w  + (int)(x + 1)];
			double k1 = x - (int)(x);
			double k2 = y - (int)(y);

			uint16_t result = (uint16_t)((a * (1 - k1) + k1 * b) * (1 - k2) +
						 (c * (1 - k1) + k1 * d) * k2);
			start[i * w + j] = result;
		}
	}
	return toReturn;
}


void g12BufferGainOffset (G12Buffer *input, double gain, int offset)
{
	int i,j;
	int h = input->h;
	int w = input->w;

	for (i = 0; i < h; i++)
	{
		for (j = 0; j < w; j++ )
		{
			double value = ((double)input->data[i * w + j] - offset) / gain;
			if (value < 0)
				input->data[i * w + j] = 0;
			else if (value >= G12BUFFER_MAX_VALUE)
				input->data[i * w + j] = G12BUFFER_MAX_VALUE - 1;
			else
				input->data[i * w + j] = (uint16_t)value;
		}
	}
}

void g12BufferMapToInterval (G12Buffer *input, int32_t min, int32_t max)
{
	int i,j;
	int h = input->h;
	int w = input->w;

	int32_t interval = max - min;

	for (i = 1; i < h - 1; i++)
	{
		for (j = 1; j < w -1; j++ )
		{
			int64_t value = ((uint64_t)value * interval) / G12BUFFER_MAX_VALUE + min;
			if (value < 0)
				input->data[i * w + j] = 0;
			else if (value > G12BUFFER_MAX_VALUE)
				input->data[i * w + j] = G12BUFFER_MAX_VALUE;
			else
				input->data[i * w + j] = (uint16_t)value;
		}
	}
}


G12Buffer *g12BufferSobelX (G12Buffer *input)
{
	int i,j;
	int h = input->h;
	int w = input->w;
	G12Buffer* toReturn = g12BufferCreate(h,w);


	for (i = 1; i < h - 1; i++)
	{
		for (j = 1; j < w -1; j++ )
		{
			// TODO: Optimize this for optimal accessing

			// DX and DY will range in [ -2^12 ; 2^12 ]
			int16_t dx =    ( - input->data[(i - 1) * w + j - 1] + input->data[(i - 1) * w + j + 1]
			          + 2 * ( - input->data[      i * w + j - 1] + input->data[      i * w + j + 1])
			                  - input->data[(i + 1) * w + j - 1] + input->data[(i + 1) * w + j + 1]) / 8;
			toReturn->data[i * w + j] = (dx + (1 << 11));
		}
	}
	return toReturn;
}

G12Buffer *g12BufferMix(G12Buffer *A, G12Buffer *B, double alpha)
{
#ifdef ASSERT

#endif
	G12Buffer *toReturn = g12BufferCreate(A->h, A->w);
	int i,j;
	for (i = 0; i < A->h; i++)
		for (j = 0; j < A->w; j++)
		{
			G12ELEM(toReturn, j,i) = G12ELEM(A, j,i) * alpha + G12ELEM(B, j, i) * (1 - alpha);
		}

	return toReturn;
}

G12Buffer *g12BufferSobelY (G12Buffer *input)
{
	int i,j;
	int h = input->h;
	int w = input->w;
	G12Buffer* toReturn = g12BufferCreate(h,w);


	for (i = 1; i < h - 1; i++)
	{
		for (j = 1; j < w -1; j++ )
		{
			// TODO: Optimize this for optimal accessing
			int16_t dy =    ( - input->data[(i - 1) * w + j - 1] + input->data[(i + 1) * w + j - 1]
					  + 2 * ( - input->data[(i - 1) * w + j    ] + input->data[(i + 1) * w + j    ])
							  - input->data[(i - 1) * w + j + 1] + input->data[(i + 1) * w + j + 1]) / 8;
			toReturn->data[i * w + j] = (dy + (1 << 11));
		}
	}
	return toReturn;
}

#ifndef NO_FLOAT
G12Buffer *g12BufferSobelMag (G12Buffer *input, int scale)
{
	int i,j;
	int h = input->h;
	int w = input->w;
	G12Buffer* toReturn = g12BufferCreate(h,w);


	for (i = 1; i < h - 1; i++)
	{
		for (j = 1; j < w -1; j++ )
		{
			// TODO: Optimize this for optimal accessing
			int16_t dy =    ( - input->data[(i - 1) * w + j - 1] + input->data[(i + 1) * w + j - 1]
					  + 2 * ( - input->data[(i - 1) * w + j    ] + input->data[(i + 1) * w + j    ])
							  - input->data[(i - 1) * w + j + 1] + input->data[(i + 1) * w + j + 1]);
			int16_t dx =    ( - input->data[(i - 1) * w + j - 1] + input->data[(i - 1) * w + j + 1]
					          + 2 * ( - input->data[      i * w + j - 1] + input->data[      i * w + j + 1])
					                  - input->data[(i + 1) * w + j - 1] + input->data[(i + 1) * w + j + 1]);

			uint32_t result= (uint32_t)(sqrt ((double)dx * dx + (double)dy*dy) / scale);
			toReturn->data[i * w + j] = (result > G12BUFFER_MAX_VALUE) ? G12BUFFER_MAX_VALUE : result;
		}
	}
	return toReturn;
}
#endif

