long __regargs	memwrite(char *,long);
void		compexit(void);
long		compinit(void);
long __regargs	compfiles(unsigned char *,long,unsigned char *,long);
long		compress(void);
void __regargs	output(long);
void		cl_block(void);
void		cl_hash(long);

#define BITS 12

#if BITS == 16
#	define HSIZE	69001 
#endif
#if BITS == 15
#	define HSIZE	35023 
#endif
#if BITS == 14
#	define HSIZE	18013 
#endif
#if BITS == 13
#	define HSIZE	9001 
#endif
#if BITS <= 12
#	define HSIZE	5003 
#endif

#define BLOCK_MASK	0x80
#define INIT_BITS	9 
#define htabof(i)	htab[i]
#define codetabof(i)	codetab[i]
#define MAXCODE(n_bits)	((1 << (n_bits)) - 1)
#define CHECK_GAP	10000
#define FIRST		257 
#define CLEAR		256 

#define myfgetc()	(reallyread++ >= watermark) ? EOF : *sect++
#define absmin(a,b)	((a > b) ? b : a)

long n_bits,maxcode,*htab,hsize,free_ent,clear_flg,ratio,checkpoint,offset,in_count,bytes_out,reallywritten,watermark,buffsize,reallyread;

unsigned char *buffstart,*sect;
unsigned short *codetab;

char buf[BITS];

const unsigned char lmask[9] = {0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,0xC0,0x80,0x00};
const unsigned char rmask[9] = {0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF};

long __regargs
memwrite(mem,size)
register char *mem;
register long size;
{
	register long byteswritten = 0;

	while(size-- > 0)
	{
		if(reallywritten++ < buffsize)
		{
			*buffstart++ = *mem++;
			byteswritten++;
		}
		else
			return(byteswritten);
	}

	return(byteswritten);
}

void
compexit()
{
	if(htab)
		FreeMem(htab,HSIZE * sizeof(long));

	if(codetab)
		FreeMem(codetab,HSIZE * sizeof(unsigned short));

	htab = NULL;
	codetab = NULL;
}

long
compinit()
{
	if(!(htab = (long *)AllocMem(HSIZE * sizeof(long),0)))
		return(FALSE);

	if(!(codetab = (unsigned short *)AllocMem(HSIZE * sizeof(unsigned short),0)))
	{
		FreeMem(htab,HSIZE * sizeof(long));

		htab = NULL;

		return(FALSE);
	}

	return(TRUE);
}

void __regargs
output(code)
long code;
{
	long r_off = offset, bits = n_bits;
	register char *bp = buf;

	if(code >= 0)
	{
		bp += (r_off >> 3);
		r_off &= 7;

		*bp = (*bp & rmask[r_off]) | (code << r_off) & lmask[r_off];
		bp++;

		bits -= (8 - r_off);
		code >>= 8 - r_off;

		if(bits >= 8)
		{
			*bp++ = code;
			code >>= 8;
			bits -= 8;
		}

		if(bits)
			*bp = code;

		offset += n_bits;

		if(offset == (n_bits << 3))
		{
			bp = buf;
			bits = n_bits;
			bytes_out += bits;

			do
			{
				if(reallywritten++ < buffsize)
					*buffstart++ = *bp++;
			}
			while(--bits);

			offset = 0;
		}

		if(free_ent > maxcode || (clear_flg > 0))
		{
			if(offset > 0)
			{
				memwrite(buf,n_bits);
				bytes_out += n_bits;
			}

			offset = 0;

			if(clear_flg)
			{
				maxcode = MAXCODE(n_bits = INIT_BITS);
				clear_flg = 0;
			}
			else
			{
				n_bits++;

				if(n_bits == BITS)
					maxcode = (1 << BITS);
				else
					maxcode = MAXCODE(n_bits);
			}
		}
	}
	else
	{
		if(offset > 0)
			memwrite(buf,(offset + 7) / 8);

		bytes_out += (offset + 7) / 8;
		offset = 0;
	}
}

void
cl_block()
{ 
	register long rat;

	checkpoint = in_count + CHECK_GAP;

	if(in_count > 0x007FFFFF)
	{
		rat = bytes_out >> 8;

		if(rat == 0)
			rat = 0x7FFFFFFF;
		else
			rat = in_count /rat;
	}
	else
		rat = (in_count << 8) / bytes_out; 

	if(rat > ratio)
		ratio = rat;
	else
	{
		ratio = 0;

		cl_hash((long)hsize);

		free_ent = FIRST;
		clear_flg = 1;

		output((long)CLEAR);
	}
}

void
cl_hash(hashsize) 
long hashsize;
{
	register long *htab_p = &htab[hashsize],i;
	register unsigned short j;

	i = hashsize - 16;

	do
	{
		for(j = 16 ; j > 0 ; j--)
			*(htab_p - j) = -1;

		htab_p -= 16;
	}
	while((i -= 16) >= 0);

	for(i += 16 ; i > 0 ; i--)
		*--htab_p = -1;
}

long __regargs
compfiles(from,sectsize,to,maxsize)
unsigned char *from,*to;
long sectsize,maxsize;
{
	sect		= from;
	watermark	= sectsize;
	buffstart	= to;
	buffsize	= maxsize;
	reallywritten	= 0;
	reallyread	= 0;

	hsize		= HSIZE;
	free_ent	= 0;
	clear_flg	= 0;
	ratio		= 0;
	checkpoint	= CHECK_GAP;
	in_count	= 1; 

	if(sectsize < (1 << 12))
		hsize = absmin(5003, HSIZE);
	else if(sectsize < (1 << 13))
		hsize = absmin(9001, HSIZE);
	else if(sectsize < (1 << 14))
		hsize = absmin(18013, HSIZE);
	else if(sectsize < (1 << 15))
		hsize = absmin(35023, HSIZE);
	else if(sectsize < 47000)
		hsize = absmin(50021, HSIZE);

	compress();

	return(reallywritten);
}

long
compress()
{
	long fcode,i = 0,c,ent,disp,hsize_reg,hshift = 0;

	offset		= 0;
	bytes_out	= 3; 
	clear_flg	= 0;
	ratio		= 0;
	in_count	= 1;

	checkpoint = CHECK_GAP;
	maxcode = MAXCODE(n_bits = INIT_BITS);

	free_ent = ((BLOCK_MASK) ? FIRST : 256);

	ent = myfgetc();

	for(fcode = (long)hsize ; fcode < 65536L ; fcode *= 2L)
		hshift++;

	hshift = 8 - hshift; 
	hsize_reg = hsize;

	cl_hash((long)hsize_reg); 

	while((c = myfgetc()) != EOF)
	{
		in_count++;

		fcode = (long)(((long) c << BITS) + ent);

		i = ((c << hshift) ^ ent); 

		if(htabof(i) == fcode)
		{
			ent = codetabof(i);
			continue;
		}
		else
			if((long)htabof(i) < 0) 
				goto nomatch;

		disp = hsize_reg - i; 

		if(i == 0)
			disp = 1;

probe:		if((i -= disp) < 0)
			i += hsize_reg;

		if(htabof(i) == fcode)
		{
			ent = codetabof(i);
			continue;
		}

		if((long)htabof(i) > 0)
			goto probe;

nomatch:	output((long)ent);

		ent = c;

		if(free_ent < (1 << BITS))
		{
			codetabof(i) = free_ent++; 
			htabof(i) = fcode;
		}
		else
			if((long)in_count >= checkpoint && BLOCK_MASK)
				cl_block();
	}

	output((long)ent);
	output((long)-1);

	return(bytes_out);
}
