/*
This product contains certain software code or other information
("AT&T Software") proprietary to AT&T Corp. ("AT&T").  The AT&T
Software is provided to you "AS IS".  YOU ASSUME TOTAL RESPONSIBILITY
AND RISK FOR USE OF THE AT&T SOFTWARE.  AT&T DOES NOT MAKE, AND
EXPRESSLY DISCLAIMS, ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND
WHATSOEVER, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, WARRANTIES OF
TITLE OR NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS, ANY
WARRANTIES ARISING BY USAGE OF TRADE, COURSE OF DEALING OR COURSE OF
PERFORMANCE, OR ANY WARRANTY THAT THE AT&T SOFTWARE IS "ERROR FREE" OR
WILL MEET YOUR REQUIREMENTS.

Unless you accept a license to use the AT&T Software, you shall not
reverse compile, disassemble or otherwise reverse engineer this
product to ascertain the source code for any AT&T Software.

(c) AT&T Corp. All rights reserved.  AT&T is a registered trademark of AT&T Corp.

***********************************************************************

History:

      24/11/99  - initial release by Hartmut Liefke, liefke@seas.upenn.edu
                                     Dan Suciu,      suciu@research.att.com
*/

//**************************************************************************
//**************************************************************************

// This module contains the interface to the ZLIB (either gzip or bzip)
// libary

#include "stdafx.h"

#include <bzlib.h>
#include <zlib.h>

//************************************************************************

/* bzalloc and zalloc only differ in the declaration line !!! */
// Function (b)zalloc is called by the (b)zlib library to allocate memory
void *bzalloc(void *opaque,int items,int size)
{
#ifdef ZALLOC_COUNT
   void *ptr=mymalloc(items*size+4);
#else
   void *ptr=mymalloc(items*size);
#endif
   if(ptr==NULL)
      ExitNoMem();

//   printf("zalloc : %lu * %lu ==> %lX\n",items,size,ptr);

#ifdef ZALLOC_COUNT
   session->allocsize+=size;
   *(unsigned *)ptr=size;
   return (void *)(((char *)ptr)+4);
#else
   return (void *)ptr;
#endif
}

void *zalloc(void *opaque,unsigned items,unsigned size)
{
#ifdef ZALLOC_COUNT
   void *ptr=mymalloc(items*size+4);
#else
   void *ptr=mymalloc(items*size);
#endif
   if(ptr==NULL)
      ExitNoMem();

//   printf("zalloc : %lu * %lu ==> %lX\n",items,size,ptr);

#ifdef ZALLOC_COUNT
   session->allocsize+=size;
   *(unsigned *)ptr=size;
   return (void *)(((char *)ptr)+4);
#else
   return (void *)ptr;
#endif
}

// Function zfree is called by the zlib library to release memory

void zfree(void *opaque,void *ptr)
{
#ifdef ZALLOC_COUNT
   session->allocsize-=*((unsigned *)ptr-1);
   tryfree(ptr-4);
#else
   tryfree(ptr);
#endif
}

// The compressor part

Compressor::Compressor(Settings *se, Output *myoutput)
   // The constructor
{
	zipper = Zipper::NewZipper(se->use_bzip, se->compressidx);
   if (se->use_bzip == XMILL_GPC_PPMDI) {
      ((PPMDI*)zipper)->setData(se->session->ppmdidata);
   }
   isinitialized = FALSE;
   coutput = myoutput;
};

Compressor::~Compressor()
   // The deconstructor
{
	if(isinitialized) {
      // We finish compression, if there has been some data in the queue
		trydel (zipper);
      isinitialized = FALSE;
   }
}

void Compressor::CompressMemStream(MemStreamer *memstream)
   // Reads the data from 'memstream' and sends
   // it to the compressor
{
   MemStreamBlock *curblock = memstream->GetFirstBlock();
   int saveavail;
	int ao, ai;
	bool empty;

   // Any data there? 
   if(memstream->GetSize()==0)
      return;

	zipper->setNextOut(coutput->GetBufPtr(zipper->getAvailOutPtr()));
	zipper->setNextIn(curblock->data);
	ao = zipper->getAvailOut();

   // If there is no space in the output buffer, we need to flush
   if(ao == 0)
   {
      // If the output buffer is full, we flush
      coutput->Flush();

      // We get the next piece of output buffer that will be filled up
		zipper->setNextOut(coutput->GetBufPtr(zipper->getAvailOutPtr()));
   }

//   state.avail_in=curblock->cursize;

   // If we haven't initialized yet, we do that now
   if (!isinitialized) {
		zipper->initCompress();
		zipper->resetTotals();
      isinitialized = TRUE;
   }

   do {
      // Let's ?? the input block to 'curblock'
		zipper->setNextIn(curblock->data);
		zipper->setAvailIn(curblock->cursize);
#ifdef _DEBUG
//		printf("saving %ld Bytes\n", curblock->cursize);
#endif
		ai = zipper->getAvailIn();
      // As long as we still have data in the input block, we continue
      while(ai>0)
      {
			saveavail = zipper->getAvailOut();
			if(zipper->doCompress(BZ_RUN) != BZ_RUN_OK) {
				throw new XMillException (XMILL_ERR_ZLIB, "Error while compressing container!");
			}
			ao = zipper->getAvailOut();
			empty = (  zipper->getAvailIn() == 0
						&& zipper->getAvailOut() > 0);

         // We tell the output stream that 'saveavail-state.avail_out' bytes
         // are now ready for output
         coutput->SaveBytes(saveavail-ao);

         // We get the next piece of output buffer that will be filled up
			zipper->setNextOut(coutput->GetBufPtr(zipper->getAvailOutPtr()));

			if(empty)
            // Is the input buffer is empty ? ==> We go to next block
            break;

         // If the output buffer is full, we flush
         coutput->Flush();

         // We get the next piece of output buffer that will be filled up
			zipper->setNextOut(coutput->GetBufPtr(zipper->getAvailOutPtr()));

			ai = zipper->getAvailIn();
      }

      // There is no more input data available ==> We go to next block in MemStreamer
      curblock=curblock->next;
   }
   while(curblock!=NULL);
}

void Compressor::CompressData(unsigned char *ptr,unsigned len)
   // Compresses the data at position 'ptr' of length 'len'
{
   int saveavail;
	int nowavail;
	bool empty;

   // Let's get some space in the output buffer
	zipper->setCompPtrs(
		coutput->GetBufPtr(zipper->getAvailOutPtr()),
		(char*)ptr,
		len);

   // If we haven't initialized the compressor yet, then let's do it now
   if(!isinitialized)
   {
		zipper->initCompress();
		zipper->resetTotals();
      isinitialized = TRUE;
   }

   do
   {
      // The actual compression
		saveavail = zipper->getAvailOut();
	   if(zipper->doCompress(BZ_RUN) != BZ_RUN_OK) {
			throw new XMillException (XMILL_ERR_ZLIB, "Error while compressing container!");
		}
		nowavail = zipper->getAvailOut();
		empty =  (zipper->getAvailIn() == 0)
				&& (zipper->getAvailOut() > 0);
      coutput->SaveBytes(saveavail-nowavail);
         // We tell the output stream that 'saveavail-state.avail_out' bytes
         // are now ready for output

      if(empty)
         // Is the input buffer is empty ? ==> We go to next block
         break;

      // If the output buffer is full, we flush
      coutput->Flush();

      // We get the next piece of output buffer that will be filled up
		zipper->setNextOut(coutput->GetBufPtr(zipper->getAvailOutPtr()));
   }
   while(1);
}

void Compressor::FinishCompress(unsigned long *uncompressedsize,unsigned long *compressedsize)
   // Finishes the compression and stores the input data size and
   // the output data size in 'uncompressedsize' and 'compressedsize'
{
   char err;
   int   saveavail;
	int ti, to;

   do
   {
      // Let's get more space in the output buffer
		zipper->setNextOut(coutput->GetBufPtr(zipper->getAvailOutPtr()));
		saveavail = zipper->getAvailOut();
		err = zipper->doCompress();
	   coutput->SaveBytes(saveavail - zipper->getAvailOut());
		if(err==BZ_STREAM_END)
			break;
		if(err!=BZ_FINISH_OK && err!=BZ_OK) {
			throw new XMillException (XMILL_ERR_ZLIB, "Error while compressing container!");
		}

      // (B)Z_OK means that the output buffer is full ! ==> We flush
      coutput->Flush();
   } while(1);

   // Let's store the input and output size
	ti = zipper->getTotalIn();
	to = zipper->getTotalOut();
	zipper->resetTotals();
   if(uncompressedsize!=NULL) *uncompressedsize = ti;
   if(compressedsize!=NULL)   *compressedsize   = to;

   // Finally, we release the internal memory
	zipper->resetCompress();
	if (zipper->needsReInit()) {
	   // bzip must be reinitialized
	   isinitialized = FALSE;
	}
}

//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
Uncompressor::Uncompressor(Session *s) 
{  
	session = s;
	zipper = Zipper::NewZipper(
		session->settings->use_bzip, 
		session->settings->compressidx, 
		false);
   if (s->settings->use_bzip == XMILL_GPC_PPMDI) {
      ((PPMDI*)zipper)->setData(session->ppmdidata);
   }
	isinitialized = FALSE;  
}

Uncompressor::~Uncompressor() 
{  
	trydel (zipper);
}

char Uncompressor::Uncompress(Input *input,unsigned char *dataptr,unsigned long *len)
   // Decompresses the data from 'input' and stores
   // the result in 'dataptr'. It decompresses at most *len
   // bytes. Afterwards, '*len' is set to the actual number
   // of bytes uncompressed.
   // The function returns TRUE if the output buffer is full and
   // there is more data to read. Otherwise, the function returns FALSE.
{
   int   save_in;
	int retcode, ai, to;

   // We haven't initialized the object yet, we do that now
   if(!isinitialized) {
		zipper->initUncompress();
	   isinitialized = TRUE;
   }

	// Let's remember how much space we have in the output ...
	// ... and we get the piece of data from the input
	zipper->setUncompPtrs(
			(char *)dataptr, 
			*len, 
			input->GetCurBlockPtr(zipper->getNextInPtr()));

   do
   {
		// We save the amount of input data that is available
		// This will be used to compute how much input data was
		// decompressed
		if ((save_in = zipper->getAvailIn()) == 0) {
			// no data available, jump out
			*len = zipper->getTotalOut();
			return FALSE;
      }
		retcode = zipper->doUncompress();
		ai = zipper->getAvailIn();
		to = zipper->getTotalOut();

		switch(retcode) {
				// Did we finish completely?
			case BZ_STREAM_END:
				// We skip over the amount of data that was decompressed
				input->SkipData(save_in-ai);

				// Let's store the overall amount of "decompressed" data.
				*len=to;

				// Let's finish the decompression entirely
				zipper->endUncompress();
				if (zipper->needsReInit()) {
					// Only for BZIP, we need to reinitialize
					isinitialized = FALSE;
				}
				return FALSE;   // We reached the end

		case BZ_OK:
				// Did we do the decompression correctly?
				// => Let's go to the next piece of data
				break;

			default:
				// In all other cases, we have an error
				throw new XMillException (XMILL_ERR_ZLIB, "Error while uncompressing container!");
      }

      // Skip the input data that was decompressed
      input->SkipData(save_in-ai);

		int avo = zipper->getAvailOut();

		// Let's get the next input data block
		input->RefillAndGetCurBlockPtr(
			zipper->getNextInPtr(),
			zipper->getAvailInPtr());

      if (avo == 0) {
			return TRUE;
      }
	} while(1);
}
