#include <zlib.h>
#include <http_common.h>
#include <http_half.h>
#include <http_convert.h>
#include <event2/buffer.h>

struct hf_content_uncompress
{
	/* MODE AND CALLBACKS */
	unsigned int content_encode;
	hf_private_cb * data_cb;
	void * data_cb_user;

	/* ZLIB STREAM */
	z_stream * z_stream_ptr;
	unsigned char * chunk;
	size_t sz_chunk;
};

struct hf_content_compress
{
	z_stream * z_stream_ptr;
	unsigned int content_encode;
};

void hf_content_uncompress_destroy(struct hf_content_uncompress * cv_object)
{
	(void) inflateEnd(cv_object->z_stream_ptr);
	free(cv_object->z_stream_ptr);
	cv_object->z_stream_ptr = NULL;
	free(cv_object);
}

struct hf_content_uncompress * hf_content_uncompress_create(unsigned int content_encode,
	hf_private_cb * data_cb, void * data_cb_user)
{
	struct hf_content_uncompress * cv_object = ALLOC(struct hf_content_uncompress, 1);
	assert(data_cb != NULL);

	cv_object->content_encode = content_encode;
	cv_object->data_cb = data_cb;
	cv_object->data_cb_user = data_cb_user;

	/* ZSTREAM */
	cv_object->z_stream_ptr = ALLOC(z_stream, 1);
	cv_object->z_stream_ptr->zalloc = NULL;
	cv_object->z_stream_ptr->zfree = NULL;
	cv_object->z_stream_ptr->opaque = NULL;
	cv_object->z_stream_ptr->avail_in = 0;
	cv_object->z_stream_ptr->next_in = Z_NULL;

	/* CHUNK, 4K */
#define CHUNK_SIZE    (1024 * 1024 * 4)
	cv_object->chunk = (unsigned char *) malloc(CHUNK_SIZE);
	cv_object->sz_chunk = CHUNK_SIZE;

	int ret = 0;
	if (content_encode == HTTP_ACCEPT_ENCODING_GZIP)
	{
		ret = inflateInit2(cv_object->z_stream_ptr, MAX_WBITS + 16);
	}
	else if (content_encode == HTTP_ACCEPT_ENCODING_DEFLATE)
	{
		ret = inflateInit2(cv_object->z_stream_ptr, -MAX_WBITS);
	}

	if (ret != Z_OK) goto __errout;
	return cv_object;

__errout:
	free(cv_object->z_stream_ptr);
	free(cv_object->chunk);
	free(cv_object);
	return NULL;
}

int hf_content_uncompress_write(struct hf_content_uncompress * cv_object,
	struct http_half_private * hf_private, tfe_http_event http_ev, const unsigned char * data, size_t datalen)
{
	z_stream * z_stream_ptr = cv_object->z_stream_ptr;
	z_stream_ptr->avail_in = (unsigned int) datalen;
	z_stream_ptr->next_in = (unsigned char *) data;

	if (z_stream_ptr->avail_in == 0)
	{
		(void) inflateEnd(z_stream_ptr);
		return Z_ERRNO;
	}

	int ret = 0;
	do
	{
		z_stream_ptr->avail_out = (unsigned int) cv_object->sz_chunk;
		z_stream_ptr->next_out = cv_object->chunk;

		ret = inflate(z_stream_ptr, Z_NO_FLUSH);
		assert(ret != Z_STREAM_ERROR);  /* state not clobbered */

		if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
		{
			goto __error;
		}

		unsigned int have = (unsigned int) cv_object->sz_chunk - z_stream_ptr->avail_out;
		if (have > 0 && cv_object->data_cb != NULL)
		{
			cv_object->data_cb(hf_private, http_ev, cv_object->chunk, (size_t) have, cv_object->data_cb_user);
		}

	} while (z_stream_ptr->avail_out == 0);
	return ret;

__error:
	(void) inflateEnd(z_stream_ptr);
	return ret;
}

struct hf_content_compress * hf_content_compress_create(unsigned int content_encode)
{
	struct hf_content_compress * cv_object = ALLOC(struct hf_content_compress, 1);
	cv_object->content_encode = content_encode;

	/* ZSTREAM */
	cv_object->z_stream_ptr = ALLOC(z_stream, 1);
	cv_object->z_stream_ptr->zalloc = NULL;
	cv_object->z_stream_ptr->zfree = NULL;
	cv_object->z_stream_ptr->opaque = NULL;
	cv_object->z_stream_ptr->avail_in = 0;
	cv_object->z_stream_ptr->next_in = Z_NULL;

	int __windows_bits = 0;
	if (content_encode == HTTP_ACCEPT_ENCODING_GZIP)
	{
		__windows_bits = MAX_WBITS + 16;
	}

	if (content_encode == HTTP_ACCEPT_ENCODING_DEFLATE)
	{
		__windows_bits = -MAX_WBITS;
	}

	int ret = deflateInit2(cv_object->z_stream_ptr, Z_DEFAULT_COMPRESSION,
		Z_DEFLATED, __windows_bits, 8, Z_DEFAULT_STRATEGY);

	if (ret != Z_OK) goto __errout;
	return cv_object;

__errout:
	free(cv_object->z_stream_ptr);
	free(cv_object);
	return NULL;
}

int hf_content_compress_write(struct hf_content_compress * cv_object,
	const unsigned char * in_data, size_t sz_in_data, struct evbuffer * out_ev_buf, int end)
{
#define SZ_IOVEC    2
	struct evbuffer_iovec v[SZ_IOVEC];

	/* Reserve the space, because the length of the compressed data will be short
	 * than uncompressed data in usually, we set the reserve space as much as sz_in_data */
	size_t __sz_reserve_space = sz_in_data > 512 ? sz_in_data : 512;
	int iov_count = evbuffer_reserve_space(out_ev_buf, __sz_reserve_space, v, SZ_IOVEC);
	if (iov_count < 1 || iov_count > SZ_IOVEC) return -1;

	z_stream * z = cv_object->z_stream_ptr;
	z->next_in = (unsigned char *) in_data;
	z->avail_in = (unsigned int) sz_in_data;

	unsigned int iov_offset = 0;
	z->next_out = (unsigned char *) v[iov_offset].iov_base;
	z->avail_out = (unsigned int) v[iov_offset].iov_len;

	int flush = end ? Z_FINISH : Z_NO_FLUSH;
	int ret = 0;
	do
	{
		ret = deflate(z, flush);
		assert(ret != Z_STREAM_ERROR);
		assert(iov_offset < SZ_IOVEC);

		if (z->avail_out == 0 || z->avail_in == 0)
		{
			unsigned int len = (unsigned int) v[iov_offset].iov_len - z->avail_out;
			v[iov_offset].iov_len = (size_t) len;

			iov_offset++;
			z->next_out = (unsigned char *) v[iov_offset].iov_base;
			z->avail_out = (unsigned int) v[iov_offset].iov_len;
		}
	} while (z->avail_in > 0);

	assert(end == 0 || ret == Z_STREAM_END);
	return evbuffer_commit_space(out_ev_buf, v, iov_count);
}

void hf_content_compress_destroy(hf_content_compress * cv_object)
{
	return;
}
