#include <zlib.h>
#include <dirent.h>
#include <vector>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
using namespace std;
// make a file archive
typedef unsigned int u32;
const size_t FILENAME_LEN = 64;
const size_t BLOCKSIZE = 128;
const u32 VERSION = 0x10001000;
// 16 bytes
struct Header {
	char id[4];
	u32 version;
	u32 size;
	u32 files;
};
// 96 bytes
struct FileEntry {
	char filename[FILENAME_LEN];
	u32 size;
	u32 offset;
	u32 namehash;
	u32 checksum;
	u32 flags;
	u32 locale;
	u32 reserved1;
	u32 reserved2;
};
enum FLAGS
{
	FLAG_LOCALIZED = 1<<0,
	FLAG_TSC = 1<<1
};
u32 calc_crc32(const void *data, u32 size)
{
	u32 crc = crc32(0L, Z_NULL, 0);
	crc = crc32(crc, (const Bytef*)data, size);
	return crc;
}
vector<FileEntry> files;
vector<string> filepaths;
u32 gsize;
void addFile(const char *filename, const struct stat *st)
{
	assert(filename);
	size_t namelen = strlen(filename);
	if (namelen>FILENAME_LEN) {
		fprintf(stderr, "Skipping file: %s (filename too long)\n", filename);
		return;
	}
	string origname(filename);
	FileEntry fe;
	memset(&fe, 0, sizeof(FileEntry));
	// check name for a locale?
	if (!strcmp(filename+namelen-3,".en")) fe.locale = 1;
	else if (!strcmp(filename+namelen-3,".jp")) fe.locale = 2;
	if (fe.locale) {
		namelen -= 3;
		fe.flags |= FLAG_LOCALIZED;
	}
	strncpy(fe.filename, filename, namelen);
	fe.size = st->st_size;
	fe.namehash = calc_crc32(filename, namelen);
	if (!strncmp(filename+namelen-4,".tsc",4)) fe.flags |= FLAG_TSC;
	FILE *f = fopen(origname.c_str(),"rb");
	char *buf = new char[fe.size];
	fread(buf, fe.size, 1, f);
	fclose(f);
	fe.checksum = calc_crc32(buf,fe.size);
	delete[] buf;
    files.push_back(fe);
	filepaths.push_back(origname);
}
void scanDir(const char *dirname) {
	assert(dirname);
	string basepath(dirname);
	DIR *dir = opendir(dirname);
	while (dirent *de = readdir(dir)) {
		const char *filename = de->d_name;
		string fullpath = basepath + "/";
		fullpath += filename;
		if (filename[0]!='.') {
			//printf("%s\n",fullpath.c_str());
			struct stat st;
			stat(fullpath.c_str(), &st);
			if (S_ISDIR(st.st_mode)) {
				// recurse
				scanDir(fullpath.c_str());
			} else {
				addFile(fullpath.c_str(), &st);
			}
		}
	}
	closedir(dir);
}
int roundUp(int value, int block = BLOCKSIZE)
{
	int rem = value % block;
	return rem ? (value + block - rem) : value;
}
void buildFile(const char *nameOut)
{
	int num = files.size();
	static char dummy[BLOCKSIZE];
	memset(dummy, 0, BLOCKSIZE);
	// fix up offsets
	int headersize = sizeof(Header) + num * sizeof(FileEntry);
	int firstoffset = roundUp(headersize);
	int of = firstoffset;
	for (vector<FileEntry>::iterator it = files.begin(); it != files.end(); ++it) {
		it->offset = of;
		of = roundUp(of+it->size);
	}
	Header h;
	strncpy(h.id, "Cave", 4);
	h.version = VERSION;
	h.size = of;
	gsize = of;
	h.files = num;
	FILE *fo = fopen(nameOut,"wb");
	// write header
	fwrite(&h, sizeof(Header), 1, fo);
	// write file entries
	for (vector<FileEntry>::iterator it = files.begin(); it != files.end(); ++it) {
		FileEntry *fe = &(*it);
		fwrite(fe, sizeof(FileEntry), 1, fo);
	}
	// pad
	fwrite(dummy, firstoffset-headersize, 1, fo);
	// write the files
	for (u32 i=0; i<num; i++) {
		FileEntry &fe = files[i];
		string &name = filepaths[i];
		FILE *fi = fopen(name.c_str(), "rb");
		char *buf = new char[fe.size];
		fread(buf, fe.size, 1, fi);
		fclose(fi);
		fwrite(buf, fe.size, 1, fo);
		delete[] buf;
		// pad
		int toPad = roundUp(fe.size) - fe.size;
		if (toPad) fwrite(dummy, toPad, 1, fo);
	}
	fclose(fo);
	printf("%d files packed.\n", num);
}
void compressFile(const char *nameIn, const char *nameOut)
{
	FILE *f = fopen(nameIn, "rb");
	char *buf = new char[gsize];
	fread(buf, gsize, 1, f);
	fclose(f);
	u32 len = compressBound(gsize);
	char *buf2 = new char[len];
	int res = compress2((Bytef*)buf2, (uLongf*)&len, (const Bytef*)buf, gsize, 9);
	if (res != Z_OK) {
		fprintf(stderr, "Couldn't compress data\n");
	} else {
		f = fopen(nameOut, "wb");
		fwrite(&gsize, sizeof(u32), 1, f);
		fwrite(buf2, len, 1, f);
		fclose(f);
		delete[] buf;
		delete[] buf2;
		printf("Compressed %d -> %d bytes\n", gsize, len);
	}
}
int main(int argc, char *argv[])
{
	scanDir("data");
	buildFile("archive.dat");
    compressFile("archive.dat", "data.csz");
	return 0;
}