#include "animia/win/quartz.h"
#include "animia/util/osx.h"
#include "animia.h"

#include <objc/runtime.h>
#include <objc/message.h>

#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>

#include <iostream>

namespace animia::internal::quartz {

typedef id (*object_message_send)(id, SEL, ...);
typedef id (*class_message_send)(Class, SEL, ...);

static const object_message_send obj_send = reinterpret_cast<object_message_send>(objc_msgSend);
static const class_message_send cls_send = reinterpret_cast<class_message_send>(objc_msgSend);

static bool GetWindowTitle(unsigned int wid, std::string& result) {
	// NSApplication* app = [NSApplication sharedApplication];
	id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

	// NSWindow* window = [app windowWithWindowNumber: wid];
	id window = obj_send(app, sel_getUid("windowWithWindowNumber:"), wid);
	if (!window)
		return false;

	// NSString* title = [window title];
	// does this have to be freed?
	CFStringRef title = reinterpret_cast<CFStringRef>(obj_send(window, sel_getUid("title")));
	if (!title)
		return false;

	// return [title UTF8String];
	return osx::util::StringFromCFString(title, result);
}

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

	const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
	if (!windows)
		return false;

	const CFIndex count = CFArrayGetCount(windows);
	for (CFIndex i = 0; i < count; i++) {
		CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windows, i));
		if (!window)
			continue;

		Process proc;
		{
			{
				CFNumberRef num = nullptr;
				if (CFDictionaryGetValueIfPresent(window, CFSTR("kCGWindowOwnerPID"), reinterpret_cast<const void**>(&num)) && num)
					osx::util::GetCFNumber(num, proc.pid);
			}
			{
				CFStringRef str = nullptr;
				if (CFDictionaryGetValueIfPresent(window, CFSTR("kCGWindowOwnerName"), reinterpret_cast<const void**>(&str)) && str)
					osx::util::StringFromCFString(str, proc.name);
			}
			if (proc.name.empty())
				osx::util::GetProcessName(proc.pid, proc.name);
		}

		Window win;
		{
			{
				CFNumberRef num = nullptr;
				if (CFDictionaryGetValueIfPresent(window, CFSTR("kCGWindowNumber"), reinterpret_cast<const void**>(&num)) && num)
					osx::util::GetCFNumber(num, win.id);
			}
			{
				CFStringRef str = nullptr;
				if (CFDictionaryGetValueIfPresent(window, CFSTR("kCGWindowName"), reinterpret_cast<const void**>(&str)) && str)
					osx::util::StringFromCFString(str, win.class_name);
			}
			GetWindowTitle(win.id, win.text);
		}

		if (!window_proc(proc, win)) {
			CFRelease(windows);
			return false;
		}
	}

	CFRelease(windows);

	return true;
}

} // namespace animia::win::detail
