#include "sys/osx/dark_theme.h"

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

#include <CoreFoundation/CoreFoundation.h>

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;

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

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

	return true;
}

bool DarkThemeAvailable()
{
	if (__builtin_available(macOS 10.14, *)) {
		return true;
	} else {
		return false;
	}
}

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"));
	if (!app)
		return false;

	// 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"));
	if (!app)
		return false;

	// 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"));
	if (!app)
		return false;

	// 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"));
	if (!app)
		return;

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

} // namespace osx
