#include "LayDes.h"

static inline int sMatchLen(const char *a, const char *al, const char *b, const char *bl)
{
	const char *q = a;
	if(al - a < bl - b)
		while(a < al && *a == *b) {
			a++;
			b++;
		}
	else
		while(b < bl && *a == *b) {
			a++;
			b++;
		}
	return a - q;
}

unsigned HashPos(byte *s)
{
	return s[0] + (s[1] << 8) + (s[2] << 16) + (s[3] << 24);
}

void DiffPacker::SetMaster(const String& m)
{
	master = m;
	mapn = master.GetLength() | 5;
	prev.Alloc(master.GetLength(), -1);
	map.Alloc(mapn, -1);
	for(int i = master.GetLength() - 4; i >= 0; --i) {
		int hp = HashPos((byte *)~master + i) % mapn;
		prev[i] = map[hp];
		map[hp] = i;
	}
}

String DiffPacker::Pack(const String& data)
{
	String result;
	const char *masterend = master.End();
	const char *dataend = data.End();
	const char *s = data;
	while(s < dataend) {
		int pos = 0;
		int ml = 0;
		int p = map[HashPos((byte *)s) % mapn];
		int ni = INT_MAX;
		while(p >= 0 && ml < ni) {
			int l = sMatchLen(s, dataend, ~master + p, masterend);
			if(l > ml) {
				pos = p;
				ml = l;
			}
			p = prev[p];
			ni >>= 1;
		}
		if(ml > 65535)
			ml = 65535;
		if(pos <= 255 && ml <= 255 && ml > 3) {
			result.Cat(0xfb);
			result.Cat(pos);
			result.Cat(ml);
			s += ml;
		}
		else
		if(pos <= 65535 && ml <= 255 && ml > 4) {
			result.Cat(0xfc);
			result.Cat(LOBYTE(pos));
			result.Cat(HIBYTE(pos));
			result.Cat(ml);
			s += ml;
		}
		else
		if(pos <= 65535 && ml > 5) {
			result.Cat(0xfd);
			result.Cat(LOBYTE(pos));
			result.Cat(HIBYTE(pos));
			result.Cat(LOBYTE(ml));
			result.Cat(HIBYTE(ml));
			s += ml;
		}
		else
		if(ml > 7) {
			result.Cat(0xfe);
			result.Cat(LOBYTE(LOWORD(pos)));
			result.Cat(HIBYTE(LOWORD(pos)));
			result.Cat(LOBYTE(HIWORD(pos)));
			result.Cat(HIBYTE(HIWORD(pos)));
			result.Cat(LOBYTE(ml));
			result.Cat(HIBYTE(ml));
			s += ml;
		}
		else {
			if((byte)*s >= 0xfb)
				result.Cat(0xff);
			result.Cat(*s++);
		}
	}
	result.Shrink();
	return result;
}

String DiffPack(const String& master, const String& data)
{
	DiffPacker p;
	p.SetMaster(master);
	return p.Pack(data);
}

String DiffUnpack(const String& master, const String& pack)
{
	String result;
	const char *s = pack;
	while(s < pack.End()) {
		int c = (byte)*s++;
		if(c == 0xfb) {
			ASSERT(s <= pack.End() - 2);
			byte p = (byte)*s++;
			byte l = (byte)*s++;
			result.Cat(~master + p, l);
		}
		else
		if(c == 0xfc) {
			ASSERT(s <= pack.End() - 3);
			byte pl = (byte)*s++;
			byte ph = (byte)*s++;
			byte ln = (byte)*s++;
			result.Cat(~master + MAKEWORD(pl, ph), ln);
		}
		else
		if(c == 0xfd) {
			ASSERT(s <= pack.End() - 4);
			byte pl = (byte)*s++;
			byte ph = (byte)*s++;
			byte ll = (byte)*s++;
			byte lh = (byte)*s++;
			result.Cat(~master + MAKEWORD(pl, ph), MAKEWORD(ll, lh));
		}
		else
		if(c == 0xfe) {
			ASSERT(s <= pack.End() - 5);
			byte pll = (byte)*s++;
			byte plh = (byte)*s++;
			byte phl = (byte)*s++;
			byte phh = (byte)*s++;
			byte ll = (byte)*s++;
			byte lh = (byte)*s++;
			result.Cat(~master + MAKELONG(MAKEWORD(pll, plh), MAKEWORD(phl, phh)),
			           MAKEWORD(ll, lh));
		}
		else
		if(c == 0xff)
			result.Cat(*s++);
		else
			result.Cat(c);
	}
	return result;
}
