#include "path.h"

#include <windows.h>

struct PathDescriptor
{
	char* path;
	uint32_t max_length;
	uint32_t length;
};

void generate_file_path_prefix(PathDescriptor* const descriptor, const char prefix)
{
	if (!descriptor->max_length) {
		return;
	} else if (descriptor->max_length == 1) {
		descriptor->path[0] = '\0';
		descriptor->length = 0;
	} else {
		descriptor->path[0] = prefix;
		descriptor->path[1] = '\0';
		descriptor->length = 1;
	}
}

uint32_t generate_file_path_key(const uint32_t c, const uint32_t key_length, const uint32_t id)
{
	if (!key_length)
		return id;

	uint32_t mask = 1u << key_length;
	uint32_t key = id;
	for (unsigned int i = 0; i < key_length; ++i) {
		key <<= 1;
		if ((key & mask) != 0)
			key ^= c;
	}

	return key;
}

void append_to_file_path(PathDescriptor* const descriptor, char c)
{
	if (descriptor->max_length - descriptor->length <= 1)
		return;
	descriptor->path[descriptor->length++] = c;
	descriptor->path[descriptor->length] = '\0';
}

void stringize_file_path_key(PathDescriptor* const descriptor, const uint32_t key, const uint32_t key_length)
{
	if (!key_length)
		return;

	static const char kALPHABET[] = { "K59W4S6H7DOVJPERUQMT8BAIC2YLG30Z1FNX" };
	static const size_t kALPHABET_LENGTH = sizeof(kALPHABET) - 1;

	char key_string[16];
	memset(key_string, 0, sizeof(key_string));

	uint64_t x = key;
	for (uint32_t i = 0; i < key_length; ++i) {
		const uint64_t y = ((x * 954437177) >> 32) >> 3;
		const uint64_t z = x - (y * 4 + y * 32);
		assert(z >= 0 && z < kALPHABET_LENGTH);
		key_string[i] = kALPHABET[z];
		x = y;
	}

	uint32_t pos;
	if (key_length % 2 == 0) {
		append_to_file_path(descriptor, '/');
		append_to_file_path(descriptor, key_string[key_length - 1]);
		pos = key_length - 2;
	} else {
		pos = key_length - 1;
	}

	while (true) {
		append_to_file_path(descriptor, key_string[pos]);
		if (!pos)
			break;
		append_to_file_path(descriptor, '/');
		append_to_file_path(descriptor, key_string[pos - 1]);
		pos -= 2;
	}
}

char* generate_file_path(const uint32_t id, char* const path, const uint32_t max_length)
{
	PathDescriptor descriptor;
	descriptor.path = path;
	descriptor.max_length = max_length;

	int32_t x = static_cast<int32_t>(id);
	if (x < 1024) {
		generate_file_path_prefix(&descriptor, 'K');
		stringize_file_path_key(&descriptor, generate_file_path_key(1177, 10, x), 2);
		return descriptor.path;
	}
	x -= 1024;
	if (x < 32768) {
		generate_file_path_prefix(&descriptor, '5');
		stringize_file_path_key(&descriptor, generate_file_path_key(34961, 15, x), 3);
		return descriptor.path;
	}
	x -= 32768;
	if (x < 1048576) {
		generate_file_path_prefix(&descriptor, '9');
		stringize_file_path_key(&descriptor, generate_file_path_key(1120393, 20, x), 4);
		return descriptor.path;
	}
	if (x - 1048576 < 33554432) {
		generate_file_path_prefix(&descriptor, 'W');
		stringize_file_path_key(&descriptor, generate_file_path_key(35922449, 25, x - 1048576), 5);
		return descriptor.path;
	}
	if (x - 34603008 >= 0) {
		generate_file_path_prefix(&descriptor, '4');
		stringize_file_path_key(&descriptor, generate_file_path_key(2290684177, 31, x - 34603008), 6);
		return descriptor.path;
	}

	return nullptr;
}

bool path_exists(const char* const path)
{
	DWORD attributes = GetFileAttributesA(path);
	return attributes != INVALID_FILE_ATTRIBUTES;
}

bool file_exists(const char* const path)
{
	DWORD attributes = GetFileAttributesA(path);
	return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
}

bool directory_exists(const char* const path)
{
	DWORD attributes = GetFileAttributesA(path);
	return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

bool make_directory(const char* const path)
{
	return CreateDirectoryA(path, nullptr) != FALSE;
}

bool make_directories(const char* const path)
{
	char directory[_MAX_PATH];

	const char* part = path;
	while (*part != '\0') {
		const char* const delimeter = path_get_delimeter(part);
		if (*delimeter == '\0')
			break;
		const size_t part_length = delimeter - path;

		memset(directory, 0, sizeof(directory));
		strncpy(directory, path, part_length);

		if (!directory_exists(directory)) {
			if (!make_directory(directory))
				return false;
		}

		part = path_skip_delimeter(delimeter);
	}

	return true;
}

const char* path_get_delimeter(const char* const path)
{
	if (*path == '\0' || *path == '/')
		return path;

	const char* p = path;
	do {
		p++;
	} while (*p != '\0' && *p != '/');

	return p;
}

const char* path_skip_delimeter(const char* const path)
{
	if (*path != '/')
		return path;

	const char* p = path;
	do {
		p++;
	} while (*p == '/');

	return p;
}
