/***************************************************************************
                           czlib.cpp  -  description
                             -------------------
    begin                : Thu Oct 12 2004
    copyright            : (C) 2004 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "czlib.h"

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

#include <zlib.h>

#include "cbytearray.h"

/** */
CZLib::CZLib()
{
	m_bInit = false;
	m_pZStream = new z_stream_s;
}

/** */
CZLib::~CZLib()
{
	if ( m_bInit )
	{
		inflateEnd(m_pZStream);
	}
	
	delete m_pZStream;
}

/** */
int CZLib::InflateZBlock( const char * in, int * inlen, char * out, int * outlen )
{
	int res = -1;

	int i;
	
	if ( m_bInit == false )
	{
		m_bInit = true;

		m_pZStream->next_in   = 0;
		m_pZStream->avail_in  = 0;
		m_pZStream->opaque    = Z_NULL;
		m_pZStream->zalloc    = Z_NULL;
		m_pZStream->zfree     = Z_NULL;
		m_pZStream->total_out = 0;

		inflateInit(m_pZStream);
	}
	
	// only update the zlib state if we have been given some data
	if ( (in != 0) && (*inlen != 0) )
	{
		m_pZStream->next_in   = (Bytef*)in;
		m_pZStream->avail_in  = *inlen;
	}
	
	if ( (out != 0) && (*outlen != 0) )
	{
		m_pZStream->next_out  = (Bytef*)out;
		m_pZStream->avail_out = *outlen;
	}
	
	i = inflate(m_pZStream,Z_SYNC_FLUSH);

	/* Z_BUF_ERROR is non fatal, and means call the function again with more memory space */

	if  ( (i == Z_OK) || (i == Z_STREAM_END) || (i == Z_BUF_ERROR) )
	{
		*outlen = *outlen - m_pZStream->avail_out;
		*inlen  = *inlen - m_pZStream->avail_in;
		
		res = 1; // more data
		
		if ( i == Z_STREAM_END )
		{
			inflateEnd(m_pZStream);
			m_bInit = false;
			res = 0; // finished
		}
	}
	else
	{
		inflateEnd(m_pZStream);
		m_bInit = false;
		*outlen = 0;
		res = -1; // error
	}
/*
	printf("INFLATE: %d %d %d %d\n",
		i,
		m_pZStream->avail_in,
		m_pZStream->avail_out,
		m_pZStream->total_out );
*/
	return res;
}

/** */
unsigned long CZLib::GetTotalOut() const
{
	return m_pZStream->total_out;
}

#define MAX_BUFFER_LEN	536870912	// 512MiB max memory

/** */
unsigned long CZLib::OneShotInflate( const char * in, const unsigned long inlen, CByteArray * out )
{
	int res = 0;
	unsigned long outlen = inlen;
	char * buffer = 0;
	
	while ( true )
	{
		outlen *= 2;
		
		if ( buffer )
		{
			free(buffer);
		}
		
		if ( outlen < MAX_BUFFER_LEN )
		{
			buffer = (char*) malloc(outlen);
		}
		
		if (!buffer)
		{
			printf("CZLib::OneShotInflate: malloc failed\n");
			outlen = 0;
			break;
		}
		
		res = uncompress( (Bytef*)buffer, &outlen, (const Bytef*)in, inlen );
		
		if ( res == Z_OK )
		{
			out->Append( (const unsigned char*)buffer, outlen );
			break;
		}
		else if ( res == Z_DATA_ERROR ) // corrupt source data
		{
			//printf("CZLib::OneShotInflate: corrupt data\n");
			outlen = 0;
			break;
		}
		else if ( res == Z_MEM_ERROR ) // not enoungh memory
		{
			//printf("CZLib::OneShotInflate: zlib out of memory\n");
			outlen = 0;
			break;
		}
		else if ( res == Z_BUF_ERROR ) // buffer too small
		{
			// nothing, try again
		}
	}
	
	if ( buffer )
	{
		free(buffer);
	}
	
	return outlen;
}

/** */
CDeflater::CDeflater()
{
	m_bInit = false;
	m_pZStream = new z_stream_s;
}

/** */
CDeflater::~CDeflater()
{
	if ( m_bInit )
	{
		/* happens whenever not finished transfer is diconnected */
		/* int ret = deflateEnd(m_pZStream);
		
		if ( ret == Z_DATA_ERROR )
		{
			printf("CDeflater::~CDeflater: warning: data discarded!\n");
		} */
		deflateEnd(m_pZStream);
	}
	
	delete m_pZStream;
}

/** */
int CDeflater::DeflateBlock( const char * in, int * inlen, char * out, int * outlen, bool more )
{
	int res = -1;
	
	int ret, flush;
	
	// if first block
	if ( m_bInit == false )
	{
		m_bInit = true;
		
		m_pZStream->next_in   = 0;
		m_pZStream->avail_in  = 0;
		m_pZStream->next_out  = 0;
		m_pZStream->avail_out = 0;
		m_pZStream->opaque    = Z_NULL;
		m_pZStream->zalloc    = Z_NULL;
		m_pZStream->zfree     = Z_NULL;
		m_pZStream->total_out = 0;
		
		deflateInit( m_pZStream, Z_DEFAULT_COMPRESSION );
	}
	
	// only update the zlib state if we have been given some data...
	if ( (in != 0) && (*inlen != 0) )
	{
		m_pZStream->next_in   = (Bytef*)in;
		m_pZStream->avail_in  = *inlen;
	}
	
	if ( (out != 0) && (*outlen != 0) )
	{
		m_pZStream->next_out  = (Bytef*)out;
		m_pZStream->avail_out = *outlen;
	}
	
	if ( more )
	{
		flush = Z_NO_FLUSH;
	}
	else
	{
		flush = Z_FINISH;
	}
	
	ret = deflate( m_pZStream, flush );
	
	if ( more )
	{
		if ( (ret == Z_OK) || (ret == Z_BUF_ERROR) )
		{
			*outlen = *outlen - m_pZStream->avail_out;
			*inlen = *inlen - m_pZStream->avail_in;
			res = 1; // more data
		}
		else
		{
			deflateEnd(m_pZStream);
			m_bInit = false;
			*outlen = 0;
			res = -1; // error
		}
	}
	else
	{
		if ( (ret == Z_OK) || (ret == Z_BUF_ERROR) )
		{
			*outlen = *outlen - m_pZStream->avail_out;
			*inlen = *inlen - m_pZStream->avail_in;
			res = 1; // more data
		}
		else if ( ret == Z_STREAM_END )
		{
			*outlen = *outlen - m_pZStream->avail_out;
			*inlen = *inlen - m_pZStream->avail_in;
			res = 0; // finished
			
			deflateEnd(m_pZStream);
			m_bInit = false;
		}
		else
		{
			*outlen = 0;
			
			deflateEnd(m_pZStream);
			m_bInit = false;
			res = -1; // error
		}
	}
	
	//printf( "DEFLATE: %d %d %d %d\n", ret, m_pZStream->avail_in, m_pZStream->avail_out, m_pZStream->total_out );
	
	return res;
}

/** */
unsigned long CDeflater::GetTotalOut() const
{
	return m_pZStream->total_out;
}

/** */
unsigned long CDeflater::OneShotDeflate( const char * in, const unsigned long inlen, CByteArray * out )
{
	int res = 0;
	unsigned long outlen = inlen;
	char * buffer = 0;
	
	while ( true )
	{
		outlen *= 2;
		
		if ( buffer )
		{
			free(buffer);
		}
		
		if ( outlen < MAX_BUFFER_LEN )
		{
			buffer = (char*) malloc(outlen);
		}
		
		if ( !buffer )
		{
			printf("CDeflater::OneShotDeflate: malloc failed\n");
			outlen = 0;
			break;
		}
		
		res = compress( (Bytef*)buffer, &outlen, (const Bytef*)in, inlen );
		
		if ( res == Z_OK )
		{
			out->Append( (const unsigned char*)buffer, outlen );
			break;
		}
		else if ( res == Z_MEM_ERROR ) // not enoungh memory
		{
			printf("CDeflater::OneShotDeflate: zlib out of memory\n");
			outlen = 0;
			break;
		}
		else if ( res == Z_BUF_ERROR ) // buffer too small
		{
			// nothing, try again
		}
	}
	
	if ( buffer )
	{
		free(buffer);
	}
	
	return outlen;
}
