///////////////////////////////////////////////////////////////////////////////
//
/// \file       lz_encoder_mf.c
/// \brief      Match finders
///
//  Authors:    Igor Pavlov
//              Lasse Collin
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#include "lz_encoder.h"
#include "lz_encoder_hash.h"


/// \brief      Find matches starting from the current byte
///
/// \return     The length of the longest match found
extern uint32_t
lzma_mf_find(lzma_mf *mf, uint32_t *count_ptr, lzma_match *matches)
{
	// Call the match finder. It returns the number of length-distance
	// pairs found.
	// FIXME: Minimum count is zero, what _exactly_ is the maximum?
	const uint32_t count = mf->find(mf, matches);

	// Length of the longest match; assume that no matches were found
	// and thus the maximum length is zero.
	uint32_t len_best = 0;

	if (count > 0) {
#ifndef NDEBUG
		uint32_t i;
		// Validate the matches.
		for (i = 0; i < count; ++i) {
			assert(matches[i].len <= mf->nice_len);
			assert(matches[i].dist < mf->read_pos);
			assert(memcmp(mf_ptr(mf) - 1,
				mf_ptr(mf) - matches[i].dist - 2,
				matches[i].len) == 0);
		}
#endif

		// The last used element in the array contains
		// the longest match.
		len_best = matches[count - 1].len;

		// If a match of maximum search length was found, try to
		// extend the match to maximum possible length.
		if (len_best == mf->nice_len) {
			uint8_t *p1;
			uint8_t *p2;

			// The limit for the match length is either the
			// maximum match length supported by the LZ-based
			// encoder or the number of bytes left in the
			// dictionary, whichever is smaller.
			uint32_t limit = mf_avail(mf) + 1;
			if (limit > mf->match_len_max)
				limit = mf->match_len_max;

			// Pointer to the byte we just ran through
			// the match finder.
			p1 = mf_ptr(mf) - 1;

			// Pointer to the beginning of the match. We need -1
			// here because the match distances are zero based.
			p2 = p1 - matches[count - 1].dist - 1;

			while (len_best < limit
					&& p1[len_best] == p2[len_best])
				++len_best;
		}
	}

	*count_ptr = count;

	// Finally update the read position to indicate that match finder was
	// run for this dictionary offset.
	++mf->read_ahead;

	return len_best;
}


/// Hash value to indicate unused element in the hash. Since we start the
/// positions from dict_size + 1, zero is always too far to qualify
/// as usable match position.
#define EMPTY_HASH_VALUE 0


/// Normalization must be done when lzma_mf.offset + lzma_mf.read_pos
/// reaches MUST_NORMALIZE_POS.
#define MUST_NORMALIZE_POS UINT32_MAX


/// \brief      Normalizes hash values
///
/// The hash arrays store positions of match candidates. The positions are
/// relative to an arbitrary offset that is not the same as the absolute
/// offset in the input stream. The relative position of the current byte
/// is lzma_mf.offset + lzma_mf.read_pos. The distances of the matches are
/// the differences of the current read position and the position found from
/// the hash.
///
/// To prevent integer overflows of the offsets stored in the hash arrays,
/// we need to "normalize" the stored values now and then. During the
/// normalization, we drop values that indicate distance greater than the
/// dictionary size, thus making space for new values.
static void
normalize(lzma_mf *mf)
{
	uint32_t i;
	uint32_t subvalue;
	uint32_t count;
	uint32_t *hash;

	assert(mf->read_pos + mf->offset == MUST_NORMALIZE_POS);

	// In future we may not want to touch the lowest bits, because there
	// may be match finders that use larger resolution than one byte.
	subvalue = (MUST_NORMALIZE_POS - mf->cyclic_size);
				// & (~(UINT32_C(1) << 10) - 1);

	count = mf->hash_size_sum + mf->sons_count;
	hash = mf->hash;

	for (i = 0; i < count; ++i) {
		// If the distance is greater than the dictionary size,
		// we can simply mark the hash element as empty.
		//
		// NOTE: Only the first mf->hash_size_sum elements are
		// initialized for sure. There may be uninitialized elements
		// in mf->son. Since we go through both mf->hash and
		// mf->son here in normalization, Valgrind may complain
		// that the "if" below depends on uninitialized value. In
		// this case it is safe to ignore the warning. See also the
		// comments in lz_encoder_init() in lz_encoder.c.
		if (hash[i] <= subvalue)
			hash[i] = EMPTY_HASH_VALUE;
		else
			hash[i] -= subvalue;
	}

	// Update offset to match the new locations.
	mf->offset -= subvalue;

	return;
}


/// Mark the current byte as processed from point of view of the match finder.
static void
move_pos(lzma_mf *mf)
{
	if (++mf->cyclic_pos == mf->cyclic_size)
		mf->cyclic_pos = 0;

	++mf->read_pos;
	assert(mf->read_pos <= mf->write_pos);

	if (unlikely(mf->read_pos + mf->offset == UINT32_MAX))
		normalize(mf);
}


/// When flushing, we cannot run the match finder unless there is nice_len
/// bytes available in the dictionary. Instead, we skip running the match
/// finder (indicating that no match was found), and count how many bytes we
/// have ignored this way.
///
/// When new data is given after the flushing was completed, the match finder
/// is restarted by rewinding mf->read_pos backwards by mf->pending. Then
/// the missed bytes are added to the hash using the match finder's skip
/// function (with small amount of input, it may start using mf->pending
/// again if flushing).
///
/// Due to this rewinding, we don't touch cyclic_pos or test for
/// normalization. It will be done when the match finder's skip function
/// catches up after a flush.
static void
move_pending(lzma_mf *mf)
{
	++mf->read_pos;
	assert(mf->read_pos <= mf->write_pos);
	++mf->pending;
}


/// Calculate len_limit and determine if there is enough input to run
/// the actual match finder code. Sets up "cur" and "pos". This macro
/// is used by all find functions and binary tree skip functions. Hash
/// chain skip function doesn't need len_limit so a simpler code is used
/// in them.
#define header(is_bt, len_min, ret_op) \
	uint32_t len_limit = mf_avail(mf); \
	if (mf->nice_len <= len_limit) { \
		len_limit = mf->nice_len; \
	} else if (len_limit < (len_min) \
			|| (is_bt && mf->action == LZMA_SYNC_FLUSH)) { \
		assert(mf->action != LZMA_RUN); \
		move_pending(mf); \
		ret_op; \
	} \
	cur = mf_ptr(mf); \
	pos = mf->read_pos + mf->offset


/// Header for find functions. "return 0" indicates that zero matches
/// were found.
#define header_find(is_bt, len_min) \
	header(is_bt, len_min, return 0)


/// Header for a loop in a skip function. "continue" tells to skip the rest
/// of the code in the loop.
#define header_skip(is_bt, len_min) \
	header(is_bt, len_min, continue)


/// Calls hc_find_func() or bt_find_func() and calculates the total number
/// of matches found. Updates the dictionary position and returns the number
/// of matches found.
#define call_find(func, len_best) \
do { \
	matches_count = func(len_limit, pos, cur, cur_match, mf->depth, \
				mf->son, mf->cyclic_pos, mf->cyclic_size, \
				matches + matches_count, len_best) \
			- matches; \
	move_pos(mf); \
	return matches_count; \
} while (0)


////////////////
// Hash Chain //
////////////////

#if defined(HAVE_MF_HC3) || defined(HAVE_MF_HC4)
///
///
/// \param      len_limit       Don't look for matches longer than len_limit.
/// \param      pos             lzma_mf.read_pos + lzma_mf.offset
/// \param      cur             Pointer to current byte (mf_ptr(mf))
/// \param      cur_match       Start position of the current match candidate
/// \param      depth           Maximum length of the hash chain
/// \param      son             lzma_mf.son (contains the hash chain)
/// \param      cyclic_pos
/// \param      cyclic_size
/// \param      matches         Array to hold the matches.
/// \param      len_best        The length of the longest match found so far.
static lzma_match *
hc_find_func(
		const uint32_t len_limit,
		const uint32_t pos,
		const uint8_t *const cur,
		uint32_t cur_match,
		uint32_t depth,
		uint32_t *const son,
		const uint32_t cyclic_pos,
		const uint32_t cyclic_size,
		lzma_match *matches,
		uint32_t len_best)
{
	son[cyclic_pos] = cur_match;

	while (true) {
		const uint32_t delta = pos - cur_match;
		const uint8_t *pb;
		if (depth-- == 0 || delta >= cyclic_size)
			return matches;

		pb = cur - delta;
		cur_match = son[cyclic_pos - delta
				+ (delta > cyclic_pos ? cyclic_size : 0)];

		if (pb[len_best] == cur[len_best] && pb[0] == cur[0]) {
			uint32_t len = 0;
			while (++len != len_limit)
				if (pb[len] != cur[len])
					break;

			if (len_best < len) {
				len_best = len;
				matches->len = len;
				matches->dist = delta - 1;
				++matches;

				if (len == len_limit)
					return matches;
			}
		}
	}
}


#define hc_find(len_best) \
	call_find(hc_find_func, len_best)


#define hc_skip() \
do { \
	mf->son[mf->cyclic_pos] = cur_match; \
	move_pos(mf); \
} while (0)

#endif


#ifdef HAVE_MF_HC3
extern uint32_t
lzma_mf_hc3_find(lzma_mf *mf, lzma_match *matches)
{
	const uint8_t *cur;
	uint32_t pos;
	uint32_t temp, hash_value, hash_2_value; /* hash_3_calc */
	uint32_t delta2, cur_match;
	uint32_t len_best = 2;
	uint32_t matches_count = 0;

	header_find(false, 3);

	hash_3_calc();

	delta2 = pos - mf->hash[hash_2_value];
	cur_match = mf->hash[FIX_3_HASH_SIZE + hash_value];

	mf->hash[hash_2_value] = pos;
	mf->hash[FIX_3_HASH_SIZE + hash_value] = pos;

	if (delta2 < mf->cyclic_size && *(cur - delta2) == *cur) {
		for ( ; len_best != len_limit; ++len_best)
			if (*(cur + len_best - delta2) != cur[len_best])
				break;

		matches[0].len = len_best;
		matches[0].dist = delta2 - 1;
		matches_count = 1;

		if (len_best == len_limit) {
			hc_skip();
			return 1; // matches_count
		}
	}

	hc_find(len_best);
}


extern void
lzma_mf_hc3_skip(lzma_mf *mf, uint32_t amount)
{
	do {
		const uint8_t *cur;
		uint32_t pos;
		uint32_t temp, hash_value, hash_2_value; /* hash_3_calc */
		uint32_t cur_match;

		if (mf_avail(mf) < 3) {
			move_pending(mf);
			continue;
		}

		cur = mf_ptr(mf);
		pos = mf->read_pos + mf->offset;

		hash_3_calc();

		cur_match = mf->hash[FIX_3_HASH_SIZE + hash_value];

		mf->hash[hash_2_value] = pos;
		mf->hash[FIX_3_HASH_SIZE + hash_value] = pos;

		hc_skip();

	} while (--amount != 0);
}
#endif


#ifdef HAVE_MF_HC4
extern uint32_t
lzma_mf_hc4_find(lzma_mf *mf, lzma_match *matches)
{
	const uint8_t *cur;
	uint32_t pos;
	uint32_t temp, hash_value, hash_2_value, hash_3_value; /* hash_4_calc */
	uint32_t delta2, delta3, cur_match;
	uint32_t len_best = 1;
	uint32_t matches_count = 0;

	header_find(false, 4);

	hash_4_calc();

	delta2 = pos - mf->hash[hash_2_value];
	delta3 = pos - mf->hash[FIX_3_HASH_SIZE + hash_3_value];
	cur_match = mf->hash[FIX_4_HASH_SIZE + hash_value];

	mf->hash[hash_2_value ] = pos;
	mf->hash[FIX_3_HASH_SIZE + hash_3_value] = pos;
	mf->hash[FIX_4_HASH_SIZE + hash_value] = pos;

	if (delta2 < mf->cyclic_size && *(cur - delta2) == *cur) {
		len_best = 2;
		matches[0].len = 2;
		matches[0].dist = delta2 - 1;
		matches_count = 1;
	}

	if (delta2 != delta3 && delta3 < mf->cyclic_size
			&& *(cur - delta3) == *cur) {
		len_best = 3;
		matches[matches_count++].dist = delta3 - 1;
		delta2 = delta3;
	}

	if (matches_count != 0) {
		for ( ; len_best != len_limit; ++len_best)
			if (*(cur + len_best - delta2) != cur[len_best])
				break;

		matches[matches_count - 1].len = len_best;

		if (len_best == len_limit) {
			hc_skip();
			return matches_count;
		}
	}

	if (len_best < 3)
		len_best = 3;

	hc_find(len_best);
}


extern void
lzma_mf_hc4_skip(lzma_mf *mf, uint32_t amount)
{
	do {
		const uint8_t *cur;
		uint32_t pos;
		uint32_t temp, hash_value, hash_2_value, hash_3_value; /* hash_4_calc */
		uint32_t cur_match;

		if (mf_avail(mf) < 4) {
			move_pending(mf);
			continue;
		}

		cur = mf_ptr(mf);
		pos = mf->read_pos + mf->offset;

		hash_4_calc();

		cur_match = mf->hash[FIX_4_HASH_SIZE + hash_value];

		mf->hash[hash_2_value] = pos;
		mf->hash[FIX_3_HASH_SIZE + hash_3_value] = pos;
		mf->hash[FIX_4_HASH_SIZE + hash_value] = pos;

		hc_skip();

	} while (--amount != 0);
}
#endif


/////////////////
// Binary Tree //
/////////////////

#if defined(HAVE_MF_BT2) || defined(HAVE_MF_BT3) || defined(HAVE_MF_BT4)
static lzma_match *
bt_find_func(
		const uint32_t len_limit,
		const uint32_t pos,
		const uint8_t *const cur,
		uint32_t cur_match,
		uint32_t depth,
		uint32_t *const son,
		const uint32_t cyclic_pos,
		const uint32_t cyclic_size,
		lzma_match *matches,
		uint32_t len_best)
{
	uint32_t *ptr0 = son + (cyclic_pos << 1) + 1;
	uint32_t *ptr1 = son + (cyclic_pos << 1);

	uint32_t len0 = 0;
	uint32_t len1 = 0;

	while (true) {
		uint32_t *pair;
		const uint8_t *pb;
		uint32_t len;

		const uint32_t delta = pos - cur_match;
		if (depth-- == 0 || delta >= cyclic_size) {
			*ptr0 = EMPTY_HASH_VALUE;
			*ptr1 = EMPTY_HASH_VALUE;
			return matches;
		}

		pair = son + ((cyclic_pos - delta
				+ (delta > cyclic_pos ? cyclic_size : 0))
				<< 1);

		pb = cur - delta;
		len = my_min(len0, len1);

		if (pb[len] == cur[len]) {
			while (++len != len_limit)
				if (pb[len] != cur[len])
					break;

			if (len_best < len) {
				len_best = len;
				matches->len = len;
				matches->dist = delta - 1;
				++matches;

				if (len == len_limit) {
					*ptr1 = pair[0];
					*ptr0 = pair[1];
					return matches;
				}
			}
		}

		if (pb[len] < cur[len]) {
			*ptr1 = cur_match;
			ptr1 = pair + 1;
			cur_match = *ptr1;
			len1 = len;
		} else {
			*ptr0 = cur_match;
			ptr0 = pair;
			cur_match = *ptr0;
			len0 = len;
		}
	}
}


static void
bt_skip_func(
		const uint32_t len_limit,
		const uint32_t pos,
		const uint8_t *const cur,
		uint32_t cur_match,
		uint32_t depth,
		uint32_t *const son,
		const uint32_t cyclic_pos,
		const uint32_t cyclic_size)
{
	uint32_t *ptr0 = son + (cyclic_pos << 1) + 1;
	uint32_t *ptr1 = son + (cyclic_pos << 1);

	uint32_t len0 = 0;
	uint32_t len1 = 0;

	while (true) {
		uint32_t *pair;
		const uint8_t *pb;
		uint32_t len;

		const uint32_t delta = pos - cur_match;
		if (depth-- == 0 || delta >= cyclic_size) {
			*ptr0 = EMPTY_HASH_VALUE;
			*ptr1 = EMPTY_HASH_VALUE;
			return;
		}

		pair = son + ((cyclic_pos - delta
				+ (delta > cyclic_pos ? cyclic_size : 0))
				<< 1);
		pb = cur - delta;
		len = my_min(len0, len1);

		if (pb[len] == cur[len]) {
			while (++len != len_limit)
				if (pb[len] != cur[len])
					break;

			if (len == len_limit) {
				*ptr1 = pair[0];
				*ptr0 = pair[1];
				return;
			}
		}

		if (pb[len] < cur[len]) {
			*ptr1 = cur_match;
			ptr1 = pair + 1;
			cur_match = *ptr1;
			len1 = len;
		} else {
			*ptr0 = cur_match;
			ptr0 = pair;
			cur_match = *ptr0;
			len0 = len;
		}
	}
}


#define bt_find(len_best) \
	call_find(bt_find_func, len_best)

#define bt_skip() \
do { \
	bt_skip_func(len_limit, pos, cur, cur_match, mf->depth, \
			mf->son, mf->cyclic_pos, \
			mf->cyclic_size); \
	move_pos(mf); \
} while (0)

#endif


#ifdef HAVE_MF_BT2
extern uint32_t
lzma_mf_bt2_find(lzma_mf *mf, lzma_match *matches)
{
	const uint8_t *cur;
	uint32_t pos;
	uint32_t hash_value; /* hash_2_calc */
	uint32_t cur_match;
	uint32_t matches_count = 0;

	header_find(true, 2);

	hash_2_calc();

	cur_match = mf->hash[hash_value];
	mf->hash[hash_value] = pos;

	bt_find(1);
}


extern void
lzma_mf_bt2_skip(lzma_mf *mf, uint32_t amount)
{
	do {
		const uint8_t *cur;
		uint32_t pos;
		uint32_t hash_value; /* hash_2_calc */
		uint32_t cur_match;

		header_skip(true, 2);

		hash_2_calc();

		cur_match = mf->hash[hash_value];
		mf->hash[hash_value] = pos;

		bt_skip();

	} while (--amount != 0);
}
#endif


#ifdef HAVE_MF_BT3
extern uint32_t
lzma_mf_bt3_find(lzma_mf *mf, lzma_match *matches)
{
	const uint8_t *cur;
	uint32_t pos;
	uint32_t temp, hash_value, hash_2_value; /* hash_3_calc */
	uint32_t delta2, cur_match;
	uint32_t len_best = 2;
	uint32_t matches_count = 0;

	header_find(true, 3);

	hash_3_calc();

	delta2 = pos - mf->hash[hash_2_value];
	cur_match = mf->hash[FIX_3_HASH_SIZE + hash_value];

	mf->hash[hash_2_value] = pos;
	mf->hash[FIX_3_HASH_SIZE + hash_value] = pos;

	if (delta2 < mf->cyclic_size && *(cur - delta2) == *cur) {
		for ( ; len_best != len_limit; ++len_best)
			if (*(cur + len_best - delta2) != cur[len_best])
				break;

		matches[0].len = len_best;
		matches[0].dist = delta2 - 1;
		matches_count = 1;

		if (len_best == len_limit) {
			bt_skip();
			return 1; // matches_count
		}
	}

	bt_find(len_best);
}


extern void
lzma_mf_bt3_skip(lzma_mf *mf, uint32_t amount)
{
	do {
		const uint8_t *cur;
		uint32_t pos;
		uint32_t temp, hash_value, hash_2_value; /* hash_3_calc */
		uint32_t cur_match;

		header_skip(true, 3);

		hash_3_calc();

		cur_match = mf->hash[FIX_3_HASH_SIZE + hash_value];

		mf->hash[hash_2_value] = pos;
		mf->hash[FIX_3_HASH_SIZE + hash_value] = pos;

		bt_skip();

	} while (--amount != 0);
}
#endif


#ifdef HAVE_MF_BT4
extern uint32_t
lzma_mf_bt4_find(lzma_mf *mf, lzma_match *matches)
{
	const uint8_t *cur;
	uint32_t pos;
	uint32_t temp, hash_value, hash_2_value, hash_3_value; /* hash_4_calc */
	uint32_t delta2, delta3, cur_match;
	uint32_t len_best = 1;
	uint32_t matches_count = 0;

	header_find(true, 4);

	hash_4_calc();

	delta2 = pos - mf->hash[hash_2_value];
	delta3 = pos - mf->hash[FIX_3_HASH_SIZE + hash_3_value];
	cur_match = mf->hash[FIX_4_HASH_SIZE + hash_value];

	mf->hash[hash_2_value] = pos;
	mf->hash[FIX_3_HASH_SIZE + hash_3_value] = pos;
	mf->hash[FIX_4_HASH_SIZE + hash_value] = pos;

	if (delta2 < mf->cyclic_size && *(cur - delta2) == *cur) {
		len_best = 2;
		matches[0].len = 2;
		matches[0].dist = delta2 - 1;
		matches_count = 1;
	}

	if (delta2 != delta3 && delta3 < mf->cyclic_size
			&& *(cur - delta3) == *cur) {
		len_best = 3;
		matches[matches_count++].dist = delta3 - 1;
		delta2 = delta3;
	}

	if (matches_count != 0) {
		for ( ; len_best != len_limit; ++len_best)
			if (*(cur + len_best - delta2) != cur[len_best])
				break;

		matches[matches_count - 1].len = len_best;

		if (len_best == len_limit) {
			bt_skip();
			return matches_count;
		}
	}

	if (len_best < 3)
		len_best = 3;

	bt_find(len_best);
}


extern void
lzma_mf_bt4_skip(lzma_mf *mf, uint32_t amount)
{
	do {
		const uint8_t *cur;
		uint32_t pos;
		uint32_t temp, hash_value, hash_2_value, hash_3_value; /* hash_4_calc */
		uint32_t cur_match;

		header_skip(true, 4);

		hash_4_calc();

		cur_match = mf->hash[FIX_4_HASH_SIZE + hash_value];

		mf->hash[hash_2_value] = pos;
		mf->hash[FIX_3_HASH_SIZE + hash_3_value] = pos;
		mf->hash[FIX_4_HASH_SIZE + hash_value] = pos;

		bt_skip();

	} while (--amount != 0);
}
#endif
