#include <psemek/util/executable_path.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/exception.hpp>

#ifdef _WIN32
#include <libloaderapi.h>
#include <errhandlingapi.h>
#include <winerror.h>
#endif

#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif

namespace psemek::util
{

	std::filesystem::path executable_path()
	{
#if defined _WIN32
		std::wstring result(256, '\0');
		while (true)
		{
			GetModuleFileNameW(NULL, result.data(), result.size());
			auto error = GetLastError();
			if (error == ERROR_SUCCESS)
				break;
			else if (error == ERROR_INSUFFICIENT_BUFFER)
				result.resize(result.size() * 2);
			else
				throw exception(util::to_string("failed to retrieve executable path: ", std::hex, error));
		}
		return std::filesystem::path(result);
#elif defined __linux__
		return std::filesystem::canonical("/proc/self/exe");
#elif defined __APPLE__
		uint32_t path_length = 0;
		_NSGetExecutablePath(nullptr, &path_length);
		std::string path(path_length, '\0');
		_NSGetExecutablePath(path.data(), &path_length);
		return std::filesystem::path(std::move(path));
#else
		throw exception("executable_path() is not implemented for this platform");
#endif
	}

}
