/*
 * util/win32.cc: utility functions only useful on windows
 */
#include "animone/util/win32.h"

#include <shlobj.h>  /* SHGetKnownFolderPath */
#include <subauth.h> /* UNICODE_STRING */
#include <windows.h>

namespace animone::internal::win32 {

std::string ToUtf8String(const std::wstring& string) {
	if (string.empty())
		return std::string();

	long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), nullptr, 0, nullptr, nullptr);
	std::string ret(size, '\0');
	::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), nullptr, nullptr);
	return ret;
}

std::string ToUtf8String(const UNICODE_STRING& string) {
	const auto wctomb = [&string](LPSTR out, int size) -> int {
		return ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, out, size, nullptr, nullptr);
	};

	if (string.Length <= 0)
		return std::string();

	long size = wctomb(nullptr, 0);
	std::string ret(size, '\0');
	wctomb(&ret.front(), ret.length());
	return ret;
}

std::wstring ToWstring(const std::string& string) {
	const auto mbtowc = [&string](LPWSTR out, int size) -> int {
		return ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), out, size);
	};

	if (string.empty())
		return std::wstring();

	long size = mbtowc(nullptr, 0);
	std::wstring ret(size, L'\0');
	mbtowc(&ret.front(), ret.length());
	return ret;
}

std::string GetProcessPath(DWORD process_id) {
	// If we try to open a SYSTEM process, this function fails and the last error
	// code is ERROR_ACCESS_DENIED.
	//
	// Note that if we requested PROCESS_QUERY_INFORMATION access right instead
	// of PROCESS_QUERY_LIMITED_INFORMATION, this function would fail when used
	// to open an elevated process.
	Handle process_handle(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id));

	if (!process_handle)
		return std::wstring();

	std::wstring buffer(MAX_PATH, L'\0');
	DWORD buf_size = buffer.length();

	// Note that this function requires Windows Vista or above. You may use
	// GetProcessImageFileName or GetModuleFileNameEx on earlier versions.
	if (!::QueryFullProcessImageNameW(process_handle.get(), 0, &buffer.front(), &buf_size))
		return std::wstring();

	buffer.resize(buf_size);
	return ToUtf8String(buffer);
}

std::string GetFileNameFromPath(const std::string& path) {
	const auto pos = path.find_last_of(L"/\\");
	return pos != std::wstring::npos ? path.substr(pos + 1) : path;
}

static std::wstring GetSystemDirectory() {
	PWSTR path_wch;
	SHGetFolderPathW(NULL, CSIDL_WINDOWS, NULL, SHGFP_TYPE_CURRENT, &path_wch);
	std::wstring path_wstr(path_wch);
	CoTaskMemFree(path_wch);
	return path_wstr;
}

bool IsSystemDirectory(const std::string& path) {
	return IsSystemDirectory(ToWstring(path));
}

bool IsSystemDirectory(std::wstring path) {
	::CharUpperBuffW(&path.front(), path.length());

	std::wstring windir = GetSystemDirectory();
	::CharUpperBuffW(&windir.front(), windir.length());

	// XXX wtf is 4?
	return path.find(windir) == 4;
}

bool VerifyProcessPath(const std::string& path) {
	return !path.empty() && !IsSystemDirectory(path);
}

bool VerifyProcessFileName(const std::string& name) {
	static const std::set<std::string> invalid_names = {
	    // System files
	    "explorer.exe",   // Windows Explorer
	    "taskeng.exe",    // Task Scheduler Engine
	    "taskhost.exe",   // Host Process for Windows Tasks
	    "taskhostex.exe", // Host Process for Windows Tasks
	    "taskmgr.exe",    // Task Manager
	    "services.exe",   // Service Control Manager
	};

	if (name.empty())
		return false;

	for (const auto& invalid_name : invalid_names)
		if (util::EqualStrings(name, invalid_name))
			return false;

	return true;
}

} // namespace animone::internal::win32
