// This file is part of the Essence operating system.
// It is released under the terms of the MIT license -- see LICENSE.md.
// Written by: nakst.

struct String {
	char *text;
	size_t bytes, allocated;
};

String StringAllocateAndFormat(const char *format, ...) {
	String string = {};
	va_list arguments;
	va_start(arguments, format);
	string.text = EsStringAllocateAndFormatV(&string.bytes, format, arguments);
	va_end(arguments);
	string.allocated = string.bytes;
	return string;
}

String StringFromLiteral(const char *literal) {
	String string = {};
	string.text = (char *) literal;
	string.bytes = EsCStringLength(literal);
	return string;
}

String StringFromLiteralWithSize(const char *literal, ptrdiff_t bytes) {
	String string = {};
	string.text = (char *) literal;
	string.bytes = bytes == -1 ? EsCStringLength(literal) : bytes;
	return string;
}

void StringAppend(String *string, String with) {
	if (string->bytes + with.bytes > string->allocated) {
		string->allocated = (string->allocated + with.bytes) * 2;
		string->text = (char *) EsHeapReallocate(string->text, string->allocated, false);
	}

	EsMemoryCopy(string->text + string->bytes, with.text, with.bytes);
	string->bytes += with.bytes;
}

void StringDestroy(String *string) {
	EsAssert(string->allocated == string->bytes); // Attempting to free a partial string.
	EsHeapFree(string->text);
	string->text = nullptr;
	string->bytes = string->allocated = 0;
}

String StringDuplicate(String string) {
	String result = {};
	result.bytes = result.allocated = string.bytes;
	result.text = (char *) EsHeapAllocate(result.bytes + 1, false);
	result.text[result.bytes] = 0;
	EsMemoryCopy(result.text, string.text, result.bytes);
	return result;
}

inline bool StringStartsWith(String a, String b) {
	return a.bytes >= b.bytes && 0 == EsMemoryCompare(a.text, b.text, b.bytes);
}

inline bool StringEndsWith(String a, String b) {
	return a.bytes >= b.bytes && 0 == EsMemoryCompare(a.text + a.bytes - b.bytes, b.text, b.bytes);
}

inline bool StringEquals(String a, String b) {
	return a.bytes == b.bytes && 0 == EsMemoryCompare(a.text, b.text, a.bytes);
}

String StringSlice(String string, uintptr_t offset, ptrdiff_t length) {
	if (length == -1) {
		length = string.bytes - offset;
	}

	string.text += offset;
	string.bytes = length;
	string.allocated = 0;
	return string;
}

#define STRING(x) x.text, x.bytes
#define STRFMT(x) x.bytes, x.text

uintptr_t PathCountSections(String string) {
	ptrdiff_t sectionCount = 0;

	for (uintptr_t i = 0; i < string.bytes; i++) {
		if (string.text[i] == '/') {
			sectionCount++;
		}
	}

	return sectionCount;
}

String PathGetSection(String string, ptrdiff_t index) {
	String output = {};
	size_t stringBytes = string.bytes;
	char *text = string.text;

	if (index < 0) {
		index += PathCountSections(string);
	}

	if (index < 0) {
		return output;
	}

	uintptr_t i = 0, bytes = 0;

	for (; index && i < stringBytes; i++) {
		if (text[i] == '/') {
			index--;
		}
	}

	if (index) {
		return output;
	}

	output.text = text + i;

	for (; i < stringBytes; i++) {
		if (text[i] == '/') {
			break;
		} else {
			bytes++;
		}
	}

	output.bytes = bytes;

	return output;
}

String PathGetParent(String string, uintptr_t index) {
	if (index == PathCountSections(string)) return string;
	String section = PathGetSection(string, index);
	string.bytes = section.bytes + section.text - string.text + 1;
	return string;
}

String PathGetExtension(String string) {
	String extension = {};
	int lastSeparator = 0;

	for (intptr_t i = string.bytes - 1; i >= 0; i--) {
		if (string.text[i] == '.') {
			lastSeparator = i;
			break;
		}
	}

	if (!lastSeparator && string.text[0] != '.') {
		extension.text = string.text + string.bytes;
		extension.bytes = 0;
		return extension;
	} else {
		extension.text = string.text + lastSeparator + 1;
		extension.bytes = string.bytes - lastSeparator - 1;
		return extension;
	}
}

String PathGetParent(String string) {
	size_t newPathBytes = 0;

	for (uintptr_t i = 0; i < string.bytes - 1; i++) {
		if (string.text[i] == '/') {
			newPathBytes = i + 1;
		}
	}

	String result = {};
	result.bytes = newPathBytes;
	result.text = string.text;

	return result;
}

bool PathHasTrailingSlash(String path) {
	return path.bytes && path.text[path.bytes - 1] == '/';
}

String PathRemoveTrailingSlash(String path) {
	if (PathHasTrailingSlash(path)) path.bytes--;
	return path;
}

String PathGetName(String path) {
	intptr_t i = path.bytes - 2;
	while (i >= 0 && path.text[i] != '/') i--;
	path.text += i + 1, path.bytes -= i + 1;
	return path;
}

String PathGetDrive(String path) {
	uintptr_t i = 0;
	while (i < path.bytes && path.text[i] != '/') i++;
	path.bytes = i;
	return path;
}

bool PathHasPrefix(String path, String prefix) {
	prefix = PathRemoveTrailingSlash(prefix);
	return StringStartsWith(path, prefix) && ((path.bytes > prefix.bytes && path.text[prefix.bytes] == '/') || (path.bytes == prefix.bytes));
}

bool PathReplacePrefix(String *knownPath, String oldPath, String newPath) {
	if (PathHasPrefix(*knownPath, oldPath)) {
		String after = StringSlice(*knownPath, oldPath.bytes, -1);
		String path = StringAllocateAndFormat("%s%s", STRFMT(newPath), STRFMT(after));
		StringDestroy(knownPath);
		*knownPath = path;
		return true;
	}

	return false;
}