#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>

#include <glib.h>

#include "zip_header.h"
#include "zip_utils.h"
#include "inflate.h"

int uncompress_file(ZipCDR *h,FILE *infile,FILE *outfile)
{
	ZipLocalHeader lh;
	int status=0;

	g_return_val_if_fail(infile != NULL,0);
	g_return_val_if_fail(outfile != NULL,0);
	g_return_val_if_fail(h != NULL,0);

	status=fseek(infile,h->place,SEEK_SET);
	g_return_val_if_fail(status == 0,0);

	status=zip_read_local_header(infile,&lh,1);
	g_return_val_if_fail(status != 0,0);

	g_return_val_if_fail(h->csize>0,0);

	/* buffer=(unsigned char*)g_malloc(h->csize);
	g_return_val_if_fail(buffer != NULL,0); */

	if (h->method == 8) /* inflated */
	{
		status=uncompress_inflated2(infile,h->csize,outfile);
	}
	else if (h->method == 0) /* stored */
	{
		status=uncompress_stored2(infile,h->csize,outfile);
	}
	else
		status=Z_ERRNO;

	if (status!=Z_OK)
		return 0;

	return 1;
}

/* uncompress_file_with_dir()
extra_flags:
0x01 - skip errors
0x02 - lowercase names */

int uncompress_file_with_dir(ZipCDR *h,FILE *infile,const char *dir,
		int extra_flags)
{
	char *filename=0;
	char *filename2;
	FILE *outfile=0;
	int status=0;

	g_return_val_if_fail(h != NULL,0);
	g_return_val_if_fail(h->filename != NULL,0);
	g_return_val_if_fail(dir != NULL,0);
	g_return_val_if_fail(infile != NULL,0);

	filename2=g_strdup(h->filename);

	g_return_val_if_fail(filename2 != NULL,0);

	if (extra_flags&0x02) {
		g_strdown(filename2);
	}

	filename=g_strdup_printf("%s/%s",dir,filename2);
	g_free(filename2);

	make_dirs(filename);

	outfile=fopen(filename,"w"); /* add "b" if this doesn't work */
	if (outfile == NULL)
		return 0;

	status=uncompress_file(h,infile,outfile);

	g_free(filename);
	fclose(outfile);

	return status;
}

int uncompress_inflated(unsigned char *compr,
		unsigned long comprLen, FILE *file)
{
	int i=0;
	int windowBits=0;
	int status=Z_OK;
	unsigned char unCompr[0x8000+1];
	z_stream z;
	z.zalloc=Z_NULL;
	z.zfree=Z_NULL;
	z.opaque=0;
	for (i=0x8000,windowBits=0;!(i&1);i>>=1,++windowBits);

	z.avail_out=0x8000;
	z.next_out=unCompr;
	z.next_in=compr;
	z.avail_in=comprLen;

	status=inflateInit2(&z,-windowBits);
	if (status!=Z_OK)
		return status;

	while(status!=Z_STREAM_END)
	{
		while(z.avail_out>0)
		{
			status=inflate(&z,Z_SYNC_FLUSH);

			if (status==Z_STREAM_END)
				break;
			else if (status!=Z_OK)
				return status;

			if (z.avail_in<=0)
			{
				z.next_in=compr;
				z.avail_in=comprLen;
			}
		}

		fwrite(unCompr,0x8000-z.avail_out,1,file);
		z.next_out=unCompr;
		z.avail_out=0x8000;
	}
	while(status!=Z_STREAM_END)
	{
		status=inflate(&z,Z_SYNC_FLUSH);
		if (status==Z_STREAM_END)
			break;
		if (status!=Z_OK)
			return status;
		z.next_out=unCompr;
		z.avail_out=0x8000;
	}

	status=inflateEnd(&z);
	if (status!=Z_OK)
	{
		return status;
	}

	return Z_OK;
}

/* quick backport of my new C++ inflate routine */
int uncompress_inflated2(FILE *file_in, unsigned long csize,
		FILE *file_out)
{
	int i=0;
	int windowBits=0;
	int status=Z_OK;
	unsigned char* unCompr = 0;
	unsigned char* buffer = 0;
	unsigned long current_loc = 0;
	unsigned long byte_amount = 0x8000;
	unsigned long bytes_available = csize;
	z_stream z;
	z.zalloc=Z_NULL;
	z.zfree=Z_NULL;
	z.opaque=0;

	unCompr = (unsigned char*)malloc(0x8000);
	buffer = (unsigned char*)malloc(byte_amount);

	for (i=0x8000,windowBits=0;!(i&1);i>>=1,++windowBits);

	if (csize == 0)
		return Z_OK;
	if (csize < byte_amount)
		byte_amount = csize;

	fread((void*)buffer, byte_amount, 1, file_in);

	if (feof(file_in) != 0)
	{
		free(unCompr);
		free(buffer);
		return Z_ERRNO;
	}
	bytes_available -= byte_amount;

	z.avail_out = 0x8000;
	z.next_out = unCompr;
	z.next_in = buffer;
	z.avail_in = byte_amount;

	status=inflateInit2(&z,-windowBits);
	if (status!=Z_OK)
	{
		free(unCompr);
		free(buffer);
		return Z_ERRNO;
	}

	while(status!=Z_STREAM_END)
	{
		while(z.avail_out>0)
		{
			status=inflate(&z,Z_SYNC_FLUSH);

			if (status==Z_STREAM_END)
			{
				break;
			}
			else if (status!=Z_OK)
			{
				free(unCompr);
				free(buffer);
				return status;
			}

			if (z.avail_in<=0)
			{
				if (bytes_available < 0x8000)
				{
					byte_amount =
						bytes_available;
				}
				else
				{
					byte_amount = 0x8000;
				}


				fread((void*)buffer, byte_amount, 1, file_in);
				bytes_available -= byte_amount;

				z.next_in = buffer;
				z.avail_in = byte_amount;
			}
		}

		fwrite((const void*)(unCompr), 0x8000-z.avail_out, 1,
				file_out);
		current_loc += z.avail_out;
		z.next_out = unCompr;
		z.avail_out = 0x8000;
	}
	while(status!=Z_STREAM_END)
	{
		status=inflate(&z,Z_SYNC_FLUSH);
		if (status==Z_STREAM_END)
			break;
		if (status!=Z_OK)
		{
			free(unCompr);
			free(buffer);
			return status;
		}
		z.next_out = unCompr;
		z.avail_out = 0x8000;
	}

	status=inflateEnd(&z);
	if (status!=Z_OK)
	{
		free(unCompr);
		free(buffer);
		return status;
	}

	free(unCompr);
	free(buffer);
	return Z_OK;
}

int uncompress_stored(unsigned char *compr,unsigned long comprLen, FILE *file)
{
	unsigned int size=0;
	size=fwrite(compr,comprLen,1,file);
	if (size!=comprLen)
		return Z_ERRNO;

	return Z_OK;
}

int uncompress_stored2(FILE *in_file, unsigned long csize, FILE *out_file)
{
	char *buffer = (char*)malloc(0x8000);
	unsigned long total_len = csize;
	unsigned long len = 0x8000;
	if (buffer == NULL)
	{
		return Z_ERRNO;
	}

	while (total_len > 0)
	{
		if (feof(in_file) != 0)
		{
			free(buffer);
			return Z_ERRNO;
		}
		if (total_len > 0x8000)
		{
			len = 0x8000;
		}
		else
		{
			len = total_len;
		}

		/* FIXME: return value not checked */
		fread((void*)buffer, len, 1, in_file);
		total_len -= len;

		if (len > 0)
		{
			fwrite((void*)buffer, len, 1, out_file);
		}
		else
		{
			break;
		}
	}

	free(buffer);

	return Z_OK;
}
