// Fast data compression library
// Copyright (C) 2006-2011 Lasse Mikkel Reinhold
// lar@quicklz.com
//
// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything
// released into public must be open source) or under a commercial license if such
// has been acquired (see http://www.quicklz.com/order.html). The commercial license
// does not cover derived or ported versions created by third parties under GPL.

// 1.5.0 final

#include "quicklz.h"

#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0
	#error quicklz.c and quicklz.h have different versions
#endif

#define MINOFFSET 2
#define UNCONDITIONAL_MATCHLEN 6
#define UNCOMPRESSED_END 4
#define CWORD_LEN 4

#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
	#define OFFSET_BASE source
	#define CAST (ui32)(size_t)
#else
	#define OFFSET_BASE 0
	#define CAST
#endif

int qlz_get_setting(int setting)
{
	switch (setting)
	{
		case 0: return QLZ_COMPRESSION_LEVEL;
		case 1: return sizeof(qlz_state_compress);
		case 2: return sizeof(qlz_state_decompress);
		case 3: return QLZ_STREAMING_BUFFER;
#ifdef QLZ_MEMORY_SAFE
		case 6: return 1;
#else
		case 6: return 0;
#endif
		case 7: return QLZ_VERSION_MAJOR;
		case 8: return QLZ_VERSION_MINOR;
		case 9: return QLZ_VERSION_REVISION;
	}
	return -1;
}

#if QLZ_COMPRESSION_LEVEL == 1
static int same(const unsigned char *src, size_t n)
{
	while(n > 0 && *(src + n) == *src)
		n--;
	return n == 0 ? 1 : 0;
}
#endif

static void reset_table_compress(qlz_state_compress *state)
{
	int i;
	for(i = 0; i < QLZ_HASH_VALUES; i++)
	{
#if QLZ_COMPRESSION_LEVEL == 1
		state->hash[i].offset = 0;
#else
		state->hash_counter[i] = 0;
#endif
	}
}

static void reset_table_decompress(qlz_state_decompress *state)
{
	int i;
	(void)state;
	(void)i;
#if QLZ_COMPRESSION_LEVEL == 2
	for(i = 0; i < QLZ_HASH_VALUES; i++)
	{
		state->hash_counter[i] = 0;
	}
#endif
}

static __inline ui32 hash_func(ui32 i)
{
#if QLZ_COMPRESSION_LEVEL == 2
	return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1);
#else
	return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1);
#endif
}

static __inline ui32 fast_read(void const *src, ui32 bytes)
{
  uint32_t ret = 0;
	if (bytes >= 1 && bytes <= 4) {
    for (uint32_t i = 0; i < bytes; i ++) {
      ret |= ((uint8_t*)src)[i] << (i * 8);
    }
  }
  return ret;
}

static __inline ui32 hashat(const unsigned char *src)
{
	ui32 fetch, hash;
	fetch = fast_read(src, 3);
	hash = hash_func(fetch);
	return hash;
}

static __inline void fast_write(ui32 f, void *dst, size_t bytes)
{
  for (size_t i = 0; i != bytes; i ++) {
    ((char*)dst)[i] = ((char*)&f)[i];
  }
}


size_t qlz_size_decompressed(const char *source)
{
	ui32 n, r;
	n = (((*source) & 2) == 2) ? 4 : 1;
	r = fast_read(source + 1 + n, n);
	r = r & (0xffffffff >> ((4 - n)*8));
	return r;
}

size_t qlz_size_compressed(const char *source)
{
	ui32 n, r;
	n = (((*source) & 2) == 2) ? 4 : 1;
	r = fast_read(source + 1, n);
	r = r & (0xffffffff >> ((4 - n)*8));
	return r;
}

size_t qlz_size_header(const char *source)
{
	size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1;
	return n;
}


static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n)
{
  assert(0); // unaligned memory access
}

static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s)
{
#if QLZ_COMPRESSION_LEVEL == 1
	ui32 hash;
	hash = hashat(s);
	state->hash[hash].offset = s;
	state->hash_counter[hash] = 1;
#elif QLZ_COMPRESSION_LEVEL == 2
	ui32 hash;
	unsigned char c;
	hash = hashat(s);
	c = state->hash_counter[hash];
	state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s;
	c++;
	state->hash_counter[hash] = c;
#endif
	(void)state;
	(void)s;
}

#if QLZ_COMPRESSION_LEVEL <= 2
static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max)
{
	while(*lh < max)
	{
		(*lh)++;
		update_hash(state, *lh);
	}
}
#endif

static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state)
{
	const unsigned char *last_byte = source + size - 1;
	const unsigned char *src = source;
	unsigned char *cword_ptr = destination;
	unsigned char *dst = destination + CWORD_LEN;
	ui32 cword_val = 1U << 31;
	const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
	ui32 fetch = 0;
	unsigned int lits = 0;

	(void) lits;

	if(src <= last_matchstart)
		fetch = fast_read(src, 3);

	while(src <= last_matchstart)
	{
		if ((cword_val & 1) == 1)
		{
			// store uncompressed if compression ratio is too low
			if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5))
				return 0;

			fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);

			cword_ptr = dst;
			dst += CWORD_LEN;
			cword_val = 1U << 31;
			fetch = fast_read(src, 3);
		}
#if QLZ_COMPRESSION_LEVEL == 1
		{
			const unsigned char *o;
			ui32 hash, cached;

			hash = hash_func(fetch);
			cached = fetch ^ state->hash[hash].cache;
			state->hash[hash].cache = fetch;

			o = state->hash[hash].offset + OFFSET_BASE;
			state->hash[hash].offset = CAST(src - OFFSET_BASE);

			if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
			{
				if (*(o + 3) != *(src + 3))
				{
					hash <<= 4;
					cword_val = (cword_val >> 1) | (1U << 31);
					fast_write((3 - 2) | hash, dst, 2);
					src += 3;
					dst += 2;
				}
				else
				{
					const unsigned char *old_src = src;
					size_t matchlen;
					hash <<= 4;

					cword_val = (cword_val >> 1) | (1U << 31);
					src += 4;

					if(*(o + (src - old_src)) == *src)
					{
						src++;
						if(*(o + (src - old_src)) == *src)
						{
							size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1;
							size_t remaining = q > 255 ? 255 : q;
							src++;
							while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining)
								src++;
						}
					}

					matchlen = src - old_src;
					if (matchlen < 18)
					{
						fast_write((ui32)(matchlen - 2) | hash, dst, 2);
						dst += 2;
					}
					else
					{
						fast_write((ui32)(matchlen << 16) | hash, dst, 3);
						dst += 3;
					}
				}
				fetch = fast_read(src, 3);
				lits = 0;
			}
			else
			{
				lits++;
				*dst = *src;
				src++;
				dst++;
				cword_val = (cword_val >> 1);
				fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16);
			}
		}
#elif QLZ_COMPRESSION_LEVEL >= 2
		{
			const unsigned char *o, *offset2;
			ui32 hash, matchlen, k, m, best_k = 0;
			unsigned char c;
			size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1);
			(void)best_k;


			//hash = hashat(src);
			fetch = fast_read(src, 3);
			hash = hash_func(fetch);

			c = state->hash_counter[hash];

			offset2 = state->hash[hash].offset[0];
			if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0)
			{
				matchlen = 3;
				if(*(offset2 + matchlen) == *(src + matchlen))
				{
					matchlen = 4;
					while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining)
						matchlen++;
				}
			}
			else
				matchlen = 0;
			for(k = 1; k < QLZ_POINTERS && c > k; k++)
			{
				o = state->hash[hash].offset[k];
#if QLZ_COMPRESSION_LEVEL == 3
				if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#elif QLZ_COMPRESSION_LEVEL == 2
				if(*(src + matchlen) == *(o + matchlen)	&& ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#endif
				{
					m = 3;
					while(*(o + m) == *(src + m) && m < remaining)
						m++;
#if QLZ_COMPRESSION_LEVEL == 3
					if ((m > matchlen) || (m == matchlen && o > offset2))
#elif QLZ_COMPRESSION_LEVEL == 2
					if (m > matchlen)
#endif
					{
						offset2 = o;
						matchlen = m;
						best_k = k;
					}
				}
			}
			o = offset2;
			state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
			c++;
			state->hash_counter[hash] = c;

#if QLZ_COMPRESSION_LEVEL == 3
			if(matchlen > 2 && src - o < 131071)
			{
				ui32 u;
				size_t offset = src - o;

				for(u = 1; u < matchlen; u++)
				{
					hash = hashat(src + u);
					c = state->hash_counter[hash]++;
					state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u;
				}

				cword_val = (cword_val >> 1) | (1U << 31);
				src += matchlen;

				if(matchlen == 3 && offset <= 63)
				{
					*dst = (unsigned char)(offset << 2);
					dst++;
				}
				else if (matchlen == 3 && offset <= 16383)
				{
					ui32 f = (ui32)((offset << 2) | 1);
					fast_write(f, dst, 2);
					dst += 2;
				}
				else if (matchlen <= 18 && offset <= 1023)
				{
					ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2;
					fast_write(f, dst, 2);
					dst += 2;
				}

				else if(matchlen <= 33)
				{
					ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3;
					fast_write(f, dst, 3);
					dst += 3;
				}
				else
				{
					ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3;
					fast_write(f, dst, 4);
					dst += 4;
				}
			}
			else
			{
				*dst = *src;
				src++;
				dst++;
				cword_val = (cword_val >> 1);
			}
#elif QLZ_COMPRESSION_LEVEL == 2

			if(matchlen > 2)
			{
				cword_val = (cword_val >> 1) | (1U << 31);
				src += matchlen;

				if (matchlen < 10)
				{
					ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5);
					fast_write(f, dst, 2);
					dst += 2;
				}
				else
				{
					ui32 f = best_k | (matchlen << 16) | (hash << 5);
					fast_write(f, dst, 3);
					dst += 3;
				}
			}
			else
			{
				*dst = *src;
				src++;
				dst++;
				cword_val = (cword_val >> 1);
			}
#endif
		}
#endif
	}
	while (src <= last_byte)
	{
		if ((cword_val & 1) == 1)
		{
			fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
			cword_ptr = dst;
			dst += CWORD_LEN;
			cword_val = 1U << 31;
		}
#if QLZ_COMPRESSION_LEVEL < 3
		if (src <= last_byte - 3)
		{
#if QLZ_COMPRESSION_LEVEL == 1
			ui32 hash, fetch;
			fetch = fast_read(src, 3);
			hash = hash_func(fetch);
			state->hash[hash].offset = CAST(src - OFFSET_BASE);
			state->hash[hash].cache = fetch;
#elif QLZ_COMPRESSION_LEVEL == 2
			ui32 hash;
			unsigned char c;
			hash = hashat(src);
			c = state->hash_counter[hash];
			state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
			c++;
			state->hash_counter[hash] = c;
#endif
		}
#endif
		*dst = *src;
		src++;
		dst++;
		cword_val = (cword_val >> 1);
	}

	while((cword_val & 1) != 1)
		cword_val = (cword_val >> 1);

	fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);

	// min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
	return dst - destination < 9 ? 9 : dst - destination;
}

static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history)
{
	const unsigned char *src = source + qlz_size_header((const char *)source);
	unsigned char *dst = destination;
	const unsigned char *last_destination_byte = destination + size - 1;
	ui32 cword_val = 1;
	const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
	unsigned char *last_hashed = destination - 1;
	const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1;
	static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};

	(void) last_source_byte;
	(void) last_hashed;
	(void) state;
	(void) history;

	for(;;)
	{
		ui32 fetch;

		if (cword_val == 1)
		{
#ifdef QLZ_MEMORY_SAFE
			if(src + CWORD_LEN - 1 > last_source_byte)
				return 0;
#endif
			cword_val = fast_read(src, CWORD_LEN);
			src += CWORD_LEN;
		}

#ifdef QLZ_MEMORY_SAFE
			if(src + 4 - 1 > last_source_byte)
				return 0;
#endif

		fetch = fast_read(src, 4);

		if ((cword_val & 1) == 1)
		{
			ui32 matchlen;
			const unsigned char *offset2;

#if QLZ_COMPRESSION_LEVEL == 1
			ui32 hash;
			cword_val = cword_val >> 1;
			hash = (fetch >> 4) & 0xfff;
			offset2 = (const unsigned char *)(size_t)state->hash[hash].offset;

			if((fetch & 0xf) != 0)
			{
				matchlen = (fetch & 0xf) + 2;
				src += 2;
			}
			else
			{
				matchlen = *(src + 2);
				src += 3;
			}

#elif QLZ_COMPRESSION_LEVEL == 2
			ui32 hash;
			unsigned char c;
			cword_val = cword_val >> 1;
			hash = (fetch >> 5) & 0x7ff;
			c = (unsigned char)(fetch & 0x3);
			offset2 = state->hash[hash].offset[c];

			if((fetch & (28)) != 0)
			{
				matchlen = ((fetch >> 2) & 0x7) + 2;
				src += 2;
			}
			else
			{
				matchlen = *(src + 2);
				src += 3;
			}

#elif QLZ_COMPRESSION_LEVEL == 3
			ui32 offset;
			cword_val = cword_val >> 1;
			if ((fetch & 3) == 0)
			{
				offset = (fetch & 0xff) >> 2;
				matchlen = 3;
				src++;
			}
			else if ((fetch & 2) == 0)
			{
				offset = (fetch & 0xffff) >> 2;
				matchlen = 3;
				src += 2;
			}
			else if ((fetch & 1) == 0)
			{
				offset = (fetch & 0xffff) >> 6;
				matchlen = ((fetch >> 2) & 15) + 3;
				src += 2;
			}
			else if ((fetch & 127) != 3)
			{
				offset = (fetch >> 7) & 0x1ffff;
				matchlen = ((fetch >> 2) & 0x1f) + 2;
				src += 3;
			}
			else
			{
				offset = (fetch >> 15);
				matchlen = ((fetch >> 7) & 255) + 3;
				src += 4;
			}

			offset2 = dst - offset;
#endif

#ifdef QLZ_MEMORY_SAFE
			if(offset2 < history || offset2 > dst - MINOFFSET - 1)
				return 0;

			if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1))
				return 0;
#endif

			memcpy_up(dst, offset2, matchlen);
			dst += matchlen;

#if QLZ_COMPRESSION_LEVEL <= 2
			update_hash_upto(state, &last_hashed, dst - matchlen);
			last_hashed = dst - 1;
#endif
		}
		else
		{
			if (dst < last_matchstart)
			{
				unsigned int n = bitlut[cword_val & 0xf];
				memcpy_up(dst, src, 4);
				cword_val = cword_val >> n;
				dst += n;
				src += n;
#if QLZ_COMPRESSION_LEVEL <= 2
				update_hash_upto(state, &last_hashed, dst - 3);
#endif
			}
			else
			{
				while(dst <= last_destination_byte)
				{
					if (cword_val == 1)
					{
						src += CWORD_LEN;
						cword_val = 1U << 31;
					}
#ifdef QLZ_MEMORY_SAFE
					if(src >= last_source_byte + 1)
						return 0;
#endif
					*dst = *src;
					dst++;
					src++;
					cword_val = cword_val >> 1;
				}

#if QLZ_COMPRESSION_LEVEL <= 2
				update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant
#endif
				return size;
			}

		}
	}
}

size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state)
{
	size_t r;
	ui32 compressed;
	size_t base;

	if(size == 0 || size > 0xffffffff - 400)
		return 0;

	if(size < 216)
		base = 3;
	else
		base = 9;

#if QLZ_STREAMING_BUFFER > 0
	if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER)
#endif
	{
		reset_table_compress(state);
		r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state);
#if QLZ_STREAMING_BUFFER > 0
		reset_table_compress(state);
#endif
		if(r == base)
		{
			bench_memcpy(destination + base, source, size);
			r = size + base;
			compressed = 0;
		}
		else
		{
			compressed = 1;
		}
		state->stream_counter = 0;
	}
#if QLZ_STREAMING_BUFFER > 0
	else
	{
		unsigned char *src = state->stream_buffer + state->stream_counter;

		bench_memcpy(src, source, size);
		r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state);

 		if(r == base)
		{
			bench_memcpy(destination + base, src, size);
			r = size + base;
			compressed = 0;
			reset_table_compress(state);
		}
		else
		{
			compressed = 1;
		}
		state->stream_counter += size;
	}
#endif
	if(base == 3)
	{
		*destination = (unsigned char)(0 | compressed);
		*(destination + 1) = (unsigned char)r;
		*(destination + 2) = (unsigned char)size;
	}
	else
	{
		*destination = (unsigned char)(2 | compressed);
		fast_write((ui32)r, destination + 1, 4);
		fast_write((ui32)size, destination + 5, 4);
	}

	*destination |= (QLZ_COMPRESSION_LEVEL << 2);
	*destination |= (1 << 6);
	*destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4);

// 76543210
// 01SSLLHC

	return r;
}

size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state)
{
	size_t dsiz = qlz_size_decompressed(source);

#if QLZ_STREAMING_BUFFER > 0
	if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER)
#endif
	{
		if((*source & 1) == 1)
		{
			reset_table_decompress(state);
			dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination);
		}
		else
		{
			bench_memcpy(destination, source + qlz_size_header(source), dsiz);
		}
		state->stream_counter = 0;
		reset_table_decompress(state);
	}
#if QLZ_STREAMING_BUFFER > 0
	else
	{
		unsigned char *dst = state->stream_buffer + state->stream_counter;
		if((*source & 1) == 1)
		{
			dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer);
		}
		else
		{
			bench_memcpy(dst, source + qlz_size_header(source), dsiz);
			reset_table_decompress(state);
		}
		bench_memcpy(destination, dst, dsiz);
		state->stream_counter += dsiz;
	}
#endif
	return dsiz;
}

