#pragma once

#include <cstring>
#include <string>

#if defined(__APPLE__) || defined(__linux__)
#	include <pthread.h>
#	include <sched.h>
#	include <unistd.h>

#	if defined(__linux__)
#		include <sys/prctl.h>
#		include <sys/resource.h>
#	endif
#elif defined(_WIN32)
// Workaround for https://github.com/chriskohlhoff/asio/issues/1441
#	if defined(WIN32_LEAN_AND_MEAN)
#		include <windows.h>
#	else
#		define WIN32_LEAN_AND_MEAN
#		include <windows.h>
#		undef WIN32_LEAN_AND_MEAN
#	endif
#endif

// Used for all low-level structs.
#if defined(__GNUC__) || defined(__clang__)
#	define PACKED_STRUCT(STRUCT_DECLARATION) STRUCT_DECLARATION __attribute__((__packed__))
#elif defined(_MSC_VER)
#	define PACKED_STRUCT(STRUCT_DECLARATION) __pragma(pack(push, 1)) STRUCT_DECLARATION __pragma(pack(pop))
#else
#	warning "Unable to ensure structs are properly packed."
#	define PACKED_STRUCT(STRUCT_DECLARATION) STRUCT_DECLARATION
#endif

namespace dv::io::support {

class ThreadExtra {
public:
	static std::string getName() {
		std::string name{};

#if defined(__linux__)
		name.resize(16); // Max 16 bytes from prctl() man.

		if (prctl(PR_GET_NAME, name.data()) == 0) {
			name.resize(strlen(name.c_str()));
		}
#elif defined(__APPLE__)
		name.resize(16); // Max 16 bytes from pthread_getname_np() man.

		if (pthread_getname_np(pthread_self(), name.data(), name.size()) == 0) {
			name.resize(strlen(name.c_str()));
		}
#elif defined(_WIN32)
		// Windows: this is not possible, only for debugging.
#else
#	warning "No portable way of getting thread name found."
#endif

		return name;
	}

	static bool setName([[maybe_unused]] const std::string &name) {
#if defined(__linux__)
		if (prctl(PR_SET_NAME, name.c_str()) == 0) {
			return true;
		}
#elif defined(__APPLE__)
		if (pthread_setname_np(name.c_str()) == 0) {
			return true;
		}
#elif defined(_WIN32)
		// Windows: this is not possible, only for debugging.
#else
#	warning "No portable way of setting thread name found."
#endif

		return false;
	}

	static bool setPriorityHighest() {
#if defined(__APPLE__) || defined(__linux__)
		int schedulerPolicy = 0;
		sched_param schedulerPriority{};

		if (pthread_getschedparam(pthread_self(), &schedulerPolicy, &schedulerPriority) != 0) {
			return false;
		}

		schedulerPriority.sched_priority = sched_get_priority_max(schedulerPolicy);

		if (pthread_setschedparam(pthread_self(), schedulerPolicy, &schedulerPriority) != 0) {
			return false;
		}

		return true;
#elif defined(_WIN32)
		if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0) {
			return false;
		}

		return true;
#else
#	warning "No portable way of raising thread priority found."
		return false;
#endif
	}
};

class ThreadNameSwitch {
public:
	explicit ThreadNameSwitch(const std::string &temporaryName) {
		originalName = ThreadExtra::getName();

		ThreadExtra::setName(temporaryName);
	}

	~ThreadNameSwitch() {
		ThreadExtra::setName(originalName);
	}

private:
	std::string originalName;
};

} // namespace dv::io::support
