/*
 * Copyright 2026 CodingMarkus
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later OR Apache-2.0
 */

#include "decompress.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

static const size_t MINIMUM_WINDOW_SIZE = 16;
static const size_t MAXIMUM_WINDOW_SIZE = 65536;
#define FORMAT_HEADER_SIZE 16

static const uint_fast32_t MEDIUM_OFFSET_MASK = 4095;

static const unsigned char FORMAT_VERSION         = 1;
static const unsigned char MAXIMUM_UNESCAPED_BYTE = 0xF4;

static const unsigned char RLE_INSTRUCTION           = 0xF5;
static const unsigned char COPY_3_INSTRUCTION        = 0xF6;
static const unsigned char EXTENDED_RLE_LENGTH       = 0x01;
static const unsigned char COPY_VARIABLE_INSTRUCTION = 0xFB;


struct Ring {
	unsigned char * bytes;
	size_t first;
	size_t length;
	size_t size;
};

struct Output {
	unsigned char * bytes;
	size_t length;
	size_t size;
	unsigned char lastByte;
	bool hasLastByte;
};


static
size_t ringIndex( const struct Ring * ring, size_t offset )
{
	return ((ring->first + offset) % ring->size);
}


static
void ringAppend( struct Ring * ring, unsigned char byte )
{
	if (ring->length < ring->size) {
		ring->bytes[ringIndex(ring, ring->length)] = byte;
		ring->length += 1;
		return;
	}
	ring->bytes[ring->first] = byte;
	ring->first = (ring->first + 1) % ring->size;
}


static
int writeByte(
	struct Ring * ring,
	struct Output * output,
	unsigned char byte )
{
	if (output->length == output->size) return -1;
	output->bytes[output->length] = byte;
	output->length += 1;
	output->lastByte = byte;
	output->hasLastByte = true;
	ringAppend(ring, byte);
	return 0;
}


static
int copyBytes(
	struct Ring * ring,
	struct Output * output,
	size_t offset,
	size_t length )
{
	if ((length == 0) || (offset >= ring->length)
		|| (output->size - output->length < length))
	{
		return -1;
	}
	size_t index = 0;
	while (index < length) {
		unsigned char byte = ring->bytes[ringIndex(
			ring, ring->length - offset - 1
		)];

		output->bytes[output->length] = byte;
		output->length += 1;
		ringAppend(ring, byte);
		index += 1;
	}
	output->lastByte = output->bytes[output->length - 1];
	output->hasLastByte = true;
	return 0;
}


static
int readByte(
	const unsigned char * input,
	size_t inputSize,
	size_t * inputOffset,
	unsigned char * value )
{
	if (*inputOffset == inputSize) return -1;
	*value = input[*inputOffset];
	*inputOffset += 1;
	return 0;
}


enum SquinchResult SquinchDecompress(
	const void * inputBytes,
	size_t inputSize,
	void * outputBytes,
	size_t outputSize,
	size_t * outBytesWritten )
{
	if ((inputBytes == NULL) && (inputSize != 0))
		return InputBufferIsNULL_SquinchResult;
	if ((outputBytes == NULL) && (outputSize != 0))
		return OutputBufferIsNULL_SquinchResult;
	if (outBytesWritten == NULL)
		return OutBytesWrittenIsNULL_SquinchResult;
	*outBytesWritten = 0;
	if (inputSize < FORMAT_HEADER_SIZE) return InvalidInput_SquinchResult;
	const unsigned char * input = inputBytes;
	struct Ring ring = { NULL, 0, 0, 0 };
	struct Output output = { outputBytes, 0, outputSize, 0, false };
	size_t inputOffset = 0;
	unsigned char high = 0;
	unsigned char low = 0;
	unsigned char version = 0;
	if ((readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'S')
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'Q')
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'U')
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'I')
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'N')
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'C')
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (high != 'H')
		|| (readByte(input, inputSize, &inputOffset, &version) != 0)
		|| (version != FORMAT_VERSION)
		|| (readByte(input, inputSize, &inputOffset, &high) != 0)
		|| (readByte(input, inputSize, &inputOffset, &low) != 0)
		|| (inputSize - inputOffset < 6)
		|| (input[inputOffset] != 0))
	{
		return InvalidInput_SquinchResult;
	}
	ring.size = (size_t)(((uint_fast32_t)high << 8) | (uint_fast32_t)low);
	if (ring.size == 0) ring.size = MAXIMUM_WINDOW_SIZE;
	if (ring.size < MINIMUM_WINDOW_SIZE) return InvalidInput_SquinchResult;
	inputOffset += 6;
	unsigned char * storage = malloc(ring.size);
	if (storage == NULL) return OutOfMemory_SquinchResult;
	ring.bytes = storage;
	ring.first = 0;
	ring.length = 0;
	enum SquinchResult result = Success_SquinchResult;
	while (inputOffset < inputSize) {
		unsigned char instruction = input[inputOffset];
		inputOffset += 1;
		if ((instruction <= MAXIMUM_UNESCAPED_BYTE)
			|| (instruction >= 0xFC))
		{
			if (writeByte(&ring, &output, instruction) != 0) {
				result = OutputBufferIsNULL_SquinchResult;
				break;
			}
			continue;
		}
		if (instruction == RLE_INSTRUCTION) {
			unsigned char length = 0;
			size_t rleLength = 0;

			if ((readByte(input, inputSize, &inputOffset, &length) != 0)
				|| !output.hasLastByte)
			{
				result = InvalidInput_SquinchResult;
			} else if (length == EXTENDED_RLE_LENGTH) {
				if ((readByte(input, inputSize, &inputOffset, &high) != 0)
					|| (readByte(input, inputSize, &inputOffset, &low) != 0))
				{
					result = InvalidInput_SquinchResult;
					continue;
				}
				rleLength = ((size_t)high << 8) | (size_t)low;
				if (rleLength == 0) {
					result = InvalidInput_SquinchResult;
					continue;
				}
				rleLength += 255;
			} else if (length < 3) {
				result = InvalidInput_SquinchResult;
				continue;
			} else {
				rleLength = length;
			}
			if (result != Success_SquinchResult) continue;
			if (output.size - output.length < rleLength) {
				result = OutputBufferIsNULL_SquinchResult;
			} else {
					size_t index = 0;
				while (index < rleLength) {
					output.bytes[output.length] = output.lastByte;
					output.length += 1;
					index += 1;
				}
			}
		} else if (instruction == COPY_3_INSTRUCTION) {
			unsigned char offset = 0;

			if (readByte(input, inputSize, &inputOffset, &offset) != 0) {
				result = InvalidInput_SquinchResult;
			} else if (offset >= 249) {
				if (writeByte(&ring, &output,
					(unsigned char)(offset - 249 + RLE_INSTRUCTION)) != 0)
				{
					result = OutputBufferIsNULL_SquinchResult;
				}
			} else if (copyBytes(&ring, &output, offset + 1, 3) != 0) {
				result = InvalidInput_SquinchResult;
			}
		} else if ((instruction >= 0xF7) && (instruction <= 0xFA))
		{
			unsigned char offset = 0;

			if (readByte(input, inputSize, &inputOffset, &offset) != 0) {
				result = InvalidInput_SquinchResult;
			}
			else if (output.size - output.length
				< (size_t)(instruction - COPY_3_INSTRUCTION + 3))
			{
				result = OutputBufferIsNULL_SquinchResult;
			} else if (
				copyBytes(
					&ring, &output,
					offset + 1,
					(size_t)(instruction - COPY_3_INSTRUCTION + 3)
				) != 0)
			{
				result = InvalidInput_SquinchResult;
			}
		} else if (instruction == COPY_VARIABLE_INSTRUCTION) {
			unsigned char selector = 0;
			unsigned char bytes[4] = { 0, 0, 0, 0 };
			size_t length = 0;
			size_t offset = 0;
			uint_fast32_t value = 0;

			if (readByte(input, inputSize, &inputOffset, &selector) != 0) {
				result = InvalidInput_SquinchResult;
			} else if (selector >= 8) {
				if (readByte(input, inputSize, &inputOffset, &bytes[0]) != 0) {
					result = InvalidInput_SquinchResult;
				} else if (copyBytes(&ring, &output, bytes[0] + 1,
					selector) != 0) {
					result = InvalidInput_SquinchResult;
				}
			} else if (selector == 1) {
				if ((readByte(input, inputSize, &inputOffset, &bytes[0]) != 0)
					|| (readByte(
						input, inputSize, &inputOffset, &bytes[1]) != 0)
					|| (readByte(
						input, inputSize, &inputOffset, &bytes[2]) != 0
					))
				{
					result = InvalidInput_SquinchResult;
				} else {
					value = ((uint_fast32_t)bytes[0] << 16)
						| ((uint_fast32_t)bytes[1] << 8)
						| (uint_fast32_t)bytes[2];
					if ((value >> 12) == 0) {
						result = InvalidInput_SquinchResult;
					} else if (copyBytes(&ring, &output,
						(value & MEDIUM_OFFSET_MASK) + 1,
						value >> 12) != 0) {
						result = InvalidInput_SquinchResult;
					}
				}
			} else if ((selector == 2) || (selector == 3)) {
				size_t byteCount = (selector == 2) ? 3 : 4;

				if ((readByte(input, inputSize, &inputOffset, &bytes[0]) != 0)
					|| (readByte(
						input, inputSize, &inputOffset, &bytes[1]) != 0)
					|| (readByte(
						input, inputSize, &inputOffset, &bytes[2]) != 0)
					|| ((byteCount == 4)
						&& (readByte(
							input, inputSize, &inputOffset, &bytes[3]) != 0
						)
					))
				{
					result = InvalidInput_SquinchResult;
				} else {
					length = bytes[0];
					if (selector == 3) {
						length = (length << 8) | (size_t)bytes[1];
						length += 255;
						offset = ((size_t)bytes[2] << 8) | (size_t)bytes[3];
					} else {
						offset = ((size_t)bytes[1] << 8) | (size_t)bytes[2];
					}
					if ((((selector == 2) && (bytes[0] == 0))
						|| ((selector == 3) && (bytes[0] == 0)
							&& (bytes[1] == 0)))
						|| (copyBytes(&ring, &output,
						offset + 1, length) != 0)) {
						result = InvalidInput_SquinchResult;
					}
				}
			} else {
				result = InvalidInput_SquinchResult;
			}
		}
		if (result != Success_SquinchResult) break;
	}
	*outBytesWritten = output.length;
	free(storage);
	return result;
}


struct SquinchDecompressor {
	struct Ring ring;
	size_t maximumWindowSize;
	unsigned char instruction;
	unsigned char parameters[5];
	size_t parameterLength;
	size_t parameterSize;
	bool hasInstruction;
	unsigned char lastByte;
	bool hasLastByte;
	size_t pendingLength;
	size_t pendingOffset;
	bool pendingRLE;
	bool pendingLiteral;
	unsigned char pendingByte;
	bool ended;
	bool endSignaled;
	bool drained;
	bool failed;
	unsigned char headerState;
	unsigned char header[FORMAT_HEADER_SIZE];
};


static
bool streamLiteral( const struct SquinchDecompressor * decompressor,
	unsigned char instruction )
{
	return ((instruction <= MAXIMUM_UNESCAPED_BYTE)
		|| (instruction >= 0xFC));
}


static
int streamWriteByte(
	struct SquinchDecompressor * decompressor,
	unsigned char * output,
	size_t outputSize,
	size_t * outputLength,
	unsigned char byte,
	bool append )
{
	if (*outputLength == outputSize) return -1;
	output[*outputLength] = byte;
	*outputLength += 1;
	decompressor->lastByte = byte;
	decompressor->hasLastByte = true;
	if (append) ringAppend(&decompressor->ring, byte);
	return 0;
}


static
enum SquinchResult streamCopy(
	struct SquinchDecompressor * decompressor,
	size_t offset,
	size_t length )
{
	if (offset >= decompressor->ring.length) {
		return InvalidInput_SquinchResult;
	}
	decompressor->pendingOffset = offset;
	decompressor->pendingLength = length;
	return Success_SquinchResult;
}


static
enum SquinchResult streamInstruction(
	struct SquinchDecompressor * decompressor,
	unsigned char * output,
	size_t outputSize,
	size_t * outputLength )
{
	uint_fast32_t value = 0;
	size_t length = 0;
	size_t offset = 0;

	if (streamLiteral(decompressor, decompressor->instruction)) {
		if (streamWriteByte(
			decompressor,
			output, outputSize, outputLength,
			decompressor->instruction,
			true) != 0)
		{
			decompressor->pendingByte = decompressor->instruction;
			decompressor->pendingLength = 1;
			decompressor->pendingLiteral = true;
		}
		return Success_SquinchResult;
	}
	if (decompressor->instruction == RLE_INSTRUCTION) {
		length = decompressor->parameters[0];
		if (!decompressor->hasLastByte) {
			return InvalidInput_SquinchResult;
		}
		if (length == EXTENDED_RLE_LENGTH) {
			length = ((size_t)decompressor->parameters[1] << 8)
				| (size_t)decompressor->parameters[2];
			if (length == 0) return InvalidInput_SquinchResult;
			length += 255;
		} else if (length < 3) return InvalidInput_SquinchResult;
		decompressor->pendingLength = length;
		decompressor->pendingRLE = true;
		return Success_SquinchResult;
	}
	if (decompressor->instruction == COPY_3_INSTRUCTION) {
		if (decompressor->parameters[0] >= 249) {
			if (streamWriteByte(
				decompressor, output, outputSize, outputLength,
				(unsigned char)(decompressor->parameters[0] - 249
					+ RLE_INSTRUCTION), true
			) != 0) {
				decompressor->pendingByte = (unsigned char)(
					decompressor->parameters[0] - 249 + RLE_INSTRUCTION
				);
				decompressor->pendingLength = 1;
				decompressor->pendingLiteral = true;
			}
			return Success_SquinchResult;
		}
		return streamCopy(decompressor, decompressor->parameters[0] + 1, 3);
	}
	if (decompressor->instruction <= 0xFA) {
		length = (size_t)(
			decompressor->instruction - COPY_3_INSTRUCTION + 3
		);
		return streamCopy(
			decompressor,
			decompressor->parameters[0] + 1,
			length
		);
	}
	if (decompressor->parameters[0] >= 8) {
		return streamCopy(decompressor, decompressor->parameters[1] + 1,
			decompressor->parameters[0]);
	}
	if (decompressor->parameters[0] == 1) {
		value = ((uint_fast32_t)decompressor->parameters[1] << 16)
			| ((uint_fast32_t)decompressor->parameters[2] << 8)
			| (uint_fast32_t)decompressor->parameters[3];
		if ((value >> 12) == 0) return InvalidInput_SquinchResult;
		return streamCopy(
			decompressor,
			(value & MEDIUM_OFFSET_MASK) + 1,
			value >> 12
		);
	}
	if ((decompressor->parameters[0] != 2)
		&& (decompressor->parameters[0] != 3))
	{
		return InvalidInput_SquinchResult;
	}
	length = decompressor->parameters[1];
	if (decompressor->parameters[0] == 3) {
		length = (length << 8) | decompressor->parameters[2];
		length += 255;
		offset = ((size_t)decompressor->parameters[3] << 8)
			| decompressor->parameters[4];
	} else {
		offset = ((size_t)decompressor->parameters[2] << 8)
			| decompressor->parameters[3];
	}
	if (((decompressor->parameters[0] == 2)
		&& (decompressor->parameters[1] == 0))
		|| ((decompressor->parameters[0] == 3)
			&& (decompressor->parameters[1] == 0)
			&& (decompressor->parameters[2] == 0)))
	{
		return InvalidInput_SquinchResult;
	}
	return streamCopy(
		decompressor,
		offset + 1,
		length
	);
}


static
bool streamWritePending(
	struct SquinchDecompressor * decompressor,
	unsigned char * output,
	size_t outputSize,
	size_t * outputLength )
{
	while ((*outputLength < outputSize)
		&& (decompressor->pendingLength != 0))
	{
		unsigned char byte = 0;

		if (decompressor->pendingLiteral) byte = decompressor->pendingByte;
		else if (decompressor->pendingRLE) byte = decompressor->lastByte;
		else {
			byte = decompressor->ring.bytes[ringIndex(
				&decompressor->ring,
				decompressor->ring.length
					- decompressor->pendingOffset - 1
			)];
		}
		output[*outputLength] = byte;
		*outputLength += 1;
		decompressor->lastByte = byte;
		decompressor->hasLastByte = true;
		if (!decompressor->pendingRLE) {
			ringAppend(&decompressor->ring, byte);
		}
		decompressor->pendingLength -= 1;
	}
	if (decompressor->pendingLength != 0) return false;
	decompressor->pendingRLE = false;
	decompressor->pendingLiteral = false;
	return true;
}


static
size_t streamParameterSize( unsigned char instruction )
{
	if ((instruction == RLE_INSTRUCTION)
		|| (instruction == COPY_3_INSTRUCTION))
	{
		return 1;
	}
	if ((instruction >= 0xF7) && (instruction <= 0xFA))
	{
		return 1;
	}
	return ((instruction == COPY_VARIABLE_INSTRUCTION) ? 1 : 0);
}


static
enum SquinchResult decompressorSetWindow(
	struct SquinchDecompressor * decompressor,
	size_t windowSize )
{
	if (windowSize < MINIMUM_WINDOW_SIZE) {
		return InvalidInput_SquinchResult;
	}
	if (decompressor->maximumWindowSize != 0) {
		if (windowSize > decompressor->maximumWindowSize) {
			return WindowTooSmall_SquinchResult;
		}
		decompressor->ring.size = windowSize;
		return Success_SquinchResult;
	}
	unsigned char * storage = malloc(windowSize);
	if (storage == NULL) return OutOfMemory_SquinchResult;
	decompressor->ring.bytes = storage;
	decompressor->ring.size = windowSize;
	return Success_SquinchResult;
}


enum SquinchResult SquinchDecompressorCreate(
	struct SquinchDecompressor ** outDecompressor,
	size_t maxWindowSize )
{
	if (outDecompressor == NULL) return DecompressorIsNULL_SquinchResult;
	if ((maxWindowSize != 0)
		&& ((maxWindowSize < MINIMUM_WINDOW_SIZE)
			|| (maxWindowSize > MAXIMUM_WINDOW_SIZE)
		)
	)
	{
		return MaximumWindowSizeOutOfRange_SquinchResult;
	}
	struct SquinchDecompressor * result = calloc(1, sizeof(*result));
	if (result == NULL) return OutOfMemory_SquinchResult;
	result->maximumWindowSize = maxWindowSize;
	if (maxWindowSize != 0) {
		result->ring.bytes = malloc(maxWindowSize);
		if (result->ring.bytes == NULL) {
			free(result);
			return OutOfMemory_SquinchResult;
		}
	}
	*outDecompressor = result;
	return Success_SquinchResult;
}


enum SquinchResult SquinchDecompressorStep(
	struct SquinchDecompressor * decompressor,
	const void * inputBytes,
	size_t inputSize,
	void * outputBytes,
	size_t outputSize,
	size_t * outInputBytesConsumed,
	size_t * outOutputBytesWritten )
{
	if (decompressor == NULL) return DecompressorIsNULL_SquinchResult;
	if (outInputBytesConsumed == NULL)
		return InputBytesConsumedIsNULL_SquinchResult;
	if (outOutputBytesWritten == NULL)
		return OutputBytesWrittenIsNULL_SquinchResult;
	if ((inputBytes == NULL) && (inputSize != 0))
		return InputBufferIsNULL_SquinchResult;
	if ((outputBytes == NULL) && (outputSize != 0))
		return OutputBufferIsNULL_SquinchResult;
	if (decompressor->endSignaled && (inputSize != 0))
		return InputAfterEndOfData_SquinchResult;
	if (decompressor->drained) return NoMoreOutputAvailable_SquinchResult;
	if (decompressor->failed) return InvalidInput_SquinchResult;
	*outInputBytesConsumed = 0;
	*outOutputBytesWritten = 0;
	unsigned char * output = outputBytes;
	const unsigned char * input = inputBytes;
	size_t outputLength = 0;
	size_t index = 0;
	enum SquinchResult result = Success_SquinchResult;
	if (decompressor->ended) {
		decompressor->drained = true;
		return Success_SquinchResult;
	}
	if (inputSize == 0) decompressor->endSignaled = true;
	if (!streamWritePending(
		decompressor, output, outputSize, &outputLength
	)) {
		*outOutputBytesWritten = outputLength;
		return Success_SquinchResult;
	}
	if (inputSize == 0) {
		if ((decompressor->headerState != FORMAT_HEADER_SIZE)
			|| decompressor->hasInstruction
			|| (decompressor->parameterLength != 0))
		{
			decompressor->failed = true;
			return InvalidInput_SquinchResult;
		}
		decompressor->ended = true;
		*outOutputBytesWritten = outputLength;
		return Success_SquinchResult;
	}
	while (index < inputSize) {
		if (decompressor->pendingLength != 0) break;
		if (decompressor->headerState < FORMAT_HEADER_SIZE) {
			decompressor->header[decompressor->headerState] = input[index];
			decompressor->headerState += 1;
			index += 1;
			if ((decompressor->headerState <= 7)
				&& (decompressor->header[
					decompressor->headerState - 1
				] != "SQUINCH"[decompressor->headerState - 1]))
			{
				decompressor->failed = true;
				return InvalidInput_SquinchResult;
			}
			if ((decompressor->headerState == 8)
				&& (decompressor->header[7] != FORMAT_VERSION))
			{
				decompressor->failed = true;
				return InvalidInput_SquinchResult;
			}
			if (decompressor->headerState == FORMAT_HEADER_SIZE) {
				size_t windowSize = (
					(size_t)decompressor->header[8] << 8
				) | (size_t)decompressor->header[9];
				if (decompressor->header[10] != 0) {
					decompressor->failed = true;
					return InvalidInput_SquinchResult;
				}
				if (windowSize == 0) windowSize = MAXIMUM_WINDOW_SIZE;
				result = decompressorSetWindow(decompressor, windowSize);
				if (result != Success_SquinchResult) {
					decompressor->failed = true;
					return result;
				}
			}
			continue;
		}
		if (!decompressor->hasInstruction) {
			decompressor->instruction = input[index];
			decompressor->parameterSize = streamLiteral(
				decompressor, decompressor->instruction
			) ? 0
				: streamParameterSize(decompressor->instruction);
			index += 1;
			decompressor->hasInstruction = true;
			if (decompressor->parameterSize == 0) {
				result = streamInstruction(
					decompressor, output, outputSize, &outputLength
				);
				decompressor->hasInstruction = false;
				if (result == InvalidInput_SquinchResult) {
					decompressor->failed = true;
					return result;
				}
				if (!streamWritePending(
					decompressor, output, outputSize, &outputLength
				)) break;
				continue;
			}
		}
		while ((index < inputSize)
			&& (decompressor->parameterLength
				< decompressor->parameterSize
			))
		{
			decompressor->parameters[decompressor->parameterLength] =
				input[index];
			decompressor->parameterLength += 1;
			index += 1;
		}
		if ((decompressor->instruction == RLE_INSTRUCTION)
			&& (decompressor->parameterLength == 1)
			&& (decompressor->parameters[0] == EXTENDED_RLE_LENGTH))
		{
			decompressor->parameterSize = 3;
			continue;
		}
		if ((decompressor->instruction == COPY_VARIABLE_INSTRUCTION)
			&& (decompressor->parameterLength == 1))
		{
			if ((decompressor->parameters[0] == 1)
				|| (decompressor->parameters[0] == 2))
			{
				decompressor->parameterSize = 4;
			} else if (decompressor->parameters[0] == 3) {
				decompressor->parameterSize = 5;
			} else {
				decompressor->parameterSize = 2;
			}
			continue;
		}
		if (decompressor->parameterLength == decompressor->parameterSize) {
			result = streamInstruction(
				decompressor, output, outputSize, &outputLength
			);
			decompressor->parameterLength = 0;
			decompressor->hasInstruction = false;
			if (result == InvalidInput_SquinchResult) {
				decompressor->failed = true;
				return result;
			}
			if (!streamWritePending(
				decompressor, output, outputSize, &outputLength
			)) break;
		}
	}
	*outInputBytesConsumed = index;
	*outOutputBytesWritten = outputLength;
	return Success_SquinchResult;
}


enum SquinchResult SquinchDecompressorGetUncompressedSize(
	const struct SquinchDecompressor * decompressor,
	uint_least64_t * outUncompressedSize )
{
	if (decompressor == NULL) return DecompressorIsNULL_SquinchResult;
	if (outUncompressedSize == NULL) return OutHeaderValueIsNULL_SquinchResult;
	if (decompressor->headerState != FORMAT_HEADER_SIZE)
		return HeaderNotFullyParsed_SquinchResult;
	uint_least64_t size = 0;
	size_t index = 10;
	while (index < FORMAT_HEADER_SIZE) {
		size = (size << 8) | decompressor->header[index];
		index += 1;
	}
	*outUncompressedSize = size;
	return Success_SquinchResult;
}


enum SquinchResult SquinchDecompressorGetWindowSize(
	const struct SquinchDecompressor * decompressor,
	size_t * outWindowSize )
{
	if (decompressor == NULL) return DecompressorIsNULL_SquinchResult;
	if (outWindowSize == NULL) return OutHeaderValueIsNULL_SquinchResult;
	if (decompressor->headerState != FORMAT_HEADER_SIZE)
		return HeaderNotFullyParsed_SquinchResult;
	size_t size = ((size_t)decompressor->header[8] << 8)
		| (size_t)decompressor->header[9];
	*outWindowSize = (size == 0) ? MAXIMUM_WINDOW_SIZE : size;
	return Success_SquinchResult;
}


enum SquinchResult SquinchDecompressorGetVersion(
	const struct SquinchDecompressor * decompressor,
	unsigned char * outVersion )
{
	if (decompressor == NULL) return DecompressorIsNULL_SquinchResult;
	if (outVersion == NULL) return OutHeaderValueIsNULL_SquinchResult;
	if (decompressor->headerState != FORMAT_HEADER_SIZE)
		return HeaderNotFullyParsed_SquinchResult;
	*outVersion = decompressor->header[7];
	return Success_SquinchResult;
}


void SquinchDecompressorDestroy( struct SquinchDecompressor * decompressor )
{
	if (decompressor == NULL) return;
	free(decompressor->ring.bytes);
	free(decompressor);
}
