#include "stdinc.h"
#include "whack.h"

enum
{
	DMaxFastLen	= 7,
	DBigLenCode	= 0x3c,		/* minimum code for large lenth encoding */
	DBigLenBits	= 6,
	DBigLenBase	= 1		/* starting items to encode for big lens */
};

static uchar lenval[1 << (DBigLenBits - 1)] =
{
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	3, 3, 3, 3, 3, 3, 3, 3,
	4, 4, 4, 4,
	5,
	6,
	255,
	255
};

static uchar lenbits[] =
{
	0, 0, 0,
	2, 3, 5, 5,
};

static uchar offbits[16] =
{
	5, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 12, 13
};

static ushort offbase[16] =
{
	0, 0x20,
	0x40, 0x60,
	0x80, 0xc0,
	0x100, 0x180,
	0x200, 0x300,
	0x400, 0x600,
	0x800, 0xc00,
	0x1000,
	0x2000
};

void
unwhackinit(Unwhack *uw)
{
	uw->err[0] = '\0';
}

int
unwhack(Unwhack *uw, uchar *dst, int ndst, uchar *src, int nsrc)
{
	uchar *s, *d, *dmax, *smax, lit;
	ulong uwbits, lithist;
	int i, off, len, bits, use, code, uwnbits, overbits;

	d = dst;
	dmax = d + ndst;

	smax = src + nsrc;
	uwnbits = 0;
	uwbits = 0;
	overbits = 0;
	lithist = ~0;
	while(src < smax || uwnbits - overbits >= MinDecode){
		while(uwnbits <= 24){
			uwbits <<= 8;
			if(src < smax)
				uwbits |= *src++;
			else
				overbits += 8;
			uwnbits += 8;
		}

		/*
		 * literal
		 */
		len = lenval[(uwbits >> (uwnbits - 5)) & 0x1f];
		if(len == 0){
			if(lithist & 0xf){
				uwnbits -= 9;
				lit = (uwbits >> uwnbits) & 0xff;
				lit &= 255;
			}else{
				uwnbits -= 8;
				lit = (uwbits >> uwnbits) & 0x7f;
				if(lit < 32){
					if(lit < 24){
						uwnbits -= 2;
						lit = (lit << 2) | ((uwbits >> uwnbits) & 3);
					}else{
						uwnbits -= 3;
						lit = (lit << 3) | ((uwbits >> uwnbits) & 7);
					}
					lit = (lit - 64) & 0xff;
				}
			}
			if(d >= dmax){
				snprint(uw->err, WhackErrLen, "too much output");
				return -1;
			}
			*d++ = lit;
			lithist = (lithist << 1) | (lit < 32) | (lit > 127);
			continue;
		}

		/*
		 * length
		 */
		if(len < 255)
			uwnbits -= lenbits[len];
		else{
			uwnbits -= DBigLenBits;
			code = ((uwbits >> uwnbits) & ((1 << DBigLenBits) - 1)) - DBigLenCode;
			len = DMaxFastLen;
			use = DBigLenBase;
			bits = (DBigLenBits & 1) ^ 1;
			while(code >= use){
				len += use;
				code -= use;
				code <<= 1;
				uwnbits--;
				if(uwnbits < 0){
					snprint(uw->err, WhackErrLen, "len out of range");
					return -1;
				}
				code |= (uwbits >> uwnbits) & 1;
				use <<= bits;
				bits ^= 1;
			}
			len += code;

			while(uwnbits <= 24){
				uwbits <<= 8;
				if(src < smax)
					uwbits |= *src++;
				else
					overbits += 8;
				uwnbits += 8;
			}
		}

		/*
		 * offset
		 */
		uwnbits -= 4;
		bits = (uwbits >> uwnbits) & 0xf;
		off = offbase[bits];
		bits = offbits[bits];

		uwnbits -= bits;
		off |= (uwbits >> uwnbits) & ((1 << bits) - 1);
		off++;

		if(off > d - dst){
			snprint(uw->err, WhackErrLen, "offset out of range: off=%d d=%zd len=%d nbits=%d",
				off, d - dst, len, uwnbits);
			return -1;
		}
		if(d + len > dmax){
			snprint(uw->err, WhackErrLen, "len out of range");
			return -1;
		}
		s = d - off;
		for(i = 0; i < len; i++)
			d[i] = s[i];
		d += len;
	}
	if(uwnbits < overbits){
		snprint(uw->err, WhackErrLen, "compressed data overrun");
		return -1;
	}

	len = d - dst;

	return len;
}
