#include "sys/osx/dark_theme.h"

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

#include <CoreFoundation/CoreFoundation.h>

#include <QOperatingSystemVersion>

namespace osx {

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 CFStringRef NSAppearanceNameAqua = nullptr;
static CFStringRef NSAppearanceNameDarkAqua = nullptr;

static const CFStringRef kAppKitBundleID = CFSTR("com.apple.AppKit");

bool RetrieveAppearanceNames() {
	CFBundleRef appkit_bundle = CFBundleGetBundleWithIdentifier(kAppKitBundleID);
	if (!appkit_bundle)
		return false;

	NSAppearanceNameAqua =
	    *reinterpret_cast<CFStringRef*>(CFBundleGetDataPointerForName(appkit_bundle, CFSTR("NSAppearanceNameAqua")));
	if (!NSAppearanceNameAqua)
		return false;

	NSAppearanceNameDarkAqua = *reinterpret_cast<CFStringRef*>(
	    CFBundleGetDataPointerForName(appkit_bundle, CFSTR("NSAppearanceNameDarkAqua")));
	if (!NSAppearanceNameDarkAqua)
		return false;

	return true;
}

bool DarkThemeAvailable() {
	return (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave);
}

bool IsInDarkTheme() {
	if (!DarkThemeAvailable())
		return false;

	if (!NSAppearanceNameAqua || !NSAppearanceNameDarkAqua)
		if (!RetrieveAppearanceNames())
			return false;

	// NSArray* array = @[NSAppearanceNameAqua, NSAppearanceNameDarkAqua];
	CFArrayRef array = []() -> CFArrayRef {
		CFStringRef refs[] = {NSAppearanceNameAqua, NSAppearanceNameDarkAqua};
		return CFArrayCreate(NULL, reinterpret_cast<const void**>(refs), 2, &kCFTypeArrayCallBacks);
	}();

	// NSApplication* app = [NSApplication sharedApplication];
	const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

	// NSAppearance* effectiveAppearance = [app effectiveAppearance];
	const id effectiveAppearance = obj_send(app, sel_getUid("effectiveAppearance"));
	if (!effectiveAppearance) {
		CFRelease(array);
		return false;
	}

	// NSAppearance* appearance = [effectiveAppearance bestMatchFromAppearancesWithNames: array];
	const id appearance = obj_send(effectiveAppearance, sel_getUid("bestMatchFromAppearancesWithNames:"), array);

	CFRelease(array);

	if (!appearance)
		return false;

	return CFEqual(appearance, NSAppearanceNameDarkAqua);
}

bool SetToDarkTheme() {
	// https://stackoverflow.com/questions/55925862/how-can-i-set-my-os-x-application-theme-in-code
	if (!DarkThemeAvailable())
		return false;

	if (!NSAppearanceNameAqua || !NSAppearanceNameDarkAqua)
		if (!RetrieveAppearanceNames())
			return false;

	// NSApplication* app = [NSApplication sharedApplication];
	const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

	// NSAppearance* appearance = [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua];
	const id appearance =
	    cls_send(objc_getClass("NSAppearance"), sel_getUid("appearanceNamed:"), NSAppearanceNameDarkAqua);
	if (!appearance)
		return false;

	// [app setAppearance: appearance];
	obj_send(app, sel_getUid("setAppearance:"), appearance);
	return true;
}

bool SetToLightTheme() {
	// https://stackoverflow.com/questions/55925862/how-can-i-set-my-os-x-application-theme-in-code
	if (!DarkThemeAvailable())
		return false;

	if (!NSAppearanceNameAqua || !NSAppearanceNameDarkAqua)
		if (!RetrieveAppearanceNames())
			return false;

	// NSApplication* app = [NSApplication sharedApplication];
	const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

	// NSAppearance* appearance = [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua];
	const id appearance = cls_send(objc_getClass("NSAppearance"), sel_getUid("appearanceNamed:"), NSAppearanceNameAqua);
	if (!appearance)
		return false;

	// [app setAppearance: appearance];
	obj_send(app, sel_getUid("setAppearance:"), appearance);
	return true;
}

void SetToAutoTheme() {
	if (!DarkThemeAvailable())
		return;

	// NSApplication* app = [NSApplication sharedApplication];
	const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

	// [app setAppearance: null];
	obj_send(app, sel_getUid("setAppearance:"), nullptr);
}

} // namespace osx
