/*
 * win/win32.cc: support for Windows
 *
 * Surprisingly, this is the one time where Microsoft actually
 * does it fairly OK. Everything has a pretty simple API, despite
 * the stupid wide string stuff.
 */
#include "animone/win/win32.h"
#include "animone.h"
#include "animone/util/win32.h"
#include "animone/win.h"
#include "animone/fd.h"

#include <set>
#include <string>

#include <windows.h>

namespace animone::internal::win32 {

static std::wstring GetWindowClassName(HWND hwnd) {
	static constexpr int kMaxSize = 256;

	std::wstring buffer(kMaxSize, L'\0');
	const auto size = ::GetClassNameW(hwnd, &buffer.front(), buffer.length());
	buffer.resize(size);
	return buffer;
}

static std::wstring GetWindowText(HWND hwnd) {
	const auto estimated_size = ::GetWindowTextLengthW(hwnd);
	std::wstring buffer(estimated_size + 1, L'\0');

	const auto size = ::GetWindowTextW(hwnd, &buffer.front(), buffer.length());
	/* GetWindowTextLength docs:
	 * "Under certain conditions, the GetWindowTextLength function may return a value
	 *  that is larger than the actual length of the text." */
	buffer.resize(size);
	return buffer;
}

static DWORD GetWindowProcessId(HWND hwnd) {
	DWORD process_id = 0;
	::GetWindowThreadProcessId(hwnd, &process_id);
	return process_id;
}

////////////////////////////////////////////////////////////////////////////////

static bool VerifyWindowStyle(HWND hwnd) {
	const auto window_style = ::GetWindowLong(hwnd, GWL_STYLE);
	const auto window_ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);

	auto has_style = [&window_style](DWORD style) { return (window_style & style) != 0; };
	auto has_ex_style = [&window_ex_style](DWORD ex_style) { return (window_ex_style & ex_style) != 0; };

	// Toolbars, tooltips and similar topmost windows
	if (has_style(WS_POPUP) && has_ex_style(WS_EX_TOOLWINDOW))
		return false;
	if (has_ex_style(WS_EX_TOPMOST) && has_ex_style(WS_EX_TOOLWINDOW))
		return false;

	return true;
}

static bool VerifyClassName(const std::string& name) {
	static const std::set<std::string> invalid_names = {
	    // System classes
	    "#32770",        // Dialog box
	    "CabinetWClass", // Windows Explorer
	    "ComboLBox",
	    "DDEMLEvent",
	    "DDEMLMom",
	    "DirectUIHWND",
	    "GDI+ Hook Window Class",
	    "IME",
	    "Internet Explorer_Hidden",
	    "MSCTFIME UI",
	    "tooltips_class32",
	};

	return !name.empty() && !invalid_names.count(name);
}

////////////////////////////////////////////////////////////////////////////////

static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param) {
	if (!::IsWindowVisible(hwnd))
		return TRUE;

	if (!VerifyWindowStyle(hwnd))
		return TRUE;

	Window window;
	window.platform = WindowPlatform::Win32;
	window.id.win32 = reinterpret_cast<std::uintptr_t>(hwnd);
	window.text = ToUtf8String(GetWindowText(hwnd));
	window.class_name = ToUtf8String(GetWindowClassName(hwnd));
	if (!VerifyClassName(window.class_name))
		return TRUE;

	Process process;
	process.platform = ExecutablePlatform::Win32;
	process.pid = GetWindowProcessId(hwnd);
	GetProcessName(process.pid, process.comm);

	auto& window_proc = *reinterpret_cast<window_proc_t*>(param);
	if (!window_proc(process, window))
		return FALSE;

	return TRUE;
}

bool EnumerateWindows(window_proc_t window_proc) {
	if (!window_proc)
		return false;

	const auto param = reinterpret_cast<LPARAM>(&window_proc);

	// Note that EnumWindows enumerates only top-level windows of desktop apps
	// (as opposed to UWP apps) on Windows 8 and above.
	return ::EnumWindows(EnumWindowsProc, param) != FALSE;
}

} // namespace animone::internal::win32
