/*
 * MessagePack for C deflate buffer implementation
 *
 * Copyright (C) 2010 FURUHASHI Sadayuki
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#ifndef MSGPACK_ZBUFFER_H__
#define MSGPACK_ZBUFFER_H__

#include "sysdep.h"
#include <stdlib.h>
#include <string.h>
#include <zlib.h>

#ifdef __cplusplus
extern "C" {
#endif


/**
 * @defgroup msgpack_zbuffer Compressed buffer
 * @ingroup msgpack_buffer
 * @{
 */

typedef struct msgpack_zbuffer {
	z_stream stream;
	char* data;
	size_t init_size;
} msgpack_zbuffer;

#ifndef MSGPACK_ZBUFFER_INIT_SIZE
#define MSGPACK_ZBUFFER_INIT_SIZE 8192
#endif

static inline bool msgpack_zbuffer_init(msgpack_zbuffer* zbuf,
		int level, size_t init_size);
static inline void msgpack_zbuffer_destroy(msgpack_zbuffer* zbuf);

static inline msgpack_zbuffer* msgpack_zbuffer_new(int level, size_t init_size);
static inline void msgpack_zbuffer_free(msgpack_zbuffer* zbuf);

static inline char* msgpack_zbuffer_flush(msgpack_zbuffer* zbuf);

static inline const char* msgpack_zbuffer_data(const msgpack_zbuffer* zbuf);
static inline size_t msgpack_zbuffer_size(const msgpack_zbuffer* zbuf);

static inline bool msgpack_zbuffer_reset(msgpack_zbuffer* zbuf);
static inline void msgpack_zbuffer_reset_buffer(msgpack_zbuffer* zbuf);
static inline char* msgpack_zbuffer_release_buffer(msgpack_zbuffer* zbuf);


#ifndef MSGPACK_ZBUFFER_RESERVE_SIZE
#define MSGPACK_ZBUFFER_RESERVE_SIZE 512
#endif

static inline int msgpack_zbuffer_write(void* data, const char* buf, unsigned int len);

static inline bool msgpack_zbuffer_expand(msgpack_zbuffer* zbuf);


bool msgpack_zbuffer_init(msgpack_zbuffer* zbuf,
		int level, size_t init_size)
{
	memset(zbuf, 0, sizeof(msgpack_zbuffer));
	zbuf->init_size = init_size;
	if(deflateInit(&zbuf->stream, level) != Z_OK) {
		free(zbuf->data);
		return false;
	}
	return true;
}

void msgpack_zbuffer_destroy(msgpack_zbuffer* zbuf)
{
	deflateEnd(&zbuf->stream);
	free(zbuf->data);
}

msgpack_zbuffer* msgpack_zbuffer_new(int level, size_t init_size)
{
	msgpack_zbuffer* zbuf = (msgpack_zbuffer*)malloc(sizeof(msgpack_zbuffer));
	if(!msgpack_zbuffer_init(zbuf, level, init_size)) {
		free(zbuf);
		return NULL;
	}
	return zbuf;
}

void msgpack_zbuffer_free(msgpack_zbuffer* zbuf)
{
	if(zbuf == NULL) { return; }
	msgpack_zbuffer_destroy(zbuf);
	free(zbuf);
}

bool msgpack_zbuffer_expand(msgpack_zbuffer* zbuf)
{
	size_t used = (char*)zbuf->stream.next_out - zbuf->data;
	size_t csize = used + zbuf->stream.avail_out;
	size_t nsize = (csize == 0) ? zbuf->init_size : csize * 2;

	char* tmp = (char*)realloc(zbuf->data, nsize);
	if(tmp == NULL) {
		return false;
	}

	zbuf->data = tmp;
	zbuf->stream.next_out  = (Bytef*)(tmp + used);
	zbuf->stream.avail_out = nsize - used;

	return true;
}

int msgpack_zbuffer_write(void* data, const char* buf, unsigned int len)
{
	msgpack_zbuffer* zbuf = (msgpack_zbuffer*)data;

	zbuf->stream.next_in = (Bytef*)buf;
	zbuf->stream.avail_in = len;

	do {
		if(zbuf->stream.avail_out < MSGPACK_ZBUFFER_RESERVE_SIZE) {
			if(!msgpack_zbuffer_expand(zbuf)) {
				return -1;
			}
		}

		if(deflate(&zbuf->stream, Z_NO_FLUSH) != Z_OK) {
			return -1;
		}
	} while(zbuf->stream.avail_in > 0);

	return 0;
}

char* msgpack_zbuffer_flush(msgpack_zbuffer* zbuf)
{
	while(true) {
		switch(deflate(&zbuf->stream, Z_FINISH)) {
		case Z_STREAM_END:
			return zbuf->data;
		case Z_OK:
			if(!msgpack_zbuffer_expand(zbuf)) {
				return NULL;
			}
			break;
		default:
			return NULL;
		}
	}
}

const char* msgpack_zbuffer_data(const msgpack_zbuffer* zbuf)
{
	return zbuf->data;
}

size_t msgpack_zbuffer_size(const msgpack_zbuffer* zbuf)
{
	return (char*)zbuf->stream.next_out - zbuf->data;
}

void msgpack_zbuffer_reset_buffer(msgpack_zbuffer* zbuf)
{
	zbuf->stream.avail_out += (char*)zbuf->stream.next_out - zbuf->data;
	zbuf->stream.next_out = (Bytef*)zbuf->data;
}

bool msgpack_zbuffer_reset(msgpack_zbuffer* zbuf)
{
	if(deflateReset(&zbuf->stream) != Z_OK) {
		return false;
	}
	msgpack_zbuffer_reset_buffer(zbuf);
	return true;
}

char* msgpack_zbuffer_release_buffer(msgpack_zbuffer* zbuf)
{
	char* tmp = zbuf->data;
	zbuf->data = NULL;
	zbuf->stream.next_out = NULL;
	zbuf->stream.avail_out = 0;
	return tmp;
}

/** @} */


#ifdef __cplusplus
}
#endif

#endif /* msgpack/zbuffer.h */

