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

#include <zlib.h>

#define BUFFERSIZE 16384

FILE*zix,*outF;

void barf(const char*msg, int exitcode)
{
	fprintf(stderr, "%s\n");
	fclose(zix);
	fclose(outF);
	exit(exitcode);
}

void myread(void*ptr, size_t size, FILE*stream)
{
	if (1 != fread(ptr, size, 1, stream))
		barf("Unexpected end of file!", 2);
}

void myreadn(uint64_t*n, FILE*stream)
{
	char c;
	int i;
	
	// This may or may not be the best way to sort out endianness, but it should work...
	*n = 0;
	for (i = 0; i < 8; ++i)
	{
		myread(&c, 1, stream);
		*n = (*n << 8) + c;
	}
}

int main(int argc, char**argv)
{
	char buffer[17], *filename;
	unsigned char in[BUFFERSIZE], out[BUFFERSIZE];
	z_stream strm;
	uint64_t clen, ulen, flen;
	
	if (argc < 1)
		barf("argv[1] is missing", 1);
	
	if (!(zix = fopen(argv[1], "rb")))
		barf("Failed to open ZIX file", 1);
	
	if (fread(buffer, 8, 1, zix) != 1 || memcmp("WINZIX\0\3", buffer, 8))
		barf("This is not a ZIX 2 file!", 1);
	myread(buffer, 2, zix);	// Always \0\1 ?
	myreadn(&clen, zix);		//   Compressed data length
	myreadn(&ulen, zix);		// Uncompressed data length
	myreadn(&flen, zix);		//          Filename length
	myread(buffer, 17, zix);	// \0 followed by 16 unidentified bytes O.o
	filename = malloc(flen + 1);
	myread(filename, flen, zix);
	filename[flen] = '\0';
	
	fprintf(stderr, "Reading %d compressed bytes of data to produce %d byte file '%s'...\n", clen, ulen, filename);
	
	/* allocate inflate state */
	strm.zalloc = Z_NULL;
	strm.zfree = Z_NULL;
	strm.opaque = Z_NULL;
	strm.avail_in = 0;
	strm.next_in = Z_NULL;
	if (Z_OK != inflateInit(&strm))
		barf("Failed to initialize decompressor", 3);
	
	if (!(outF = fopen(filename, "wb")))
		barf("Failed to open output file", 4);
	
	/* decompress until deflate stream ends or end of file */
	do {
		size_t have;
		
		strm.avail_in = fread(in, 1, BUFFERSIZE, zix);
		if (ferror(zix)) {
			(void)inflateEnd(&strm);
			barf("Input file error while decompressing", 1);
		}
		if (strm.avail_in == 0)
		{
			(void)inflateEnd(&strm);
			barf("Ran out of data to decompress", 2);
		}
		strm.next_in = in;
		/* run inflate() on input until output buffer not full */
		do {
			strm.avail_out = BUFFERSIZE;
			strm.next_out = out;
			switch (inflate(&strm, Z_NO_FLUSH)) {
			case Z_STREAM_END:
				flen = 0;
				break;
			case Z_NEED_DICT:
			case Z_DATA_ERROR:
			case Z_MEM_ERROR:
				(void)inflateEnd(&strm);
				barf("Error during decompression", 3);
			}
			have = BUFFERSIZE - strm.avail_out;
			if (fwrite(out, 1, have, outF) != have || ferror(outF)) {
				(void)inflateEnd(&strm);
				barf("Error writing to output file", 4);
			}
		} while (strm.avail_out == 0);
		/* done when inflate() says it's done */
	} while (flen);
	/* clean up and return */
	(void)inflateEnd(&strm);
	
	fclose(zix);
	fclose(outF);
	
	fputs("Done!\n", stderr);
}

