/*
 * RPAKER: File archive utility for Rovgend's Labyrinth PAK files
 */

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <dirent.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <zlib.h>

#include "RPK.h"

static RPK_Header header;
static RPK_TOCEntry *entries;
static void *fileData;
static size_t totalDataLen;

void GetBaseFileName(char *name, char outbuf[RPK_NAME_LENGTH])
{
	size_t namelen, i;
	char *startp = NULL, *endp = NULL;
	char *ch;

	namelen = strlen(name);

	for (ch = name + namelen; ch != name; ch--) {
		if (endp == NULL && *ch == '.')
			endp = ch;
		if (startp == NULL && (*ch == '/' || *ch == '\\'))
			startp = ch + 1;
	}
	if (startp == NULL)
		startp = name;
	if (endp == NULL)
		endp = name + namelen;
	
	memset(outbuf, 0, RPK_NAME_LENGTH);
	for (i = 0, ch = startp; i < RPK_NAME_LENGTH && ch != endp; i++, ch++) {
		outbuf[i] = *ch;
	}
}

void AddFile(const char *path)
{
	size_t fileLen;
	byte *indata, *outdata;
	size_t outlen;

	FILE *fp = fopen(path, "rb");
	if (!fp)
	{
		fprintf(stderr, "Unable to read file %s: %s\n", path, strerror(errno));
		return;
	}

	fseek(fp, 0, SEEK_END);
	fileLen = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	fileData = realloc(fileData, totalDataLen + fileLen);
	if (fileData == NULL)
		perror("Filedata alloc");
	entries = realloc(entries, (header.numentries + 1) * sizeof(RPK_TOCEntry));
	if (entries == NULL)
		perror("Entries alloc");

	outlen = compressBound(fileLen);
	indata = malloc(fileLen);
	outdata = malloc(outlen);

	fread(indata, 1, fileLen, fp);
	compress(outdata, &outlen, indata, fileLen);

	GetBaseFileName(path, entries[header.numentries].name);
	entries[header.numentries].size = outlen;
	entries[header.numentries].origSize = fileLen;
	entries[header.numentries].offset = totalDataLen;

	memcpy(((char *)fileData) + totalDataLen, outdata, outlen);
	totalDataLen += outlen;
	header.numentries++;

	fclose(fp);

	free(indata);
	free(outdata);
}

// Iterates over all files in a directory and adds each of them. Doesn't recurse into subdirectories.
#ifdef _WIN32
void AddDirectory(const char *dirname)
{
	WIN32_FIND_DATA ffd;
	HANDLE hFile;

	char dirPath[MAX_PATH], filePath[MAX_PATH];

	strcpy(dirPath, dirname);
	strcat(dirPath, "\\*");
	strcpy(filePath, dirname);
	strcat(filePath, "\\");

	hFile = FindFirstFileA(dirPath, &ffd);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		fprintf(stderr, "Unable to iterate dir %s: %d\n", dirPath, GetLastError());
		return;
	}

	do
	{
		if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			continue;
		strcpy(filePath, dirname);
		strcat(filePath, "\\");
		strcat(filePath, ffd.cFileName);
		AddFile(filePath);
	} while (FindNextFileA(hFile, &ffd));
	FindClose(hFile);
}
#else
void AddDirectory(const char *dirname)
{
	struct dirent *entry;
	struct stat filestat;
	char *pathbuf;

	DIR *folder = opendir(dirname);
	if (folder == NULL)
	{
		perror(dirname);
		return;
	}

	pathbuf = malloc(strlen(dirname) + 256);

	while ((entry = readdir(folder)) != NULL)
	{
		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
			continue;

		strcpy(pathbuf, dirname);
		strcat(pathbuf, "/");
		strcat(pathbuf, entry->d_name);

		stat(pathbuf, &filestat);

		if (S_ISREG(filestat.st_mode))
			AddFile(pathbuf);
	}

	free(pathbuf);
	closedir(folder);
}
#endif

void PrintUsage(char *program)
{
	printf("USAGE: %s [options] <files>\n", program);
	printf("\t-out <output path> : Set the output file path.\n");
	printf("\t-dir <dir path>    : Add all files in the specified directory.\n");
}

int main(int argc, char **argv)
{
	FILE *fp;
	int argi;
	const char *outname = "rovgend.rpk";

	if (argc < 2)
	{
		PrintUsage(argv[0]);
		return EXIT_FAILURE;
	}
	
	memcpy(header.header, "RPAK", 4);
	header.version = RPK_FILE_VERSION;
	header.flags = RPK_FLAG_COMPRESSED;
	header.numentries = 0;

	for (argi = 1; argi < argc; argi++)
	{
		if (strncmp(argv[argi], "-out", 4) == 0)
		{
			if (argi == argc - 1)
			{
				fprintf(stderr, "-out specified without output file name\n");
				return 1;
			}
			outname = argv[++argi];
		}
		else if (strncmp(argv[argi], "-dir", 4) == 0)
		{
			if (argi == argc - 1)
			{
				fprintf(stderr, "-dir specified without directory path\n");
				return 1;
			}
			AddDirectory(argv[++argi]);
		}
		else
		{
			AddFile(argv[argi]);
		}
	}
	
	for (int i = 0; i < header.numentries; i++)
	{
		entries[i].offset += sizeof(RPK_Header) + sizeof(RPK_TOCEntry) * header.numentries;
	}

	fp = fopen(outname, "wb");
	if (!fp)
	{
		perror(outname);
		return EXIT_FAILURE;
	}

	fwrite(&header, sizeof(RPK_Header), 1, fp);
	fwrite(entries, sizeof(RPK_TOCEntry), header.numentries, fp);
	fwrite(fileData, 1, totalDataLen, fp);

	fclose(fp);

	free(entries);
	free(fileData);

	return EXIT_SUCCESS;
}
