/*
 * PowerPacker decompression routine
 */
#include <string.h>

#include "Loaders.h"
#include "Song.h"

struct bitstream
{
	// bit buffer for rolling data bit by bit from the compressed file
	uint32_t       word;
	uint32_t       bitcount;
	// compressed data source
	const uint8_t* src;
	const uint8_t* orgsrc;
};

// Note the stange bit reading used will work only if bit-0 of first byte is 1
static void initGetb(struct bitstream* bs, const uint8_t* src, uint32_t src_length)
{
	bs->src = src + src_length - 1;
	bs->orgsrc = src;

	// get the first 8-bits of the compressed stream
	bs->word = *bs->src--;
	bs->bitcount = 8;
}

// get nbits from the compressed stream
static uint32_t getb(struct bitstream* bs, int nbits)
{
	uint32_t val = 0;

	for (;nbits > 0; nbits--)
	{
		if (!bs->bitcount)
		{
			if (bs->src >= bs->orgsrc)
				bs->word = *bs->src--;
			bs->bitcount = 8;
		}
		val = (val << 1) + (bs->word & 1);
		bs->word >>= 1;
		bs->bitcount--;
	}
	return val;
}

const _kernel_oserror* Decomp_PP20(SongHdr* pSong)
{
	const _kernel_oserror* e = NULL;
	char tag[4];
	char bits[4];
	uint32_t outSize, inSize;
	uint8_t* pSrc = NULL;
	int32_t l, c, x;
	uint8_t* dst;
	uint8_t* dst2;
	uint8_t* dststart;
	struct bitstream bs;

	inSize = FileLoad_GetSize(pSong) - 12;
	if (inSize & 2)
		return Loaders_NotThisType;

	e = FileLoad_ReadInt(pSong, (uint32_t*) tag, 4);
	if (!e) e = FileLoad_ReadInt(pSong, (uint32_t*) bits, 4);
	if (e) return e;

	if ((memcmp(tag, "PP20", 4) != 0)
	||  bits[0] < 9 || bits[0] > 15
	||  bits[1] < 9 || bits[1] > 15
	||  bits[2] < 9 || bits[2] > 15
	||  bits[3] < 9 || bits[3] > 15)
		return Loaders_NotThisType;

	e = FileLoad_SetPos(pSong, FileLoad_GetSize(pSong) - 4);
	if (!e) e = FileLoad_ReadInt(pSong, (uint32_t*) tag, 4);
	if (e) return e;

	outSize = (tag[0] << 16) + (tag[1] << 8) + tag[2];
	if (outSize < inSize)
		return Loaders_NotThisType;

	e = Loaders_Alloc(pSong, (void**) &pSong->pLoaderData->Decomp.pMemory, outSize);
	if (e) return e;
	pSong->pLoaderData->Decomp.MemSize = outSize;

	e = Loaders_Alloc(pSong, (void**) &pSrc, inSize);
	if (e) return e;

	e = FileLoad_SetPos(pSong, 8);
	if (!e) e = FileLoad_Read(pSong, pSrc, inSize);
	if (e) return e;

	dststart = pSong->pLoaderData->Decomp.pMemory;
	dst = dststart + outSize - 1;

	initGetb(&bs, pSrc, inSize);

	// filler bits
	getb(&bs, tag[3]);

	while (dst >= dststart)
	{
		if (!getb(&bs, 1))
		{
			l = 1;
			do
			{
				c = getb(&bs, 2);
				l += c;
			}
			while (c == 3);

			if ((dst + 1 - dststart) < l)
			{
				e = Loaders_Error(pSong, 0, "PP20: Writing out of bounds, %d", l);
				goto decomp_error;
			}
			if ((bs.src + 1 - bs.orgsrc) < l)
			{
				e = Loaders_Error(pSong, 0, "PP20: Reading out of bounds, %d", l);
				goto decomp_error;
			}

			for (; l > 0; l--)
			{
				*dst-- = getb(&bs, 8);
			}
		}

		if (dst < dststart)
			break;

		l = getb(&bs, 2);
		x = bits[l];
		l += 2;

		if (l == 5)
		{
			x = getb(&bs, getb(&bs, 1) ? x : 7);
			do
			{
				c = getb(&bs, 3);
				l += c;
			}
			while (c == 7);
		}
		else
		{
			x = getb(&bs, x);
		}

		dst2 = dst + 1 + x;

		if ((dst + 1 - dststart) < l)
		{
			e = Loaders_Error(pSong, 0, "PP20: Pasting out of bounds, %d", l);
			goto decomp_error;
		}
		if (dst2 >= dststart + outSize)
		{
			e = Loaders_Error(pSong, 0, "PP20: Copying out of bounds, %p %p %p %d", dst, dst2, dststart + outSize, l);
			goto decomp_error;
		}

		for(; l > 0; l--)
		{
			*dst-- = *dst2--;
		}
	}

decomp_error:
	Loaders_Free(pSong, pSrc);

	return e;
}
