/* See LICENSE file for copyright and license details. */
#include "liblss16.h"
#include <string.h>


enum liblss16_decode_state
liblss16_decode_to_colour_index(struct liblss16_decoder *decoder,
                                const void *data_, size_t length, size_t *consumed_out,
                                uint8_t *pixels, size_t pixels_size, size_t *npixels_out,
                                enum liblss16_decode_error *error_out)
{
	const uint8_t *data = data_;
	uint8_t n;
	uint16_t m;
	size_t cnt;

	*consumed_out = 0;
	*npixels_out = 0;

	if (decoder->internal.kept) {
		while (decoder->internal.kept && pixels_size) {
			*pixels++ = decoder->internal.previous;
			--decoder->internal.kept;
			--pixels_size;
			++*npixels_out;
		}
		if (decoder->internal.kept)
			return LIBLSS16_DECODE_RUNNING;
	}

	if (!length) {
		enum liblss16_decode_error error;
		if (decoder->internal.init_state < 4U)
			error = LIBLSS16_DECODE_TRUNCATED_MAGIC;
		else if (decoder->internal.init_state < 8U + 16U * 3U)
			error = LIBLSS16_DECODE_TRUNCATED_HEADER;
		else if (!decoder->internal.y_rem)
			return LIBLSS16_DECODE_END_OF_IMAGE;
		else
			error = LIBLSS16_DECODE_TRUNCATED_IMAGE;
		if (error_out)
			*error_out = error;
		return LIBLSS16_DECODE_ERROR;
	}

	switch (decoder->internal.init_state) {
	case 0:
		if (*data++ != 0x3DU) {
		bad_magic:
			if (error_out)
				*error_out = LIBLSS16_DECODE_BAD_MAGIC;
			return LIBLSS16_DECODE_ERROR;
		}
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 1:
		if (*data++ != 0xF3U)
			goto bad_magic;
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 2:
		if (*data++ != 0x13U)
			goto bad_magic;
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 3:
		if (*data++ != 0x14U)
			goto bad_magic;
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 4:
		decoder->image.width = (uint16_t)*data++;
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 5:
		decoder->image.width |= (uint16_t)((uint16_t)*data++ << 8);
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 6:
		decoder->image.height = (uint16_t)*data++;
		++*consumed_out;
		++decoder->internal.init_state;
		if (!--length)
			break;
		/* fall through */

	case 7:
		decoder->image.height |= (uint16_t)((uint16_t)*data++ << 8);
		++*consumed_out;
		++decoder->internal.init_state;

		decoder->internal.x_rem = decoder->image.width;
		decoder->internal.y_rem = decoder->image.height;
		if (!decoder->image.width || !decoder->image.height || decoder->image.height >> 15) {
			if (error_out)
				*error_out = LIBLSS16_DECODE_BAD_IMAGE_SIZE;
			return LIBLSS16_DECODE_ERROR;
		}

		if (!--length)
			break;
		/* fall through */

	default:
		while (decoder->internal.init_state < 8U + 16U * 3U) {
			size_t i = decoder->internal.init_state++ - 8U;
			uint8_t value = *data++;
			++*consumed_out;
			if (value & 0xC0) {
				((uint8_t *)decoder->image.colour_map)[i] = value;
				if (error_out)
					*error_out = LIBLSS16_DECODE_BAD_COLOUR;
				return LIBLSS16_DECODE_ERROR;
			} else {
				value = (uint8_t)(((unsigned)value * 255U + 31U) / 63U);
				((uint8_t *)decoder->image.colour_map)[i] = value;
			}

			if (!--length)
				return LIBLSS16_DECODE_RUNNING;
		}

		for (;;) {
			if (!decoder->internal.y_rem)
				return LIBLSS16_DECODE_END_OF_IMAGE;

			if (decoder->internal.saved) {
				n = (uint8_t)(decoder->internal.saved - 1U);
				decoder->internal.saved = 0;
			} else {
				uint8_t high, low;
				if (!pixels_size || !length)
					return LIBLSS16_DECODE_RUNNING;
				high = (uint8_t)(*data >> 4);
				low = (uint8_t)(*data & 15);
				n = low;
				decoder->internal.saved = (uint8_t)(high + 1U);
				++data;
				--length;
				++*consumed_out;
			}

			if (decoder->internal.state == 0U) {
				if (n != decoder->internal.previous) {
					decoder->internal.previous = n;
					m = 1U;
					goto put_m_pixels;
				} else {
					decoder->internal.state = 1U;
				}
			} else if (decoder->internal.state == 1U) {
				if (n) {
					m = (uint16_t)n;
				put_m_pixels:
					decoder->internal.state = 0U;
					if (m > decoder->internal.x_rem) {
						if (error_out)
							*error_out = LIBLSS16_DECODE_EXCESSIVE_RUN_LENGTH;
						return LIBLSS16_DECODE_ERROR;
					}
					decoder->internal.x_rem -= m;
					if (!decoder->internal.x_rem) {
						decoder->internal.x_rem = decoder->image.width;
						decoder->internal.y_rem--;
						if (decoder->internal.saved > 1U) {
							if (error_out)
								*error_out = LIBLSS16_DECODE_BAD_ROW_PADDING;
							return LIBLSS16_DECODE_ERROR;
						}
						decoder->internal.saved = 0;
						decoder->internal.previous = 0;
					}
					cnt = (size_t)m < pixels_size ? (size_t)m : pixels_size;
					memset(pixels, decoder->internal.previous, cnt);
					pixels = &pixels[cnt];
					pixels_size -= cnt;
					*npixels_out += cnt;
					m -= (uint16_t)cnt;
					if (m) {
						decoder->internal.kept = m;
						return LIBLSS16_DECODE_RUNNING;
					}
				} else {
					decoder->internal.state = 2U;
				}
			} else if (decoder->internal.state == 2U) {
				decoder->internal.state = (uint8_t)(n | 128U);
			} else {
				m = (uint16_t)((n << 4) + (decoder->internal.state & 15U) + 16U);
				goto put_m_pixels;
			}
		}
		break;
	}

	return LIBLSS16_DECODE_RUNNING;
}