#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <zlib.h>
#include <math.h>
#include <gd.h>

unsigned char *in;
unsigned char *out;
size_t in_bytes;

size_t Zlib_Dec(unsigned char *in, unsigned char *out);
void draw_image(void);

int main(int argc, char **argv){
	FILE *in_file;
	FILE *out_file;
	//char *in = NULL;
	//char *out = NULL;
	int status;
	size_t file_bytes;
	size_t bytes_written;
	size_t out_size;
	struct stat buffer;

    int input = open("output1.zlib", O_RDONLY);
    if(input == -1){
		printf("Error opening input file\n");
        //fprintf( stderr, "Error opening %s\n", filename );
        return(1);
    }
    status = fstat(input, &buffer);
    file_bytes=buffer.st_size;
	close(input);
	
	printf("input file bytes: %zu\n", file_bytes);

	in_file = fopen("output1.zlib", "r");
	if( in_file == NULL ){
		printf("failed to open in_file\n");
		return(1);
	}
	out_file = fopen("output2.zlib", "w");
	if( out_file == NULL ){
		printf("failed to open out_file\n");
		return(1);
	}

	in = (unsigned char *)malloc(file_bytes);
	if( file_bytes != fread ( in, 1, file_bytes, in_file ) ){
		printf("Error: fread in_file !\n");
		//free(in);
		return(1);
	}
	printf("%zu bytes written to in array\n", file_bytes);
    //size_t out_total = 0;
    //size_t out_size = ceil(file_bytes*1.5);
    //size_t step_size = ceil(out_size/10);
    //out = (unsigned char *)malloc( ceil(file_bytes*1.5)*sizeof(char) );

	in_bytes = file_bytes;
	out_size = Zlib_Dec(in, out);
	printf("%zu bytes written to out array\n", out_size);
/*
	if(bytes_written != file_bytes){
		printf("Error !\n");
		free(in);
		free(out);
		return(1);
	}
*/

	//bytes_written = fwrite ( out, out_size, 1, out_file );
	//for(bytes_written=0; bytes_written<out_size; bytes_written++){
		//if(fwrite ( &out[bytes_written], 1, 1, out_file ) != 1){
		//	perror ("The following error occurred");
		//	exit(1);
		//}
	//	printf("%zu\n", bytes_written);
	//	fprintf(out_file, "%c", out[bytes_written]);
	//}


	//out[


//	printf("%zu bytes written to out file\n", bytes_written);
	//if (ferror (out_file))
	//	perror ("The following error occurred");


	free(in);
	free(out);
	fclose(in_file);
	fclose(out_file);
	return(0);
}

size_t my_in_func(void *desc, unsigned char *window){
	//return the number of bytes of provided input, and a pointer to that input in window
	//If there is no input available, in() must return zero

//	return 0;

	//designed to be called once
	//printf("here 1\n");
	size_t call_num = *(size_t *)desc;
//printf("call_num: %zu\n", call_num);
	size_t bytes_sofar = (call_num-1)*32768;


	if( bytes_sofar < in_bytes-32768 ){

		window = &in[(call_num-1)*32768];
		return 32768;
	}else{
		window = &in[(call_num-1)*32768];
		return in_bytes-bytes_sofar;
	}
}

size_t my_out_func(void *desc, unsigned char *window, size_t len){
	//return 0 on success, non-zero on failure
	//read window into out
	//printf("here 4\n");

	printf("len: %zu\n", len);


	size_t offset = *(size_t *)desc;
	if(offset > 1)
		out = (unsigned char *)realloc(out, offset*32768);

	(void)memcpy((void*)&out[(offset-1)*32768], (const void*)window, len);

	return 0;
}

size_t Zlib_Dec(unsigned char *in, unsigned char *out){
	if(in_bytes == 0){
		printf("Error 0 size input!\n");
		exit(1);
	}
	FILE *out_file;


    out_file = fopen("output2.zlib", "w");
    if( out_file == NULL ){
        printf("failed to open out_file\n");
        return(1);
    }


	int zlib_status;
	z_stream strm;
	strm.zalloc = Z_NULL;
	strm.zfree = Z_NULL;
	strm.opaque = Z_NULL;
	strm.avail_in = 0;
	strm.next_in = Z_NULL;
	
	//zlib_status = inflateInit(&strm);

	size_t window_size = 32768;
	size_t offset = 1;
	size_t call_num = 1;
	void *in_desc = &call_num;
	void *out_desc = &offset;


	//unsigned char *window = malloc(32768);
//	unsigned char *window = out;
	unsigned char *window = (unsigned char *)malloc( window_size*sizeof(unsigned char));
	out = (unsigned char *)malloc( in_bytes*10*sizeof(unsigned char));
//	zlib_status = inflateBackInit(&strm, 15, window );
/*
	if (zlib_status != Z_OK){
		printf("inflateInit Error!\n");
		exit(1);
	}
*/
	//size_t in_total = 0;
	size_t out_total = 0;
	size_t out_size = window_size;
	//	size_t step_size = ceil(out_size/5);
	size_t step_size = out_size;
	//out = (unsigned char *)malloc( out_size*sizeof(unsigned char));
	printf("step size: %zu out size: %zu\n", step_size, out_size);
	//exit(1);

	//strm.avail_in = 
	//strm.next_in = in;

	out_size = in_bytes*10;
	
	zlib_status = uncompress(out, (uLongf *)&out_size, in, in_bytes);
	draw_image();
	return out_total;

	int passes = 0;

	//strm.avail_in = in_bytes;
	//strm.next_in = in;
//zlib_status = inflateBack(&strm, (in_func)my_in_func, in_desc, (out_func)my_out_func, out_desc);

	do{
		//strm.avail_out = window_size;
/*	
	if( (step_size*passes+step_size)>out_size ){
			out_size+=step_size;
			out = (unsigned char *)realloc(out, out_size);
			if(out == NULL){
				printf("Error reallocating memory!!!\n");
				exit(1);
			}
		}

		strm.next_out = (unsigned char *)&out[step_size*passes];
*/
		//strm.next_out = (unsigned char *)out;
printf("here 2\n");
		zlib_status = inflateBack(&strm, (in_func)my_in_func, in_desc, (out_func)my_out_func, out_desc);
		call_num++;
printf("here 3\n");
		if(zlib_status == Z_STREAM_ERROR){
			printf("inflate Z_STREAM_ERROR\n");
			exit(1);
		}
		switch (zlib_status) {
			case Z_NEED_DICT:
				(void)inflateBackEnd(&strm);
				//zlib_status = Z_DATA_ERROR;
				printf("inflate Z_DATA_ERROR\n");
				exit(1);
			case Z_DATA_ERROR:
				printf("inflate Z_DATA_ERROR\n");
				printf("%s\n", strm.msg);
				(void)inflateBackEnd(&strm);
				//printf("inflate Z_DATA_ERROR\n");
				exit(1);
			case Z_MEM_ERROR:
				(void)inflateBackEnd(&strm);
				printf("inflate Z_MEM_ERROR\n");
				exit(1);
			case Z_STREAM_ERROR:
				(void)inflateBackEnd(&strm);
				printf("inflate Z_STREAM_ERROR\n");
				exit(1);
			case Z_BUF_ERROR:
				(void)inflateBackEnd(&strm);
				printf("inflate Z_BUF_ERROR\n");
				exit(1);
		}
		//keep track of # of bytes written
//		out_total += step_size - strm.avail_out;
		out_total = step_size - strm.avail_out;
		offset++;
//printf("pass %d: %zubytes written\n", passes, step_size - strm.avail_out); 
		passes++;
	} while (strm.avail_out == 0); //while allocated *out has been filled
printf("passes: %d\n", passes);
printf("total_in: %lu\n", strm.total_in);
printf("last message: %s\n", strm.msg);
	(void)inflateBackEnd(&strm);
	//free(strm);


//printf("out total: %zu\n", out_total);


	step_size = fwrite ( (const char *)out, 1, out_total, out_file );

	if( step_size != out_total){
		printf("fwrite error: not all bytes written\n");
		printf("%zu written\n", step_size);
		//if (ferror (out_file))
			perror ("The following error occurred");
		exit(1);
	}

//	for(step_size=0; step_size<out_total; out_total++){
//		fprintf(out_file, "%c", out[step_size]);
//	}


/*
	//TEST
	unsigned char *out2 = &out[1];
	int width = 640;
	int height = 480;
	int counter1, counter2;
	for(counter1=0; counter1<100; counter1++){
		for(counter2=0; counter2<100; counter2++){
			printf("y%dx%d: r%dg%db%d ", counter1, counter2, out2[counter1*width+counter2+1], out2[counter1*width+counter2+2], out2[counter1*width+counter2+3] );
			//if(counter2%25)
			//	printf("\n");
		}
		printf("\n");
	}
*/
	draw_image();
	//bytes_written = fwrite ( (const void *)out, 1, out_total, out_file );
	return out_total;
}


void draw_image(void){
	gdImagePtr im;
	FILE *out;


	unsigned char *in2 = in;
	int width = 640;
	int height = 480;
//	im = gdImageCreate(width, height);
	im = gdImageCreateTrueColor(width, height);
	int counter1;
	int counter2;
	int set_color = gdImageColorAllocate(im, 0, 0, 255);

/*
struct my_colors{
    char red;
    char green;
    char blue;
	//char alpha;
};
struct my_colors *colors = &in[1];
*/

/*
struct my_colors **colors;
    colors = (struct my_colors **)malloc(width * sizeof(struct my_colors *) );
    for(counter1=0; counter1<width; counter1++){
        colors[counter1] = (struct my_colors *)malloc( sizeof(struct my_colors) * height);
    }
*/

	for(counter1=0; counter1<height; counter1++){
		for(counter2=0; counter2<width; counter2++){
			//if(counter2%2){
			//	set_color = gdImageColorResolve(im, 255, 0, 0);
			//}else{
			//	set_color = gdImageColorResolve(im, 255, 255, 255);
			//}
			set_color = gdImageColorResolve(im, in2[counter1*height+counter2], in2[counter1*height+counter2+1], in2[counter1*height+counter2+2]);

//			set_color = gdImageColorResolve(im, colors[counter1*height+counter2].red, colors[counter1*height+counter2].green, colors[counter1*height+counter2].blue);
//			set_color = gdImageColorResolve(im, (int)in2[counter2*width+counter1], (int)in2[counter2*width+counter1+1], (int)in2[counter2*width+counter1+2]);
			//set_color = gdImageColorAllocate(im, in2[counter2*width+counter1], in2[counter2*width+counter1+1], in2[counter2*width+counter1+2]);
			gdImageSetPixel(im, counter1, counter2, set_color);
			//gdImageColorDeallocate(im, set_color);
		}
	}

	out = fopen("reassembled.png", "w");
    gdImageInterlace(im, 1);
    gdImagePng(im, out);
    fclose(out); 
    gdImageDestroy(im);
}