/***********************************************************
	slide.c -- sliding dictionary with percolating update
***********************************************************/
#include "lharc.h"
#include "slidehuf.h"
#include "intrface.h"

#include "lhex_protos.h"

#define PERCOLATE  1
#define NIL        0

node *next = NULL;
int unpackable;
unsigned long origsize, compsize;
unsigned short dicbit;
unsigned short maxmatch;
unsigned long count;
unsigned short loc;
unsigned char *text;
int prev_char;

static unsigned long encoded_origsize;

#ifndef LHEXTRACT
static struct encode_option encode_define[2] = {
#if defined(__STDC__) || defined(AIX)
/* lh1 */
	{(void(*)())output_dyn,
	 (void(*)())encode_start_fix,
	 (void(*)())encode_end_dyn},
/* lh4, 5 */
	{(void(*)())output_st1,
	(void(*)())encode_start_st1,
	(void(*)())encode_end_st1}
#else
/* lh1 */
	{(int(*)())output_dyn,
	 (int(*)())encode_start_fix,
	 (int(*)())encode_end_dyn},
/* lh4, 5 */
	{(int(*)())output_st1,
	(int(*)())encode_start_st1,
	(int(*)())encode_end_st1}
#endif
};
#endif /* LHEXTRACT */

static struct decode_option decode_define[7] = {
/* lh1 */
	{decode_c_dyn, decode_p_st0, decode_start_fix},
/* lh2 */
	{decode_c_dyn, decode_p_dyn, decode_start_dyn},
/* lh3 */
	{decode_c_st0, decode_p_st0, decode_start_st0},
/* lh4 */
	{decode_c_st1, decode_p_st1, decode_start_st1},
/* lh5 */
	{decode_c_st1, decode_p_st1, decode_start_st1},
/* lzs */
	{decode_c_lzs, decode_p_lzs, decode_start_lzs},
/* lz5 */
	{decode_c_lz5, decode_p_lz5, decode_start_lz5}
};

static struct encode_option encode_set;
static struct decode_option decode_set;

static node pos, matchpos, avail,
		*position, *parent, *prev;
static int remainder, matchlen;
static unsigned char *level, *childcount;
static unsigned short dicsiz;
static unsigned short max_hash_val;
static unsigned short hash1, hash2;

#ifndef LHEXTRACT
int encode_alloc(int method)
{
  if (method == 1) {
    encode_set = encode_define[0];
    maxmatch = 60;
    dicbit = 12;
  } else {
    encode_set = encode_define[1];
    maxmatch = MAXMATCH;
    dicbit = 13;
  }
  while (1) {
    dicsiz = 1 << dicbit;
    max_hash_val = 3 * dicsiz + (dicsiz / 512 + 1) * UCHAR_MAX;
    text = (unsigned char *)malloc(dicsiz * 2 + maxmatch);
    level = (unsigned char *)malloc((dicsiz + UCHAR_MAX + 1) * sizeof(*level));
    childcount = (unsigned char *)malloc((dicsiz + UCHAR_MAX + 1) * sizeof(*childcount));
#if PERCOLATE
    position = (node *)malloc((dicsiz + UCHAR_MAX + 1) * sizeof(*position));
#else
    position = (node *)malloc(dicsiz * sizeof(*position));
#endif
    parent     = (node *)malloc(dicsiz * 2 * sizeof(*parent));
    prev       = (node *)malloc(dicsiz * 2 * sizeof(*prev));
    next       = (node *)malloc((max_hash_val + 1) * sizeof(*next));
    if (next == NULL || 
	method > 1 && alloc_buf() == NULL) {
      if (next) free(next);
      if (prev) free(prev);
      if (parent) free(parent);
      if (position) free(position);
      if (childcount) free(childcount);
      if (level) free(level);
      if (text) free(text);
    } else {
      break;
    }
    if (--dicbit < 12 )
      /* error(MEMOVRERR, NULL); */
      exit( 207 );
  }
  if (method == 5)
    method = dicbit - 8;
  return method;
}

static void init_slide(void)
{
	node i;

	for (i = dicsiz; i <= dicsiz + UCHAR_MAX; i++) {
		level[i] = 1;
#if PERCOLATE
			position[i] = NIL;  /* sentinel */
#endif
	}
	for (i = dicsiz; i < dicsiz * 2; i++) parent[i] = NIL;
	avail = 1;
	for (i = 1; i < dicsiz - 1; i++) next[i] = i + 1;
	next[dicsiz - 1] = NIL;
	for (i = dicsiz * 2; i <= max_hash_val; i++) next[i] = NIL;
	hash1 = dicbit - 9;
	hash2 = dicsiz * 2;
}

#define HASH(p, c) ((p) + ((c) << hash1) + hash2)

static /* inline */ node child(node q, unsigned char c)
	/* q's child for character c (NIL if not found) */
{
	node r;

	r = next[HASH(q, c)];
	parent[NIL] = q;  /* sentinel */
	while (parent[r] != q) r = next[r];
	return r;
}

static /* inline */ void makechild(node q, unsigned char c, node r)
	/* Let r be q's child for character c. */
{
	node h, t;

	h = HASH(q, c);
	t = next[h];  next[h] = r;  next[r] = t;
	prev[t] = r;  prev[r] = h;
	parent[r] = q;  childcount[q]++;
}

static /*inline*/ void split(node old)
{
	node new, t;

	new = avail;  avail = next[new];  childcount[new] = 0;
	t = prev[old];  prev[new] = t;  next[t] = new;
	t = next[old];  next[new] = t;  prev[t] = new;
	parent[new] = parent[old];
	level[new] = matchlen;
	position[new] = pos;
	makechild(new, text[matchpos + matchlen], old);
	makechild(new, text[pos + matchlen], pos);
}

static void insert_node(void)
{
	node q, r, j, t;
	unsigned char c, *t1, *t2;

	if (matchlen >= 4) {
		matchlen--;
		r = (matchpos + 1) | dicsiz;
		while ((q = parent[r]) == NIL) r = next[r];
		while (level[q] >= matchlen) {
			r = q;  q = parent[q];
		}
#if PERCOLATE
			t = q;
			while (position[t] < 0) {
				position[t] = pos;  t = parent[t];
			}
			if (t < dicsiz) position[t] = pos | SHRT_MIN;
#else
			t = q;
			while (t < dicsiz) {
				position[t] = pos;  t = parent[t];
			}
#endif
	} else {
		q = text[pos] + dicsiz;  c = text[pos + 1];
		if ((r = child(q, c)) == NIL) {
			makechild(q, c, pos);  matchlen = 1;
			return;
		}
		matchlen = 2;
	}
	for ( ; ; ) {
		if (r >= dicsiz) {
			j = maxmatch;  matchpos = r;
		} else {
			j = level[r];
			matchpos = position[r] & SHRT_MAX;
		}
		if (matchpos >= pos) matchpos -= dicsiz;
		t1 = &text[pos + matchlen];  t2 = &text[matchpos + matchlen];
		while (matchlen < j) {
			if (*t1 != *t2) {  split(r);  return;  }
			matchlen++;  t1++;  t2++;
		}
		if (matchlen == maxmatch) break;
		position[r] = pos;
		q = r;
		if ((r = child(q, *t1)) == NIL) {
			makechild(q, *t1, pos);  return;
		}
		matchlen++;
	}
	t = prev[r];  prev[pos] = t;  next[t] = pos;
	t = next[r];  next[pos] = t;  prev[t] = pos;
	parent[pos] = q;  parent[r] = NIL;
	next[r] = pos;  /* special use of next[] */
}

static void delete_node(void)
{
#if PERCOLATE
		node q, r, s, t, u;
#else
		node r, s, t, u;
#endif

	if (parent[pos] == NIL) return;
	r = prev[pos];  s = next[pos];
	next[r] = s;  prev[s] = r;
	r = parent[pos];  parent[pos] = NIL;
	if (r >= dicsiz || --childcount[r] > 1) return;
#if PERCOLATE
		t = position[r] & SHRT_MAX;
#else
		t = position[r];
#endif
	if (t >= pos) t -= dicsiz;
#if PERCOLATE
		s = t;  q = parent[r];
		while ((u = position[q]) < 0) {
			u &= SHRT_MAX;  if (u >= pos) u -= dicsiz;
			if (u > s) s = u;
			position[q] = (s | dicsiz);  q = parent[q];
		}
		if (q < dicsiz) {
			if (u >= pos) u -= dicsiz;
			if (u > s) s = u;
			position[q] = (s | dicsiz) | SHRT_MIN;
		}
#endif
	s = child(r, text[t + level[r]]);
	t = prev[s];  u = next[s];
	next[t] = u;  prev[u] = t;
	t = prev[r];  next[t] = s;  prev[s] = t;
	t = next[r];  prev[t] = s;  next[s] = t;
	parent[s] = parent[r];  parent[r] = NIL;
	next[r] = avail;  avail = r;
}

/* static */void get_next_match(void)
{
	int n;

	remainder--;
	if (++pos == dicsiz * 2) {
		bcopy(&text[dicsiz], &text[0], dicsiz + maxmatch);
		n = fread_crc(&text[dicsiz + maxmatch], dicsiz, infile);
		encoded_origsize += n;
		remainder += n;  pos = dicsiz;
	}
	delete_node();  insert_node();
}

void encode(struct interfacing *interface)
{
	int lastmatchlen, dicsiz1;
	node lastmatchpos;

	infile = interface -> infile;
	outfile = interface -> outfile;
	origsize = interface -> original;
	compsize = count = 0L;
	crc = unpackable = 0;
	init_slide();  encode_set.encode_start();
	dicsiz1 = dicsiz - 1;
	pos = dicsiz + maxmatch;
	memset(&text[pos], ' ', dicsiz);
	remainder = fread_crc(&text[pos], dicsiz, infile);
	encoded_origsize = remainder;
	matchlen = 0;
	insert_node();
	while (remainder > 0 && ! unpackable) {
		lastmatchlen = matchlen;  lastmatchpos = matchpos;
		get_next_match();
		if (matchlen > remainder) matchlen = remainder;
		if (matchlen > lastmatchlen || lastmatchlen < THRESHOLD) {
			encode_set.output(text[pos - 1], 0);
			count++;
		} else {
			encode_set.output(lastmatchlen + (UCHAR_MAX + 1 - THRESHOLD),
				   (pos - lastmatchpos - 2) & dicsiz1);
			while (--lastmatchlen > 0) {
				get_next_match();
				count++;
			}
			if (matchlen > remainder) matchlen = remainder;
		}
	}
	encode_set.encode_end();
	interface -> packed = compsize;
	interface -> original = encoded_origsize;
}
#endif /* LHEXTRACT */

void decode(struct interfacing *interface)
{
	int i, j, k, c, dicsiz1, offset;

	infile = interface -> infile;
	outfile = interface -> outfile;
	dicbit = interface -> dicbit;
	origsize = interface -> original;
	compsize = interface -> packed;
	decode_set = decode_define[interface -> method - 1];
	crc = 0;
	prev_char = -1;
	dicsiz = 1 << dicbit;
	text = (unsigned char *)malloc(dicsiz);
	if (text == NULL)
		/* error(MEMOVRERR, NULL); */
		exit( errno );
	memset(text, ' ', dicsiz);
	decode_set.decode_start();
	dicsiz1 = dicsiz - 1;
	offset = (interface -> method == 6) ? 0x100 - 2 : 0x100 - 3;
	count = 0;  loc = 0;
	while (count < origsize) {
		c = decode_set.decode_c();
		if (c <= UCHAR_MAX) {
			text[loc++] = c;
			if (loc == dicsiz) {
				fwrite_crc(text, dicsiz, outfile);
				loc = 0;
			}
			count++;
		} else {
			j = c - offset;
			i = (loc - decode_set.decode_p() - 1) & dicsiz1;
			count += j;
			for (k = 0; k < j; k++) {
				c = text[(i + k) & dicsiz1];
				text[loc++] = c;
				if (loc == dicsiz) {
					fwrite_crc(text, dicsiz, outfile);
					loc = 0;
				}
			}
		}
	}
	if (loc != 0) {
		fwrite_crc(text, loc, outfile);
	}
	free(text);
}

