#include <setjmp.h>
#include "memory_debug.h"
#include <string.h>
#include <stdlib.h>
#include <memory.h>
#include "lock_level.h"
#include "gif.h"
#include "mediancut.h"
#include "task.h"

//define for compress
#define far_null_ptr	0L
#define null_ptr		0
#define largest_code	4095
#define table_size		5003


jmp_buf recover;

static SEM gif_compres_lock=NULL;

struct code_entry
{
    int prior_code;
    int code_id;
    unsigned char added_char;
};


static int code_size;
static int clear_code;
static int eof_code;
static int min_code;
static int bit_offset;
static int byte_offset, bits_left;
static int max_code;
static int free_code;
static int prefix_code;
static int suffix_char;
static int hx, d;

static unsigned char code_buffer[256+3];
static struct code_entry *code_table;

//define for writing
#define GIF_MALLOC_UNIT	8192
static char GIFsignature[] = "GIF87a";

unsigned char *	_dst = 0;
unsigned char *	_dst_work = 0;
int				_dst_written = 0;
static short			_dst_malloc_num = 0;


///impliment
static int byte_writer(int n)
{
	if(_dst == 0){
		_dst = (unsigned char *)d_alloc(GIF_MALLOC_UNIT,235);
		_dst_work = _dst;
		_dst_malloc_num++;
	}

	*_dst_work = (unsigned char)n;
	_dst_work++;
	_dst_written++;
	if(_dst_written == GIF_MALLOC_UNIT*_dst_malloc_num) {
		_dst_malloc_num++;
		_dst = (unsigned char *)d_re_alloc(_dst, GIF_MALLOC_UNIT*_dst_malloc_num);
		_dst_work = _dst+GIF_MALLOC_UNIT*(_dst_malloc_num-1);
	}
	return 0;
}

static int pixel_reader(unsigned char bitmap[], int width, int height, int * idx)
{
	int	i = *idx;
	if(i >= width*height)
		return -1;
	(*idx)++;
	return (int)(bitmap[i]);
}

static void init_table(int min_code_size)
{
	int i;
	code_size = min_code_size + 1;
	clear_code = 1 << min_code_size;
	eof_code = clear_code + 1;
	free_code = clear_code + 2;
	max_code = 1 << code_size;

	for (i = 0; i < table_size; i++)
		code_table[i].code_id = 0;
}

static void buffer_flush(int n)
{
	int i, status;

	status = byte_writer(n);

	if (status != 0)
		longjmp(recover, status);

	for( i = 0; i < n; i++ ){
		status = byte_writer(code_buffer[i]);

		if (status != 0)
			longjmp(recover, status);
	}
}

static void write_code(int code)
{
	long temp;
	byte_offset = bit_offset >> 3;
	bits_left = bit_offset & 7;
	if(byte_offset >= 254) {
		buffer_flush(byte_offset);
		code_buffer[0] = code_buffer[byte_offset];
		bit_offset = bits_left;
		byte_offset = 0;
	}
	if(bits_left > 0) {
		temp = ((long) code << bits_left) | code_buffer[byte_offset];
		code_buffer[byte_offset] = (char)temp;
		code_buffer[byte_offset + 1] = (char)(temp >> 8);
		code_buffer[byte_offset + 2] = (char)(temp >> 16);
	}
    else{
		code_buffer[byte_offset] = code;
		code_buffer[byte_offset + 1] = code >> 8;
	}
	bit_offset += code_size;
}

static int enLZW_v2(int min_code_size, unsigned char bitmap[], int width, int height)
{
	int status;
	int idx;
	
	idx = 0;

	if(min_code_size < 2 || min_code_size > 9)
		if(min_code_size == 1)
			min_code_size = 2;
		else
			return -3;
	
	code_table = (struct code_entry *)d_alloc(sizeof(struct code_entry)*table_size,23);
	
	if(code_table == null_ptr)
		return -2;

	status = setjmp(recover);

	if(status != 0){
		d_f_ree((char *) code_table);
		return status;
	}
	
	// record the minimum code size 
	byte_writer(min_code_size);		
	bit_offset = 0;
	init_table(min_code_size);
	write_code(clear_code);
	suffix_char = pixel_reader(bitmap, width, height, &idx);

	if(suffix_char >= 0){
		prefix_code = suffix_char;
		while((suffix_char = pixel_reader(bitmap, width, height, &idx)) >= 0) {
			hx = (prefix_code ^ suffix_char << 5) % table_size;
			d = 1;
			for( ; ; ) {
				if(code_table[hx].code_id == 0) {
					write_code(prefix_code);
					d = free_code;
					if(free_code <= largest_code) {
						code_table[hx].prior_code = prefix_code;
						code_table[hx].added_char = suffix_char;
						code_table[hx].code_id = free_code;
						free_code++;
					}
					if (d == max_code)
						if (code_size < 12){
							code_size++;
							max_code <<= 1;
						}
						else{
							write_code(clear_code);
							init_table(min_code_size);
						}

					prefix_code = suffix_char;
					break;
				}
				if(code_table[hx].prior_code == prefix_code && code_table[hx].added_char == suffix_char) {
					prefix_code = code_table[hx].code_id;
					break;
				}
				hx += d;
				d += 2;
				if(hx >= table_size)
					hx -= table_size;
			}
		}
		if(suffix_char != -1)
			longjmp(recover, suffix_char);
		write_code(prefix_code);
	}
	else if (suffix_char != -1)
		longjmp(recover, suffix_char);
	
	write_code(eof_code);
	
	if (bit_offset > 0)
		buffer_flush((bit_offset + 7)/8);
	buffer_flush(0);
	d_f_ree((char *) code_table);
	
	return 0;
}



static int write_color_map(int cr, int bp, int normalize, GIF_RGB *color_map)
{
	int num_colors;
	int n, i, color, status;

	num_colors = 1 << bp;
	n = (1 << cr) - 1;
	for( i = 0; i < num_colors; i++ ){
		color = color_map[i].r;
		if(normalize)
			color = color*255/n;
		status = byte_writer(color);
		if(status != 0)
			return status;

		color = color_map[i].g;
		if(normalize)
			color = color*255/n;
		status = byte_writer(color);
		if(status != 0)
			return status;

		color = color_map[i].b;
		if(normalize)
			color = color*255/n;
		status = byte_writer(color);
		if(status != 0)
			return status;
	}
	return 0;
}

static int write_screen_desc (int width,int height,int cr,int fill_color,int bp,int normalize,GIF_RGB* color_map)
{
	int	status;
	int	i;
	unsigned char buffer[16];

	for( i = 0; i < 6; i++ )
		if((status = byte_writer(GIFsignature[i])) != 0)
			return status;

	buffer[0] = width & 0xFF;
	buffer[1] = width >> 8;
	buffer[2] = height & 0xFF;
	buffer[3] = height >> 8;
	if(color_map == NULL)
		buffer[4] = ((cr - 1) & 0x07) << 4;
	else
		buffer[4] = 0x80 | ((cr - 1) << 4) | ((bp - 1) & 0x0F);
	buffer[5] = fill_color;
	buffer[6] = 0;

	for( i = 0; i < 7; i++ )
		if((status = byte_writer(buffer[i])) != 0)
			return status;

	if(color_map != NULL)
		return write_color_map(cr, bp, normalize, color_map);
	else
		return 0;
}

static int write_image_desc (int left_edge,int top_edge,int width,int height,
						int cr,int interlaced,int bp,int normalize,GIF_RGB *color_map)
{
	int	status;
	int	i;
	unsigned char buffer[16];

	interlaced &= 1;

	buffer[0] = ',';
	buffer[1] = left_edge & 0xFF;
	buffer[2] = left_edge >> 8;
	buffer[3] = top_edge & 0xFF;
	buffer[4] = top_edge >> 8;
	buffer[5] = width & 0xFF;
	buffer[6] = width >> 8;
	buffer[7] = height & 0xFF;
	buffer[8] = height >> 8;

	if(color_map == NULL)
		buffer[9] = interlaced << 6;
	else
		buffer[9] = 0x80 | (interlaced << 6) | ((bp - 1) & 0x0F);

    for( i = 0; i < 10; i++ )
		if((status = byte_writer(buffer[i])) != 0)
			return status;

	if(color_map != NULL)
		return write_color_map(cr, bp, normalize, color_map);
    else
		return 0;
}

static int __gif_compress(unsigned char bitmap[], int width, int height, int cr, int interlaced, int bp,int normalize, GIF_RGB *color_map)
{
	int status;

	status = write_screen_desc(width, height, cr, 0, bp, normalize, color_map);
	if (status != 0)
		return status;

	status = write_image_desc(0, 0, width, height, 0, interlaced, 0, 0, NULL);
	if (status != 0)
		return status;

	status = enLZW_v2(bp, bitmap, width, height);
	if (status != 0)
		return status;
	
	return byte_writer(';');
}

void init_gif_compress(){
	if(gif_compres_lock==NULL)
		gif_compres_lock = new_lock(LL_GIF);
	lock_task(gif_compres_lock);
	_dst = 0;
	_dst_work = 0;
	_dst_written = 0;
	_dst_malloc_num = 0;
}

void close_gif_compress(){
	_dst = 0;
	_dst_work = 0;
	_dst_written = 0;
	_dst_malloc_num = 0;
	unlock_task(gif_compres_lock, NULL);
}

unsigned char * gif_compress( int * ret_length, GIF_ENV * ret_ge, GIF_ENV * ge, unsigned int * data, int width, int height)
{
	unsigned char * pszDecoloredBuffer = (unsigned char*)d_alloc(width*height,33);
	unsigned char * pszCompressedData = 0;
	GIF_RGB gifColorTable[256];
	int i;
	RGB4 *pColorSrc;
	GIF_RGB *pColorDst;
	
	init_gif_compress();
	ret_ge->width = width;
	ret_ge->height = height;
	memset(pszDecoloredBuffer, 0, width * height);
	MedianCut((int*)data, width, height, &(ret_ge->cmap), pszDecoloredBuffer, 1);
	pColorSrc = ret_ge->cmap.color;
	pColorDst = gifColorTable;
	for(i=0; i<256; ++i, ++pColorSrc, ++pColorDst){
		pColorDst->r = pColorSrc->r;
		pColorDst->g = pColorSrc->g;
		pColorDst->b = pColorSrc->b;
	}
	__gif_compress( pszDecoloredBuffer, width, height, 8, 0, 8, 0, gifColorTable);
	d_f_ree(pszDecoloredBuffer);
	pszCompressedData = _dst;
	*ret_length = _dst_written;
	close_gif_compress();
	return pszCompressedData;
}
