module deflate_test @test;

import std::compression::deflate, std::io, std::math;

fn void test_deflate_basic()
{
	String original = "Hello, world! This is a test of the DEFLATE compression algorithm.";
	char[] compressed = deflate::compress(mem, original)!!;
	defer free(compressed.ptr);

	char[] decompressed = deflate::decompress(mem, compressed)!!;
	defer free(decompressed.ptr);

	assert((String)decompressed == original, "Decompressed data does not match original");
}

fn void test_deflate_repetitive()
{
	// 5000 bytes of repetitive data should compress very well
	usz len = 5000;
	char[] original = mem::malloc(len)[:len];
	defer free(original.ptr);

	for (usz i = 0; i < len; i++)
	{
		original[i] = (char)((i % 10) + '0');
	}

	char[] compressed = deflate::compress(mem, original)!!;
	defer free(compressed.ptr);

	// Check that we actually achieved some compression
	assert(compressed.len < len / 10, "Repetitive data should compress well");

	char[] decompressed = deflate::decompress(mem, compressed)!!;
	defer free(decompressed.ptr);

	assert(decompressed.len == original.len, "Length mismatch");
	assert((String)decompressed == (String)original, "Data mismatch");
}

fn void test_deflate_empty()
{
	char[] original = {};
	char[] compressed = deflate::compress(mem, original)!!;
	defer free(compressed.ptr);

	char[] decompressed = deflate::decompress(mem, compressed)!!;
	defer free(decompressed.ptr);

	assert(decompressed.len == 0, "Expected empty decompression");
}

fn void test_deflate_large_repetitive() @if($feature(SLOW_TESTS))
{
	// Test larger buffer to trigger reallocs in inflater
	usz len = 100000;
	char[] original = mem::malloc(len)[:len];
	defer free(original.ptr);

	mem::set(original.ptr, (char)'A', len);

	char[] compressed = deflate::compress(mem, original)!!;
	defer free(compressed.ptr);

	char[] decompressed = deflate::decompress(mem, compressed)!!;
	defer free(decompressed.ptr);

	assert(decompressed.len == len, "Length mismatch");
	assert(decompressed[0] == 'A' && decompressed[len-1] == 'A', "Data mismatch");
}

fn void test_deflate_random_ish()
{
	// Data that doesn't compress well
	usz len = 1024;
	char[] original = mem::malloc(len)[:len];
	defer free(original.ptr);

	for (usz i = 0; i < len; i++)
	{
		original[i] = (char)(i & 0xFF);
	}

	char[] compressed = deflate::compress(mem, original)!!;
	defer free(compressed.ptr);

	char[] decompressed = deflate::decompress(mem, compressed)!!;
	defer free(decompressed.ptr);

	assert((String)decompressed == (String)original, "Data mismatch");
}

fn void test_deflate_corrupted()
{
	char[] compressed = deflate::compress(mem, "Some data")!!;
	defer free(compressed.ptr);

	// Corrupt the block type (bits 1-2 of first byte) to 3 (reserved/invalid)
	compressed[0] |= 0x06;

	char[]? decompressed = deflate::decompress(mem, compressed);
	assert(!@ok(decompressed), "Expected decompression to fail for corrupted data");
}

fn void test_deflate_stream()
{
	@pool()
	{
		String base = "This is a streaming test for DEFLATE. ";
		usz base_len = base.len;
		usz count = 50;
		char[] original_arr = mem::malloc(base_len * count)[:base_len * count];
		defer free(original_arr.ptr);
		for (usz i = 0; i < count; i++) {
			mem::copy(original_arr.ptr + i * base_len, base.ptr, base_len);
		}
		String original = (String)original_arr;

		char[] compressed = deflate::compress(mem, original_arr)!!;
		defer free(compressed.ptr);

		// Use a temporary file on disk to test the streaming interface
		File f = file::open("unittest_stream_deflate.bin", "wb+")!!;
		defer { (void)f.close(); (void)file::delete("unittest_stream_deflate.bin"); }

		f.write(compressed)!!;
		f.seek(0, Seek.SET)!!;

		// Decompress using stream
		File out_f = file::open("unittest_stream_out.bin", "wb+")!!;
		defer { (void)out_f.close(); (void)file::delete("unittest_stream_out.bin"); }

		deflate::decompress_stream(&f, &out_f)!!;

		usz out_size = out_f.seek(0, Seek.CURSOR)!!;
		assert(out_size == original.len, "Length mismatch in streaming decompression");

		out_f.seek(0, Seek.SET)!!;
		char[] result = mem::malloc(out_size)[:out_size];
		defer free(result.ptr);
		out_f.read(result)!!;

		assert((String)result == original, "Data mismatch in streaming decompression");
	};
}

fn void test_deflate_embedded_stream()
{
	String base = "This is a streaming test for DEFLATE. ";

	char[] compressed = deflate::compress(mem, base[..])!!;
	defer free(compressed.ptr);

	usz append_len = compressed.len + 1;
	char[] append = mem::malloc(append_len)[:append_len];
	defer free(append.ptr);

	append[:compressed.len] = compressed[..];
	append[compressed.len..] = 'c';

	ByteReader reader;
	reader.init(append);

	ByteWriter writer;
	writer.tinit();

	deflate::decompress_stream(&reader, &writer)!!;

	assert(writer.str_view() == base);

	assert(reader.read_byte()!! == 'c');
}

fn void test_deflate_incremental()
{
	@pool()
	{
		String original = "This is a test of incremental decompression. We will read it byte by byte.";
		char[] compressed = deflate::compress(mem, original)!!;
		defer free(compressed.ptr);

		// Use a ByteReader for the compressed data
		io::ByteReader in_stream;
		in_stream.init(compressed);

		Inflater* inflater = mem::new(Inflater);
		char[] bit_buf = mem::malloc(8192)[:8192];
		inflater.init(&in_stream, bit_buf);
		defer free(bit_buf.ptr);
		defer free(inflater);

		char[] decompressed = mem::malloc(original.len)[:original.len];
		defer free(decompressed.ptr);

		for (usz i = 0; i < original.len; i++)
		{
			char[1] one_byte;
			usz n = inflater.read(one_byte[..])!!;
			assert(n == 1, "Expected 1 byte");
			decompressed[i] = one_byte[0];
		}

		// One more read should return 0 (or EOF)
		char[1] extra;
		assert(inflater.read(extra[..])!! == 0, "Expected EOF");

		assert((String)original == (String)decompressed, "Incremental decompression failed");
	};
}
