#include "mupdf/fitz.h"

#include <zlib.h>

#include <limits.h>

static void *fz_z_alloc(void *opaque, unsigned int count, unsigned int size)
{
	fz_context *ctx = (fz_context *)opaque;
	size_t c = count * size;

	return fz_malloc_no_throw(ctx, c);
}

static void fz_z_free(void *opaque, void *addr)
{
	fz_context *ctx = (fz_context *)opaque;

	fz_free(ctx, addr);
}

void fz_deflate(fz_context *ctx, unsigned char *dest, size_t *destLen, const unsigned char *source, size_t sourceLen, fz_deflate_level level)
{
	z_stream stream;
	int err;
	size_t left;

	left = *destLen;
	*destLen = 0;

	stream.zalloc = fz_z_alloc;
	stream.zfree = fz_z_free;
	stream.opaque = ctx;

	err = deflateInit(&stream, (int)level);
	if (err != Z_OK)
		fz_throw(ctx, FZ_ERROR_GENERIC, "zlib compression failed: %d", err);

	stream.next_out = dest;
	stream.avail_out = 0;
	stream.next_in = (z_const Bytef *)source;
	stream.avail_in = 0;

	do {
		if (stream.avail_out == 0) {
			stream.avail_out = left > UINT_MAX ? UINT_MAX : (uInt)left;
			left -= stream.avail_out;
		}
		if (stream.avail_in == 0) {
			stream.avail_in = sourceLen > UINT_MAX ? UINT_MAX : (uInt)sourceLen;
			sourceLen -= stream.avail_in;
		}
		err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
	} while (err == Z_OK);

	/* We might have problems if the compressed length > uLong sized. Tough, for now. */
	*destLen = stream.total_out;
	deflateEnd(&stream);
	if (err != Z_STREAM_END)
		fz_throw(ctx, FZ_ERROR_GENERIC, "Zlib failure: %d", err);
}

unsigned char *fz_new_deflated_data(fz_context *ctx, size_t *compressed_length, const unsigned char *source, size_t source_length, fz_deflate_level level)
{
	size_t bound = fz_deflate_bound(ctx, source_length);
	unsigned char *cdata = fz_malloc(ctx, bound);
	*compressed_length = 0;

	fz_try(ctx)
		fz_deflate(ctx, cdata, &bound, source, source_length, level);
	fz_catch(ctx)
	{
		fz_free(ctx, cdata);
		fz_rethrow(ctx);
	}

	*compressed_length = bound;
	return cdata;
}

unsigned char *fz_new_deflated_data_from_buffer(fz_context *ctx, size_t *compressed_length, fz_buffer *buffer, fz_deflate_level level)
{
	unsigned char *data;
	size_t size = fz_buffer_storage(ctx, buffer, &data);

	if (size == 0 || data == NULL)
	{
		*compressed_length = 0;
		return NULL;
	}

	return fz_new_deflated_data(ctx, compressed_length, data, size, level);
}

size_t fz_deflate_bound(fz_context *ctx, size_t size)
{
	/* Copied from zlib to account for size_t vs uLong */
	return size + (size >> 12) + (size >> 14) + (size >> 25) + 13;
}
