/** @file Zipper.h
 *
 *  @brief CZipperデータ圧縮クラス
 *
 *  @version	1.0
 *	@date		2013/1/7
 *	@author		DENSO WAVE
 *
 */
#ifndef _ZIPPER_H
#define _ZIPPER_H

#include <stdlib.h>

#define ZLIB_WINAPI
#include "zlib.h"	// zlib http://zlib.net/ 
#ifdef _DEBUG
#pragma comment(lib, "zlibd.lib")
#else
#pragma comment(lib, "zlib.lib")
#endif

#define ZIP_DEFAULT_BUFFER_SIZE 40960
class CZipper 
{
public:
	CZipper() 
	{
		m_pBuffer = NULL;
		m_lBufferSize = 0;
		m_lDataSize = 0;
		m_lZipLevel = Z_DEFAULT_COMPRESSION;
	}
	CZipper(long size) 
	{
		m_pBuffer = NULL;
		m_lBufferSize = 0;
		m_lDataSize = 0;
		m_lZipLevel = Z_DEFAULT_COMPRESSION;
		Allocate(size);
	}
	~CZipper() 
	{
		Free();
	}

	// Return code Value Likely reason 
	// Z_OK 0 succeeded
	// Z_STREAM_ERROR -2 pDest is NULL 
	// Z_DATA_ERROR -3 corrupted pSrc passed to ezuncompress 
	// Z_MEM_ERROR -4 out of memory 
	// Z_BUF_ERROR -5 pDest length is not enough 
	int Compress( const unsigned char* pSrc, long nSrcLen )
	{
		if (m_lBufferSize == 0) {
			if (Allocate(ZIP_DEFAULT_BUFFER_SIZE) == 0) return Z_MEM_ERROR;
		}
		long nLen = m_lBufferSize;
		int err = Compress(m_pBuffer, &nLen, pSrc, nSrcLen);
		if ( err == Z_BUF_ERROR ) {
			if (Allocate(nLen) == 0) return Z_MEM_ERROR;
			err = Compress(m_pBuffer, &nLen, pSrc, nSrcLen);
		}
		if (err == Z_OK) m_lDataSize = nLen;
		return err;
	}
	int Compress( unsigned char* pDest, long* pnDestLen, const unsigned char* pSrc, long nSrcLen )
	{
	    z_stream stream;
	    int err;
	    int nExtraChunks;

	    if (pDest == pSrc) return Z_DATA_ERROR;

	    stream.next_in = (Bytef*)pSrc;
	    stream.avail_in = (uInt)nSrcLen;
	    stream.zalloc = (alloc_func)0;
	    stream.zfree = (free_func)0;
	    stream.opaque = (voidpf)0;

	    err = deflateInit(&stream, m_lZipLevel);
	    if (err != Z_OK) return err;

	    nExtraChunks = 0;
	    do {
	        stream.next_out = pDest;
	        stream.avail_out = *pnDestLen;
	        err = deflate(&stream, Z_FINISH);
	        if (err == Z_STREAM_END )
	            break;
	        if (err != Z_OK) {
	            deflateEnd(&stream);
	            return err;
	        }
	        nExtraChunks += 1;
	    } while (stream.avail_out == 0);

	    *pnDestLen = stream.total_out;

	    err = deflateEnd(&stream);
	    if (err != Z_OK) return err;

	    return nExtraChunks ? Z_BUF_ERROR : Z_OK;
	}

	// Return code Value Likely reason 
	// Z_OK 0 succeeded
	// Z_STREAM_ERROR -2 pDest is NULL 
	// Z_DATA_ERROR -3 corrupted pSrc passed to ezuncompress 
	// Z_MEM_ERROR -4 out of memory 
	// Z_BUF_ERROR -5 pDest length is not enough 
	int Uncompress( const unsigned char* pSrc, long nSrcLen )
	{
		if (m_lBufferSize == 0) {
			if (Allocate(ZIP_DEFAULT_BUFFER_SIZE) == 0) return Z_MEM_ERROR;
		}
		long nLen = m_lBufferSize;
		int err = Uncompress(m_pBuffer, &nLen, pSrc, nSrcLen);
		if ( err == Z_BUF_ERROR ) {
			if (Allocate(nLen) == 0) return Z_MEM_ERROR;
			err = Uncompress(m_pBuffer, &nLen, pSrc, nSrcLen);
		}
		if (err == Z_OK) m_lDataSize = nLen;
		return err;
	}
	int Uncompress( unsigned char* pDest, long* pnDestLen, const unsigned char* pSrc, long nSrcLen )
	{
	    z_stream stream;
	    int err;

	    int nExtraChunks;

	    if (pDest == pSrc) return Z_DATA_ERROR;

	    stream.next_in = (Bytef*)pSrc;
	    stream.avail_in = (uInt)nSrcLen;
	    stream.zalloc = (alloc_func)0;
	    stream.zfree = (free_func)0;

	    err = inflateInit(&stream);
	    if (err != Z_OK) return err;

	    nExtraChunks = 0;
	    do {
	        stream.next_out = pDest;
	        stream.avail_out = *pnDestLen;
	        err = inflate(&stream, Z_FINISH);
	        if (err == Z_STREAM_END )
	            break;
	        if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
	            err = Z_DATA_ERROR;
	        if (err != Z_BUF_ERROR) {
	            inflateEnd(&stream);
	            return err;
	        }
	        nExtraChunks += 1;
	    } while (stream.avail_out == 0);

	    *pnDestLen = stream.total_out;

	    err = inflateEnd(&stream);
	    if (err != Z_OK) return err;

	    return nExtraChunks ? Z_BUF_ERROR : Z_OK;
	}

	long Allocate(long size) 
	{
		Free();
		m_pBuffer = (unsigned char*)malloc(size);
		if (m_pBuffer) {
			m_lBufferSize = size;
		}
		return m_lBufferSize;
	}
	void Free()
	{
		if(m_pBuffer) {
			free(m_pBuffer);
			m_pBuffer = NULL;
			m_lBufferSize = 0;
			m_lDataSize = 0;
		}
	}
	long BufferSize() { return m_lBufferSize; }
	long DataSize() { return m_lDataSize; }
	// ZipLevel = -1, 1 - 9
	long SetZipLevel(long lLevel) 
	{ 
		if (((lLevel >= Z_BEST_SPEED) && (lLevel <= Z_BEST_COMPRESSION)) 
			|| (lLevel == Z_DEFAULT_COMPRESSION)) {
			m_lZipLevel = lLevel;
			return Z_OK;
		}
		return Z_ERRNO;
	}
	long GetZipLevel() { return m_lZipLevel; }
	unsigned char* Buffer() { return m_pBuffer; }

private:
	unsigned char* m_pBuffer;
	long m_lBufferSize;
	long m_lDataSize;
	long m_lZipLevel;
};

/* outside buffer sample - how to use CZipper class 

    BYTE pbSrc[] = "Learn from yesterday, live for today, hope for tomorrow. The important thing is not to stop questioning.\n";
    BYTE *pSrcBuf = pbSrc;
    long nSrcLen = (long)strlen( (char*)pbSrc )+1; // include terminating NULL
    int nErr = Z_OK;
    CZipper zip;
    //-------------- compress
	if (nErr == Z_OK)
	{
		long nDestLen = 1024;
		unsigned char* pDest = new unsigned char[nDestLen];
		nErr = zip.Cmpress( pDest, &nDestLen, pSrcBuf, nSrcLen );
		if ( nErr == Z_BUF_ERROR ) {
		  delete[] pDest;
		  pDest = new unsigned char[nDestLen]; // enough room now
		  nErr = zip.Cmpress( pDest, &nDestLen, pSrcBuf, nSrcLen );
		  if (nErr == Z_OK) {
			pSrcBuf = pDest;
			nSrcLen = nDestLen;
		  }
		}
	}
    //-------------- uncompress
	if (nErr == Z_OK)
	{
		long nDestLen = 1024;
		unsigned char* pDest = new unsigned char[nDestLen];
		nErr = zip.Uncompress( pDest, &nDestLen, pSrcBuf, nSrcLen );
		if ( nErr == Z_BUF_ERROR ) {
		  delete[] pDest;
		  pDest = new unsigned char[nDestLen]; // enough room now
		  nErr = zip.Uncompress( pDest, &nDestLen, pSrcBuf, nSrcLen );
		  if (nErr == Z_OK) {
			pSrcBuf = pDest;
			nSrcLen = nDestLen;
		  }
		}
	}
*/

/* inside buffer sample - how to use CZipper class 

    BYTE pbSrc[] = "Learn from yesterday, live for today, hope for tomorrow. The important thing is not to stop questioning.\n";
    BYTE *pSrcBuf = pbSrc;
    long nSrcLen = (long)strlen( (char*)pbSrc )+1; // include terminating NULL
    int nErr = Z_OK;
    CZipper zip, unzip;
    //-------------- compress
	if (nErr == Z_OK)
	{
		nErr = zip.Cmpress( pSrcBuf, nSrcLen );
	}
    //-------------- uncompress
	if (nErr == Z_OK)
	{
		nErr = unzip.Uncompress( zip.Buffer(), zip.DataSize() );
	}
*/

#endif // _ZIPPER_H
