/* Copyright 2013 Akira Ohta (akohta001@gmail.com)
    This file is part of ntch.

    The ntch is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    The ntch is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ntch.  If not, see <http://www.gnu.org/licenses/>.
    
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <zlib.h>
#include <limits.h>
#include <assert.h>

#include "utils/nt_std_t.h"

static z_stream* nt_zip_alloc();
static void nt_zip_free(z_stream *ptr);

BOOL nt_zip_inflate(int in_fd, int out_fd)
{
	z_stream *zipp;
	BOOL result;
	unsigned char in_buf[1024*2];
	unsigned char out_buf[1024*2];
	int status, count;

	result = FALSE;
	status = Z_OK;

	zipp = nt_zip_alloc();
	if(!zipp)
		return FALSE;
	zipp->next_out = out_buf;
	zipp->avail_out = sizeof(out_buf);

	while(status != Z_STREAM_END){
		if(zipp->avail_in == 0){
			zipp->next_in = in_buf;
			zipp->avail_in = 
					read(in_fd, in_buf, sizeof(in_buf));
		}
		status = inflate(zipp, Z_NO_FLUSH);
		if(status == Z_STREAM_END)
			break;
		if(status != Z_OK)
			goto ERROR_TRAP;

		if(zipp->avail_out == 0){
			if(write(out_fd, out_buf, sizeof(out_buf)) 
					!= sizeof(out_buf))
				goto ERROR_TRAP;
			zipp->next_out = out_buf;
			zipp->avail_out = sizeof(out_buf);
		}
	}/* end while */
	count = sizeof(out_buf) - zipp->avail_out;
	if(count != 0){
		if(write(out_fd, out_buf, count) != count)
			goto ERROR_TRAP;
	}

	result = TRUE;

ERROR_TRAP:
	nt_zip_free(zipp);

	return result;
}

BOOL nt_zip_inflate2(int in_fd, int out_fd)
{
	z_stream *zipp;
	BOOL result;
	unsigned char in_buf[1024*2];
	unsigned char out_buf[1024*2];
	int status;
	int in_buf_len;
	unsigned char *in_bufp;
	unsigned char *cptr;

	result = FALSE;
	in_buf_len = sizeof(in_buf);
	in_bufp = in_buf;

	zipp = nt_zip_alloc();
	if(!zipp)
		return FALSE;
	zipp->next_out = out_buf;
	zipp->avail_out = sizeof(out_buf);

	int chunk, in_buf_remain, chunk_remain;
	int nread, len;

	in_buf_remain = chunk_remain = 0;

	do{
		if(in_buf_remain == 0){
			in_buf_len = sizeof(in_buf);
			in_bufp = in_buf;
			
			nread = read(in_fd, in_bufp, in_buf_len);
			if(nread == -1)
				goto ERROR_TRAP;
			else if(nread == 0)
				break;
			if(chunk_remain > 0){
				chunk = chunk_remain;
				if(nread > chunk){
					zipp->next_in = in_bufp;
					zipp->avail_in = chunk;
					in_buf_remain = nread - chunk;
					in_bufp += chunk;
					chunk_remain = 0;
				}else{
					zipp->next_in = in_bufp;
					zipp->avail_in = nread;
					in_buf_remain = 0;
					chunk_remain = chunk - nread;
				}
			}else{
				chunk = strtol((char*)in_bufp,(char**)&cptr, 16);
				if(chunk == LONG_MAX ||
						chunk == LONG_MIN || !cptr ||
						cptr[0] != '\r' || cptr[1] != '\n'){
					goto ERROR_TRAP;
				}
				cptr += 2;
				len = nread - (cptr - in_bufp);
				assert(len >= 0);
				if(len > chunk){
					zipp->next_in = cptr;
					zipp->avail_in = chunk;
					in_buf_remain = (cptr - in_bufp) + chunk;
					in_bufp = cptr + chunk;
					chunk_remain = 0;
				}else{
					zipp->next_in = cptr;
					zipp->avail_in = len;
					in_buf_remain = 0;
					chunk_remain = chunk - len;
				}
			}
		}else if(in_buf_remain > 0){
			chunk = strtol((char*)(in_bufp), 
						(char**)&cptr, 16);
			if(chunk == LONG_MAX ||
					chunk == LONG_MIN || !cptr ||
					cptr[0] != '\r' || cptr[1] != '\n'){
				goto ERROR_TRAP;
			}
			cptr += 2;
			assert(nread >= 0);
			if(in_buf_remain > chunk){
				zipp->next_in = cptr;
				zipp->avail_in = chunk;
				in_buf_remain = in_buf_remain - chunk;
				in_bufp = cptr + chunk;
				chunk_remain = 0;
			}else{
				len = cptr - in_bufp;
				in_buf_remain -= len;
				zipp->next_in = cptr;
				zipp->avail_in = in_buf_remain;
				chunk_remain = chunk - in_buf_remain;
				in_buf_remain = 0;
			}
		}else{
			assert(0);
		}

		status = Z_OK;

		while(zipp->avail_in != 0){
			status = inflate(zipp, Z_NO_FLUSH);
			if(status == Z_STREAM_END)
				break;
			if(status != Z_OK)
				goto ERROR_TRAP;

			if(zipp->avail_out == 0){
				if(write(out_fd, out_buf, sizeof(out_buf)) 
						!= sizeof(out_buf))
					goto ERROR_TRAP;
				zipp->next_out = out_buf;
				zipp->avail_out = sizeof(out_buf);
			}
		}/* end while */
	}while(status != Z_STREAM_END);
	len = sizeof(out_buf) - zipp->avail_out;
	if(len > 0){
		if(write(out_fd, out_buf, len) != len)
			goto ERROR_TRAP;
	}
	result = TRUE;

ERROR_TRAP:
	nt_zip_free(zipp);

	return result;
}

static z_stream* nt_zip_alloc()
{
	z_stream *zipp;

	zipp = malloc(sizeof(z_stream));
	if(!zipp)
		return  NULL;
	zipp->zalloc = Z_NULL; 
	zipp->zfree = Z_NULL;
	zipp->opaque = Z_NULL;
	zipp->next_in = Z_NULL;
	zipp->avail_in = 0;
	if(inflateInit2(zipp, 32+MAX_WBITS) != Z_OK){
		free(zipp);
		return NULL;
	}
	return zipp;
}

static void nt_zip_free(z_stream *zipp)
{
	assert(zipp);
	inflateEnd(zipp);
	free(zipp);
}

