#include "pck_packer.h"

#include "core/os/file_access.h"

static uint64_t _align(uint64_t p_n, int p_alignment) {

	if (p_alignment == 0)
		return p_n;

	uint64_t rest = p_n % p_alignment;
	if (rest == 0)
		return p_n;
	else
		return p_n + (p_alignment - rest);
};

static void _pad(FileAccess* p_file, int p_bytes) {

	for (int i=0; i<p_bytes; i++) {

		p_file->store_8(0);
	};
};

void PCKPacker::_bind_methods() {

	ObjectTypeDB::bind_method(_MD("pck_start","pck_name","alignment"),&PCKPacker::pck_start);
	ObjectTypeDB::bind_method(_MD("add_file","pck_path","source_path"),&PCKPacker::add_file);
	ObjectTypeDB::bind_method(_MD("flush","verbose"),&PCKPacker::flush);
};


Error PCKPacker::pck_start(const String& p_file, int p_alignment) {

	file = FileAccess::open(p_file, FileAccess::WRITE);
	if (file == NULL) {

		return ERR_CANT_CREATE;
	};

	alignment = p_alignment;

	file->store_32(0x43504447); // MAGIC
	file->store_32(0); // # version
	file->store_32(0); // # major
	file->store_32(0); // # minor
	file->store_32(0); // # revision

	for (int i=0; i<16; i++) {

		file->store_32(0); // reserved
	};

	files.clear();

	return OK;
};

Error PCKPacker::add_file(const String& p_file, const String& p_src) {

	FileAccess* f = FileAccess::open(p_src, FileAccess::READ);
	if (!f) {
		return ERR_FILE_CANT_OPEN;
	};

	File pf;
	pf.path = p_file;
	pf.src_path = p_src;
	pf.size = f->get_len();
	pf.offset_offset = 0;

	files.push_back(pf);

	f->close();
	memdelete(f);

	return OK;
};

Error PCKPacker::flush(bool p_verbose) {

	if (!file) {
		ERR_FAIL_COND_V(!file, ERR_INVALID_PARAMETER);
		return ERR_INVALID_PARAMETER;
	};

	// write the index

	file->store_32(files.size());

	for (int i=0; i<files.size(); i++) {

		file->store_pascal_string(files[i].path);
		files[i].offset_offset = file->get_pos();
		file->store_64(0); // offset
		file->store_64(files[i].size); // size

		// # empty md5
		file->store_32(0);
		file->store_32(0);
		file->store_32(0);
		file->store_32(0);
	};


	uint64_t ofs = file->get_pos();
	ofs = _align(ofs, alignment);

	_pad(file, ofs - file->get_pos());

	const uint32_t buf_max = 65536;
	uint8_t *buf = memnew_arr(uint8_t, buf_max);

	int count = 0;
	for (int i=0; i<files.size(); i++) {

		FileAccess* src = FileAccess::open(files[i].src_path, FileAccess::READ);
		uint64_t to_write = files[i].size;
		while (to_write > 0) {

			int read = src->get_buffer(buf, MIN(to_write, buf_max));
			file->store_buffer(buf, read);
			to_write -= read;
		};

		uint64_t pos = file->get_pos();
		file->seek(files[i].offset_offset); // go back to store the file's offset
		file->store_64(ofs);
		file->seek(pos);

		ofs = _align(ofs + files[i].size, alignment);
		_pad(file, ofs - pos);

		src->close();
		memdelete(src);
		count += 1;
		if (p_verbose) {
			if (count % 100 == 0) {
				printf("%i/%i (%.2f)\r", count, files.size(), float(count) / files.size() * 100);
				fflush(stdout);
			};
		};
	};

	if (p_verbose)
		printf("\n");

	file->close();

	return OK;
};

PCKPacker::PCKPacker() {

	file = NULL;
};

PCKPacker::~PCKPacker() {
	if (file != NULL) {
		memdelete(file);
	};
	file = NULL;
};
