#include "fileutil.h"
#include "../xz/Xz.h"
#include "../xz/7zCrc.h"
#include "../xz/XzCrc64.h"

static void *SzAlloc(void *p, size_t size) { p = p; return malloc(size); }
static void SzFree(void *p, void *address) { p = p; free(address); }
static ISzAlloc lzmaAlloc = { SzAlloc, SzFree };

void init_crc_tables()
{
	CrcGenerateTable();
	Crc64GenerateTable();
}

static Uint32 xz_file_read_unchecked(FILE* file, const void* file_buffer,
	const Uint64 file_size, void** buffer, Uint64* size)
{
	CXzUnpacker state;
	Uint64 uncompressed_size, dst_idx, src_idx;
	SizeT dst_size, src_size;
	Uint32 err;
	ECoderStatus status;

	err = XzUnpacker_Create(&state, &lzmaAlloc);

	*buffer = 0;
	*size = 0;

	if (err != SZ_OK)
	{
		return err;
	}

	dst_idx = 0;
	src_idx = 0;

	uncompressed_size = 0;

	do
	{
		uncompressed_size += 0x40000;

		*buffer = realloc(*buffer, uncompressed_size);

		dst_size = uncompressed_size - dst_idx;
		src_size = file_size - src_idx;

		err = XzUnpacker_Code(&state, (Byte*)*buffer + dst_idx,
			&dst_size, (Byte*)file_buffer + src_idx,
			&src_size, CODER_FINISH_ANY, &status);

		src_idx += src_size;
		dst_idx += dst_size;
	}
	while ((err == SZ_OK) && (status == CODER_STATUS_NOT_FINISHED));

	XzUnpacker_Free(&state);

	if (err == SZ_OK)
	{
		*size = dst_idx;
		*buffer = realloc(*buffer, dst_idx);
	}
	else
	{
		free(*buffer);

		*buffer = 0;
	}

	return err;
}

Uint32 file_read(FILE* file, const Uint64 file_size, void** buffer, Uint64* size)
{
	void* file_buffer;
	Uint32 result;

	if (file_size == 0)
	{
		*size = 0;
		*buffer = 0;

		return 1;
	}

	file_buffer = malloc(file_size);

	fseek(file, 0, SEEK_SET);
	fread(file_buffer, file_size, 1, file);

	if (file_size > XZ_SIG_SIZE)
	{
		if (memcmp(file_buffer, XZ_SIG, XZ_SIG_SIZE) == 0)
		{
			result = xz_file_read_unchecked(file, file_buffer,
				file_size, buffer, size);
	
			free(file_buffer);

			return result;
		}
	}

	*size = file_size;
	*buffer = file_buffer;

	return 0;
}

Uint32 xz_file_read(FILE* file, void** buffer, Uint64* size)
{
	void* file_buffer;
	Uint64 file_size;
	Uint32 result;

	fseek(file, 0, SEEK_END);
	file_size = ftell(file);
	*size = 0;
	*buffer = 0;

	if (file_size <= XZ_SIG_SIZE)
	{
		return 1;
	}

	file_buffer = malloc(file_size);

	fseek(file, 0, SEEK_SET);
	fread(file_buffer, file_size, 1, file);

	if (memcmp(file_buffer, XZ_SIG, XZ_SIG_SIZE) != 0)
	{
		free(file_buffer);

		return 1;
	}

	result = xz_file_read_unchecked(file, file_buffer, file_size, buffer,
		size);
	
	free(file_buffer);

	return result;
}

