/*   
Copyright 2010 - 2011 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "linux_tile.h"
#include "meshcore/meshdefines.h"
#include "microstack/ILibParsers.h"

extern int SCREEN_NUM;
extern int SCREEN_WIDTH;
extern int SCREEN_HEIGHT;
extern int SCREEN_DEPTH;
extern int TILE_WIDTH;
extern int TILE_HEIGHT;
extern int TILE_WIDTH_COUNT;
extern int TILE_HEIGHT_COUNT;
extern int COMPRESSION_RATIO;
extern struct tileInfo_t **g_tileInfo;
extern unsigned char *jpeg_buffer;
extern int jpeg_buffer_length;

int tilebuffersize = 0;
void* tilebuffer = NULL;
int COMPRESSION_QUALITY = 50;

/******************************************************************************
 * INTERNAL FUNCTIONS
 ******************************************************************************/

//Extracts the required tile buffer from the desktop buffer
int get_tile_buffer(int x, int y, void **buffer, long long bufferSize, void *desktop, long long desktopsize, int tilewidth, int tileheight)
{
	char *target = *buffer;
	int height = 0;

	for (height = y; height < y + tileheight; height++) {
		memcpy(target, (const void *)(((char *)desktop) + (3 * ((height * adjust_screen_size(SCREEN_WIDTH)) + x))), (size_t)(tilewidth * 3));
		target = (char *) (target + (3 * tilewidth));
	}

	return 0;
}

//This function returns 0 and *buffer != NULL if everything was good. retval = jpegsize if the captured image was too large.
int calc_opt_compr_send(int x, int y, int captureWidth, int captureHeight, void* desktop, long long desktopsize, void ** buffer, long long *bufferSize)
{

	*buffer = NULL;
	*bufferSize = 0;

	// Make sure a tile buffer is available. Most of the time, this is skipped.
	if (tilebuffersize != captureWidth * captureHeight * 3)
	{
		if (tilebuffer != NULL) free(tilebuffer);
		tilebuffersize = captureWidth * captureHeight * 3;
		if ((tilebuffer = malloc(tilebuffersize)) == NULL) return 0;
	}

	//Get the final coalesced tile
	get_tile_buffer(x, y, &tilebuffer, tilebuffersize, desktop, desktopsize, captureWidth, captureHeight);

	write_JPEG_buffer(tilebuffer, captureWidth, captureHeight, COMPRESSION_QUALITY);

	if (jpeg_buffer_length > 65500) {
		return jpeg_buffer_length;
	}
	else {
		return 0;
	}
}

#if 0
void dump32bit (const XImage * input)
{
    int row, col;
    static char head[256];

    static FILE *fp2 = NULL;
    char *ptr2, *output;
    long size;

    register unsigned int
	rm = input->red_mask,
	gm = input->green_mask,
	bm = input->blue_mask,
	rs = 16,
	gs = 8,
	bs = 0, *p32 = (unsigned int *) input->data;


    sprintf (head, "P6\n%d %d\n%d\n", input->width, input->height, 255);
    size = ((input->bytes_per_line * input->height) / 4) * 3;
    output = malloc (size);
    ptr2 = output;

    for (row = 0; row < input->height; row++) {
		for (col = 0; col < input->width; col++) {
			*output++ = ((*p32 & rm) >> rs);
			*output++ = ((*p32 & gm) >> gs);
			*output++ = ((*p32 & bm) >> bs);
			p32++;		     // ignore alpha values
		}
		//
		// eat padded bytes, for better speed we use shifting,
		// (bytes_per_line - bits_per_pixel / 8 * width ) / 4
		//
		p32 += (input->bytes_per_line - (input->bits_per_pixel >> 3)
			* input->width) >> 2;
    }

    fp2 = fopen ("/tmp/pic.rgb.pnm", "w");
    fwrite (head, strlen (head), 1, fp2);

    fwrite (ptr2, size, 1, fp2);
    fclose (fp2);
    free (ptr2);
}
#endif

// Really fast CRC-like method. Used for the KVM.
int util_crc(int x, int y, long long bufferSize, void *desktop, long long desktopsize, int tilewidth, int tileheight)
{
    int hval = 0;
    int *bp = NULL;
    int *be = NULL;
    int height = 0;

    for (height = y; height < y + tileheight; height++) {
    	bp = (int *)(((char *)desktop) + (3 * ((height * adjust_screen_size(SCREEN_WIDTH)) + x)));
    	be = (int *)(((char *)desktop) + (3 * ((height * adjust_screen_size(SCREEN_WIDTH)) + x + tilewidth)));
    	while ((bp + 1) <= be)
		{
			// hval *= 0x01000193;
			hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
			hval ^= *bp++;
		}

    	if (be - bp >= 0) {
    		hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
    		hval ^= (*(int *)(((char *)be) - 3));
    	}
    }

    return hval;
}

/******************************************************************************
 * EXTERNAL FUNCTIONS
 ******************************************************************************/

// Adjusts the screen size(width or height) to be exactly divisible by TILE_WIDTH
int adjust_screen_size(int pixles)
{
	int extra = pixles % TILE_WIDTH; //Assuming tile width and height will remain the same.

	if (extra != 0) { return pixles + TILE_WIDTH - extra; }

	return pixles;
}

// Reset the tile info structure
int reset_tile_info(int old_height_count) {
	int row, col;

	if (g_tileInfo != NULL) {
		for (row = 0; row < old_height_count; row++) {
			free(g_tileInfo[row]);
		}
		free(g_tileInfo);
		g_tileInfo = NULL;
	}

	if ((g_tileInfo = (struct tileInfo_t **) malloc (sizeof(struct tileInfo_t *) * TILE_HEIGHT_COUNT)) == NULL) ILIBCRITICALEXIT(254);

	for (row = 0; row < TILE_HEIGHT_COUNT; row++) {
		if ((g_tileInfo[row] = (struct tileInfo_t *)calloc(TILE_WIDTH_COUNT, sizeof(struct tileInfo_t))) == NULL) ILIBCRITICALEXIT(254);
		for (col = 0; col < TILE_WIDTH_COUNT; col++) {
			g_tileInfo[row][col].crc = 0xff;
		}
	}

	return 0;
}

//Fetches the encoded jpeg tile at the given location. The neighboring tiles are coalesced to form a larger jpeg before returning.
int getTileAt(int x, int y, void** buffer, long long *bufferSize, void *desktop, long long desktopsize, int row, int col)
{
	int CRC, rcol, i, r, c;
	int rightcol = col; //Used in coalescing. Indicates the rightmost column to be coalesced.
	int botrow = row; //Used in coalescing. Indicates the bottom most row to be coalesced.
	int r_x = x;
	int r_y = y;
	int captureWidth = TILE_WIDTH;
	int captureHeight = TILE_HEIGHT;

	*buffer = NULL; // If anything fails, this will be the indication.
	*bufferSize = 0;

	if (g_tileInfo[row][col].flag == TILE_TODO) { //First check whether the tile-crc needs to be calculated or not.
		if ((CRC = util_crc(x, y, TILE_HEIGHT * TILE_WIDTH * 3, desktop, desktopsize, TILE_WIDTH, TILE_HEIGHT)) == g_tileInfo[row][col].crc) return 0;
		g_tileInfo[row][col].crc = CRC; //Update the tile CRC in the global data structure.
	}

	g_tileInfo[row][col].flag = TILE_MARKED_NOT_SENT;

	//COALESCING SECTION

	// First got to the right most changed tile and record it
	while (rightcol + 1 < TILE_WIDTH_COUNT) {
		rightcol++;
		r_x = rightcol * TILE_WIDTH;

		CRC = g_tileInfo[row][rightcol].crc;

		if (g_tileInfo[row][rightcol].flag == TILE_TODO) {
			CRC = util_crc(r_x, y, TILE_HEIGHT * TILE_WIDTH * 3, desktop, desktopsize, TILE_WIDTH, TILE_HEIGHT);
		}

		if (CRC != g_tileInfo[row][rightcol].crc || g_tileInfo[row][rightcol].flag == TILE_MARKED_NOT_SENT) { //If the tile has changed, increment the capturewidth.
			g_tileInfo[row][rightcol].crc = CRC;
			//Here we check whether the size of the coalesced bitmap is greater than the threshold (65500)
			if ((captureWidth + TILE_WIDTH) * TILE_HEIGHT * 3 / COMPRESSION_RATIO > 65500) {
				g_tileInfo[row][rightcol].flag = TILE_MARKED_NOT_SENT;
				--rightcol;
				break;
			}

			g_tileInfo[row][rightcol].flag = TILE_MARKED_NOT_SENT;
			captureWidth += TILE_WIDTH;
		}
		else {
			g_tileInfo[row][rightcol].flag = TILE_DONT_SEND;
			--rightcol;
			break;
		}
	}

	//int TOLERANCE = (rightcol - col) / 3;

	// Now go to the bottom tiles, check if they have changed and record them
	while ((botrow + 1 < TILE_HEIGHT_COUNT) && ((captureHeight + TILE_HEIGHT) * captureWidth * 3 / COMPRESSION_RATIO <= 65500)) {
		botrow++;
		r_y = botrow * TILE_HEIGHT;
		int fail = 0;
		r_x = x;

		//int missCount = 0;

		for (rcol = col; rcol <= rightcol; rcol++) {

			CRC = g_tileInfo[botrow][rcol].crc;
			if (g_tileInfo[botrow][rcol].flag == TILE_TODO) {
				CRC = util_crc(r_x, r_y, TILE_HEIGHT * TILE_WIDTH * 3, desktop, desktopsize, TILE_WIDTH, TILE_HEIGHT);
			}

			if (CRC != g_tileInfo[botrow][rcol].crc || g_tileInfo[botrow][rcol].flag == TILE_MARKED_NOT_SENT) {
				g_tileInfo[botrow][rcol].flag = TILE_MARKED_NOT_SENT;
				g_tileInfo[botrow][rcol].crc = CRC;
				r_x += TILE_WIDTH;
			}
			else {
				/*//Keep this part commented. Adding tolerance adds to the complexity of this code.
				missCount++;

				if (missCount > TOLERANCE) {
					fail = 1;
					for (int i = col; i < rcol; i++) {
						if (g_tileInfo[botrow][i].flag == TILE_SKIPPED) {
							g_tileInfo[botrow][i].flag = TILE_DONT_SEND;
						}
						else {
							g_tileInfo[botrow][i].flag = TILE_MARKED_NOT_SENT;
						}
					}
					g_tileInfo[botrow][rcol].flag = TILE_DONT_SEND;
					botrow--;
					break;
				}
				else {
					g_tileInfo[botrow][rcol].flag = TILE_SKIPPED;
					g_tileInfo[botrow][rcol].crc = CRC;
					r_x += TILE_WIDTH;
				}*/
				fail = 1;
				for (i = col; i < rcol; i++) {
					g_tileInfo[botrow][i].flag = TILE_MARKED_NOT_SENT;
				}
				g_tileInfo[botrow][rcol].flag = TILE_DONT_SEND;
				botrow--;
				break;
			}
		}

		if (!fail) {
			captureHeight += TILE_HEIGHT;
		}
		else {
			break;
		}
	}

	int retval = 0;
	int firstTime = 1;

	//This loop is used to adjust the COMPRESSION_RATIO. This loop runs only once most of the time.
	do {
		//retval here is 0 if everything was good. It is > 0 if it contains the size of the jpeg that was created and not sent.
		retval = calc_opt_compr_send(x, y, captureWidth, captureHeight, desktop, desktopsize, buffer, bufferSize);
		if (retval != 0) {
			if (firstTime) {
				// Re-adjust the compression ratio.
				COMPRESSION_RATIO = (int)(((double)COMPRESSION_RATIO/(double)retval) * 60000);//Magic number: 60000 ~= 65500
				if (COMPRESSION_RATIO <= 1) { COMPRESSION_RATIO = 2; }
				firstTime = 0;
			}

			if (botrow > row) { //First time, try reducing the height.
				botrow = row + ((botrow - row + 1) / 2);
				captureHeight = (botrow - row + 1) * TILE_HEIGHT;
			}
			else if (rightcol > col){ //If it is not possible, reduce the width
				rightcol = col + ((rightcol - col + 1) / 2);
				captureWidth = (rightcol - col + 1) * TILE_WIDTH;
			}
			else { //This never happens in any case.
				retval = 0;
				break;
			}

		}
	} while (retval != 0);

	//Set the flags to TILE_SENT
	if (jpeg_buffer != NULL) {
		*bufferSize = jpeg_buffer_length + 8;

		if ((*buffer = malloc(*bufferSize)) == NULL) ILIBCRITICALEXIT(254);
		((unsigned short*)*buffer)[0] = (unsigned short)htons((unsigned short)MNG_KVM_PICTURE);	// Write the type
		((unsigned short*)*buffer)[1] = (unsigned short)htons((unsigned short)*bufferSize);		// Write the size
		((unsigned short*)*buffer)[2] = (unsigned short)htons((unsigned short)x);				// X position
		((unsigned short*)*buffer)[3] = (unsigned short)htons((unsigned short)y);				// Y position
		memcpy((char *)(*buffer) + 8, jpeg_buffer, jpeg_buffer_length);
		free(jpeg_buffer);
		jpeg_buffer = NULL;
		jpeg_buffer_length = 0;

		for (r = row; r <= botrow; r++) {
			for (c = col; c <= rightcol; c++) {
				g_tileInfo[r][c].flag = TILE_SENT;
			}
		}
	}

	return retval;
}


// Get screen buffer from the XImage structure
int getScreenBuffer(char **desktop, long long *desktopsize, XImage *image)
{
	long long size = adjust_screen_size(SCREEN_WIDTH) * adjust_screen_size(SCREEN_HEIGHT) * 3;
	int row, col, i, width_padding_size = 0, height_padding_size = 0;
	char *output;
	register unsigned char* tmpPtr2 = (unsigned char *)image->data;
	register unsigned short* tmpPtr3 = (unsigned short *)image->data;
	register unsigned int
		rm = image->red_mask,
		gm = image->green_mask,
		bm = image->blue_mask,
		rs = 16,
		gs = 8,
		bs = 0, *tmpPtr = (unsigned int *) image->data;
	int bpp = image->bits_per_pixel;

	if (*desktopsize != size) {
		if (*desktop != NULL) { free(*desktop); }
		*desktopsize = size;
		if ((*desktop = (char *) malloc (*desktopsize + 4)) == NULL) ILIBCRITICALEXIT(254);
	}

	if (bpp == 16) {
		rs = 11;
		gs = 5;
	}
	output = *desktop;

	for (row = 0; row < image->height; row++) {
		for (col = 0; col < image->width; col++) {
			if (bpp == 16) {
				*output++ = ((*tmpPtr3 >> rs) & 0x01f) << 3;
				*output++ = ((*tmpPtr3 >> gs) & 0x03f) << 2;
				*output++ = ((*tmpPtr3 >> bs) & 0x01f) << 3;
				tmpPtr3++;
			}
			else if (bpp == 24) {
				*output++ = *(tmpPtr2 + 2);
				*output++ = *(tmpPtr2 + 1);
				*output++ = *tmpPtr2;
				tmpPtr2 += 3;
			}
			else {
				*output++ = ((*tmpPtr & rm) >> rs);
				*output++ = ((*tmpPtr & gm) >> gs);
				*output++ = ((*tmpPtr & bm) >> bs);
				tmpPtr = (unsigned int *) (((char *)tmpPtr) + (image->bits_per_pixel >> 3));
				//tmpPtr++;		     // ignore alpha values
			}
		}
		//
		// eat padded bytes, for better speed we use shifting,
		// (bytes_per_line - bits_per_pixel / 8 * width ) / 4

		tmpPtr += (image->bytes_per_line - (image->bits_per_pixel >> 3)
			* image->width) >> 2;

		width_padding_size = (adjust_screen_size(SCREEN_WIDTH) - image->width) * 3;

		if (width_padding_size != 0) {
			for (i = 0; i < width_padding_size; i++) {
				*output++ = 0;
			}
		}
	}

	height_padding_size = adjust_screen_size(SCREEN_HEIGHT) - image->height;

	if (height_padding_size != 0) {
		for (row = 0; row < height_padding_size; row++) {
			for (col = 0; col < (image->width * 3) + width_padding_size; col++) {
				*output++ = 0;
			}
		}
	}

	return 0;
}


// Set the compression quality
void set_tile_compression(int type, int level)
{
	if (level > 0 && level <= 100) {
		COMPRESSION_QUALITY = level;
	}
	else {
		COMPRESSION_QUALITY = 60;
	}

	//  TODO Make sure the all the types are handled. We ignore the type variable for now.
}
