/*
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:

      03.01.2003  Hedzer Westra  Initial coding
*/

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

// This module implements an interface to PPMDI, an implementation of a variant
// of the PPMD+ protocol done by Dmitry Shkarin and then upgraded by James Cheney
// for XMLPPM

#include "stdafx.h"
#include "Model.h"
#include "Coder.h"

/* find an int in piece of binary memory, like strstr() */
static char *memint(char *mem, int len, int num)
{
	int i = 0, ilen = sizeof(num);

	while (i <= len-ilen) {
		if (!memcmp(&mem[i], &num, ilen))
			return &mem[i];
		i++;
	}

	return NULL;
}

/* (de)constructors */
PPMDI::PPMDI(int idx, bool iscomp): Zipper(idx, iscomp)
{
   /* uncompress needs the same idx setting, so store it in the header */
   if (idx >= XMILL_PPMDI_IDXS) {
      idx = XMILL_PPMDI_IDX;
   }
   start = XMILL_HEADER_PPMDI_START + (idx << 24);
   end = XMILL_HEADER_PPMDI_END;

	PPMDIstate.avail_out = 65536;
	resetTotals();

   enc = NULL;
   dec = NULL;

   dst = NULL;
   endptr = NULL;
   src = NULL;

   size = ppmsettings[idx].size;       // MB of window size
   order = ppmsettings[idx].order;     // # look-back chars, 0-16

   data = NULL;

	initialized = false;
}

PPMDI::~PPMDI() 
{
   trydel (enc);
   trydel (dec);
}

void PPMDI::setData(PPMDIData *d)
{
   data = d;
   data->getAlloc(size);
   data->setFp(this);
}

/* compression */
void PPMDI::initCompress() 
{
   trydel (enc);
   enc = new PPM_ENCODER(size, order, data);
   data->ariInitEncoder();
	initialized = false;
}

void PPMDI::setDst(unsigned char *s)
{
   dst = s;
}

void PPMDI::_putc(char c)
{
   if (dst) {
      dst[0] = c;
      dst++;
   }
}

int PPMDI::compress(unsigned char *Dst, unsigned char *src, int inlen, int *inused, int outlen, int *outused)
{
   unsigned char *endptr = Dst + outlen;
   int ret = BZ_OK, j = 0;

   setDst(Dst);
   for (j = 0; j<inlen; j++) {
      enc->EncodeChar((int)src[j]);
      if (dst >= endptr - 8) {
         /* pause compression when we approach the PPMDi safety margin; max. 8 Bytes can be saved in a single 
            EncodeChar() action */
         ret = BZ_STREAM_END;
         j++;
         goto cleanup;
      }
   }

cleanup:
   *inused = j;
   *outused = dst - Dst;
	return ret;
}

int PPMDI::doCompress(int flag)
{
   int offset = 0, out, in, ret;
   unsigned char *curdst = dst;

	if (PPMDIstate.avail_in == 0) {
      /* set destination address */
      setDst((unsigned char*)PPMDIstate.next_out);
      curdst = dst;
      /* flush last bytes */
      enc->EncodeChar(-1);
      data->ariFlushEncoder();
		/* store end-of-stream marker */
		memcpy(dst, &end, sizeof(end));
      if ((offset = dst - curdst + sizeof(end)) < 0) {
         throw new XMillException(XMILL_ERR_FATAL, "ppmdi compressor length negative!");
      }
		PPMDIstate.total_out += offset;
		PPMDIstate.avail_out -= offset;
      /* prevent any other Bytes from being written */
      setDst(NULL);
		return BZ_STREAM_END;
	}

	if (!initialized) {
		/* store begin-of-stream marker */
		offset = sizeof(start);
		PPMDIstate.avail_out -= offset;
		PPMDIstate.total_out += offset;
		memcpy(PPMDIstate.next_out, &start, offset);
		initialized = true;
	}

   /* do we need to handle the return value 'ret'? */
	ret = compress((unsigned char*)PPMDIstate.next_out+offset, (unsigned char*)PPMDIstate.next_in, 
            PPMDIstate.avail_in, &in, PPMDIstate.avail_out, &out);

   if (out < 0) {
      throw new XMillException(XMILL_ERR_FATAL, "ppmdi compressor length negative!");
   }

   if (/*PPMDIstate.avail_*/in > PPMDIstate.avail_out) {
		PPMDIstate.next_in += /*PPMDIstate.avail_*/in;
   }

	PPMDIstate.total_in += /*PPMDIstate.avail_*/in;
	PPMDIstate.total_out += out;
	PPMDIstate.avail_in -= in;
	PPMDIstate.avail_out -= out;

	if (flag == -1)
		return BZ_OK;
	else
		return BZ_RUN_OK;
}

void PPMDI::endCompress() 
{
	initialized = false;
}

/* decompression */
void PPMDI::initUncompress() 
{
   trydel (dec);
   dec = new PPM_DECODER(size, order, data);
   resetTotals();
   resetsrc = false;
	initialized = false;
}

int PPMDI::_getc()
{
   if (!src || !endptr || src >= endptr) {
      return EOF;
   }
   int i = (int)src[0];
   src++;
   return i;
}

void PPMDI::setSrc(unsigned char *s, int len)
{
   if (s) {
      origsrc = src = s;
   }
   endptr = origsrc+len;
}

int PPMDI::uncompress(unsigned char *dst, unsigned char *Src, int inlen, int outlen, int *outused, int *inused)
{
   int i;
   int j = 0, ret = BZ_OK;

   setSrc(resetsrc ? Src : NULL, inlen);
   resetsrc = false;
   while (src <= endptr && j<outlen) {
      if ((i = dec->DecodeChar()) == EOF) {
         ret = BZ_STREAM_END;
         goto cleanup;
      } else if (i == EOB) {
         /* more data is expected, BZ_OK signals this */
         resetsrc = true;
         goto cleanup;
      }
      dst[j++] = (unsigned char)i;
   }

cleanup:
   *outused = j;
   *inused = inlen - (int)endptr + (int)src;
   return ret;
}
 
int PPMDI::doUncompress()
{
	int offset = 0, off = 0, ret = BZ_OK;
	char *headerptr = NULL;
   int outlen = 0, inlen = 0, realinlen = 0;

	if (PPMDIstate.avail_in == 0)
		return BZ_STREAM_END;

	if (!initialized) {
		/* check for begin-of-stream header */
		off = sizeof(start);
		if (!memcmp(PPMDIstate.next_in, &start, off)) {
			initialized = true;
			PPMDIstate.avail_in -= off;
      	PPMDIstate.total_in += off;
		} else {
			off = 0;
		}
		/* init decoder */
      setSrc((unsigned char*)PPMDIstate.next_in+off, PPMDIstate.avail_in);
      data->ariInitDecoder();
	}

	/* check for end-of-stream marker */
   inlen = PPMDIstate.avail_in;
	if ((headerptr = memint(PPMDIstate.next_in+off, inlen, end))) {
		/* yep, found footer somewhere, don't read any further */
		inlen = headerptr-(PPMDIstate.next_in+off);
      ret = BZ_STREAM_END;
	}

   /* uncompress! */
   if (uncompress((unsigned char*)PPMDIstate.next_out, (unsigned char*)PPMDIstate.next_in+off, 
                  inlen, PPMDIstate.avail_out, &outlen, &realinlen) == BZ_STREAM_END) {
      ret = BZ_STREAM_END;
   }

   /* if inlen != realinlen, something strange happened.. */

	PPMDIstate.total_in += inlen;
	PPMDIstate.total_out += outlen;
	PPMDIstate.next_out += outlen;
	PPMDIstate.avail_in -= inlen;
	PPMDIstate.avail_out -= outlen;

	/* check for end-of-stream marker */
	offset = sizeof(end);
	if (!memcmp(PPMDIstate.next_in+off+inlen, &end, offset)) {
		/* yep, found footer */
		PPMDIstate.avail_in -= offset;
		PPMDIstate.total_in += offset;
		initialized = false;
	}
	return ret;
}

void PPMDI::endUncompress() 
{
	initialized = false;
}

bool PPMDI::needsReInit()
{
	return true;
}

/* pointer administration */
void PPMDI::setCompPtrs(char *nextout, char* nextin, int availin)
{
	PPMDIstate.next_out = nextout;
	PPMDIstate.next_in = nextin;
	PPMDIstate.avail_in = availin;
}

void PPMDI::setUncompPtrs(char *nextout, int availout, int availin)
{
	PPMDIstate.next_out = nextout;
	PPMDIstate.avail_out = availout;
	PPMDIstate.avail_in = availin;
}

void PPMDI::setNextOut(char* nextout)
{
	PPMDIstate.next_out = nextout;
}

void PPMDI::setNextIn(char* nextin)
{
	PPMDIstate.next_in = nextin;
}

void PPMDI::setAvailIn(int availin)
{
	PPMDIstate.avail_in = availin;
}

char **PPMDI::getNextInPtr() 
{
	return (char **)&(PPMDIstate.next_in);
}

int PPMDI::getAvailIn()
{
	return PPMDIstate.avail_in;
}

int* PPMDI::getAvailInPtr()
{
	return (int*)&PPMDIstate.avail_in;
}

int PPMDI::getAvailOut()
{
	return PPMDIstate.avail_out;
}

int* PPMDI::getAvailOutPtr()
{
	return (int*)&PPMDIstate.avail_out;
}

int PPMDI::getTotalOut()
{
	return PPMDIstate.total_out;
}

int PPMDI::getTotalIn()
{
	return PPMDIstate.total_in;
}

void PPMDI::resetTotals()
{
	PPMDIstate.total_in = 0;
	PPMDIstate.total_out = 0;
}
