//	utf-8

/*	shrink and expand

	Copyright  (c)	Günter Woigk   1991-1999
  					mailto:kio@little-bat.de

	This file is free software

 	This program 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.  

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission. The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.
*/


#include	"config.h"
#include	"shrink.h"



/* ----	Fast compression of simple byte repetitions -------------------------------------

		note:	resulting 'compressed' data may be slightly longer!

		return:
				errno = ok             && return value = compressed data size
				errno = buffertoosmall && return value = compressed data size so far

sheme:

	the compressed data consists of repetitions of 4 possible sequences.
	
	every sequence starts with a flag byte, which includes the size of the 
	following (expanded) data. if the size does not fit into the flag byte,
	it is continued in an optional 2nd and maybe in an optional 3rd byte.
	
	after that, no more data is needed or a single byte to repeat follows or
	the uncompressible data follows:


	flag		%TTnnnnnn		type  = TT
								count = nnnnnn = 0 or 1..63
	
	cnt1		$NN				only present if flag.count = 0
								count = 64..255 or 0...63
								
	cnt2		$NN				only present if cnt1 <= 63
								count = cnt1*256+cnt2
								
	data		depending on type:
	
	TT=00:		data[count]		uncompressed data:	verbatim copy of data 
	TT=01:		byte			compressed data:	c2=c1; c1=byte; data = count * c1
	TT=10:		----			compressed data:                    data = count * c1
	TT=11:		----			compressed data:	swap(c1,c2);    data = count * c1	
*/

size_t Shrink ( const uchar* sp, size_t ssz, uchar* dp, size_t dsz )
{
	const uchar* se = sp+ssz;	// source end
	uchar* de = dp+dsz;			// dest. buffer end
	uchar  c1 = 0;				// preset memory
	uchar  c2 = 32;				// ""
	uchar  c;
	size_t n;
	const uchar* s0;

	ClearError();

	while (sp<se)
	{
		s0 = sp;				// remember start
		c = *sp++;				// get 1st byte

//  	compressible sequence ?
//		yes if >=2 bytes and c==c1 or c==c2
//			or >=3 bytes

		while (sp<se && *sp==c) sp++;		// count same bytes
		n = sp-s0;

		if ( n>=2 && (n>=3||c==c1||c==c2) )
		{
			// check target space
			if (de-dp<4)
			{
				int bytes=1;				// flag byte
				bytes += n>63;				// opt. length byte
				bytes += n>255;				// opt. length byte
				bytes += c!=c1 && c!=c2;	// the byte
				if (bytes>de-dp) { SetError(buffertoosmall); break; }
			}

			*dp++ = ( c==c1 ? 0x80 : c==c2 ? 0xC0 : 0x40 ) + ( n<=63 ? n : 0 );
			if (n>63)
			{
				if (n<=255) *dp++ = n;
				else 
				{
					if (n>63*256+255) { n=63*256+255; sp=s0+n; }
					*dp++ = n>>8;	// n/256
					*dp++ = n;		// n%256
				}
			}
			if (c==c2)	{ Swap(c1,c2); }
			if (c!=c1)	{ *dp++ = c; c2=c1; c1=c; }
			continue;
		}
	
//  	uncompressible sequence
//		terminated by: compressible sequence

a:		while (sp<se && *sp!=c) c=*sp++;	// skip until sp[0]==sp[-1]

		if (sp<se)
		{
			if (c!=c1&&c!=c2&&sp[1]!=c) { sp++; goto a; }
			sp--;
		}
		n = sp-s0;

		// check target space
		if (de<dp+n+3)	
		{
			int bytes=1;				// flag byte
			bytes += n>63;				// additional length byte
			bytes += n>255;				// additional length byte
			bytes += MAX(n,63*256+255);	// the bytes
			if (bytes>de-dp) { SetError(buffertoosmall); break; }
		}

		// store them
		*dp++ = 0x00 + (n<=63?n:0);
		if (n>63)
		{
			if (n<=255) *dp++ = n;
			else 
			{
				if (n>63*256+255) { n=63*256+255; sp=s0+n; }
				*dp++ = n>>8;	// n/256
				*dp++ = n;		// n%256
			}
		}		

		memcpy (dp,s0,n);
		dp += n;
	}
	
	return dp-(de-dsz);
}	


/* ----	expand shrinked buffer --------------------------------------------
		returns size of expanded data
		sets/clears errno
*/
size_t Expand ( const uchar* sp, size_t ssz, uchar* dp, size_t dsz )
{
	const uchar* se = sp+ssz;	// source end
	uchar* de = dp+dsz;			// dest. buffer end
	uchar  c1 = 0;				// preset memory
	uchar  c2 = 32;				// ""
	uchar  c;
	size_t n;

	SetError(buffertoosmall);


	while (true)
	{
	
	// near end of source ?
		if (sp>=se-4)
		{
			if (sp==se) { ClearError(); break; }		// ok, finished !

		// make sure that we can read a complete header
			const uchar* p = sp;
			c = *p++;								// flag
			n = c&~0xc0;							// size
			if ( n==0 )
			{	if (p>=se || *p++<=63) p++;			// opt. cnt1 and opt. cnt2
			}	
			p += (c&0xc0)==0x40;	 				// the byte
			if (p>se) { x: SetError(endoffile); break;}	// error !
		}


	// read header
		c = *sp++;			// flag

		n = c&~0xc0;		// size
		if ( n==0 )
		{	n = *sp++;
			if (n<=63) n = n*256+*sp++;
		}
		

	// check destination space
		if (dp+n>de) break;	// errno = buffertoosmall


	// compressed data ?
		if (c & 0xc0)
		{
			if (c & 0x80)	// use old bytes ?
			{ 	if (c & 0x40) Swap(c1,c2); 	// use c2
			}
			else 			// get the byte
			{ 	c2=c1; c1=*sp++; 
			} 
		
			memset (dp, c1, n);
			dp+=n;
		}


	// uncompressed data
		else
		{
			if (sp+n>se) goto x;		// unexpected eof
			
			memcpy (dp,sp,n);
			dp+=n; sp+=n;
		}
	}
	

	return dp-(de-dsz);
}



/* ----	expand shrinked buffer --------------------------------------------
		returns size of expanded data
		sets/clears errno
		the expanded data is xored on the old destination buffer contents!
*/
size_t XorExpand ( const uchar* sp, size_t ssz, uchar* dp, size_t dsz )
{
	const uchar* se = sp+ssz;	// source end
	uchar* de = dp+dsz;			// dest. buffer end
	uchar  c1 = 0;				// preset memory
	uchar  c2 = 32;				// ""
	uchar  c;
	size_t n;

	SetError(buffertoosmall);


	while (true)
	{
	
	// near end of source ?
		if (sp>=se-4)
		{
			if (sp==se) { ClearError(); break; }		// ok, finished !

		// make sure that we can read a complete header
			const uchar* p = sp;
			c = *p++;								// flag
			n = c&~0xc0;							// size
			if ( n==0 )
			{	if (p>=se || *p++<=63) p++;			// opt. cnt1 and opt. cnt2
			}	
			p += (c&0xc0)==0x40;	 				// the byte
			if (p>se) { x: ForceError(endoffile); break;}	// error !
		}


	// read header
		c = *sp++;			// flag

		n = c&~0xc0;		// size
		if ( n==0 )
		{	n = *sp++;
			if (n<=63) n = n*256+*sp++;
		}
		

	// check destination space
		if (dp+n>de) break;	// errno = buffertoosmall


	// compressed data ?
		if (c & 0xc0)
		{
			if (c & 0x80)	// use old bytes ?
			{ 	if (c & 0x40) Swap(c1,c2); 	// use c2
			}
			else 			// get the byte
			{ 	c2=c1; c1=*sp++; 
			} 

			while (n) { *dp++ ^= c1; n--; }
		}


	// uncompressed data
		else
		{
			if (sp+n>se) goto x;		// unexpected eof
			
			while (n) { *dp++ ^= *sp++; n--; }
		}
	}
	

	return dp-(de-dsz);
}






















