/**
 * \file
 * ObjectiveC hacks to improve our changes with thread shutdown
 *
 * Author:
 *	Rodrigo Kumpera (kumpera@gmail.com)
 *
 * (C) 2014 Xamarin Inc
 */

#if defined(__MACH__)

#include "config.h"
#include <glib.h>
#include <stdio.h>
#include <objc/runtime.h>
#include <objc/message.h>
#include <mono/utils/mono-compiler.h>
#include <mono/utils/mono-publib.h>

/*
 * We cannot include mono-threads.h as this includes io-layer internal types
 * which conflicts with objc.
 * Hence the hack here.
*/
void mono_threads_init_dead_letter (void);
void mono_threads_install_dead_letter (void);
MONO_API void
mono_thread_info_detach (void);

static Class nsobject, nsthread, mono_dead_letter_class;
static SEL dealloc, release, currentThread, threadDictionary, init, alloc, objectForKey, setObjectForKey;
static id mono_dead_letter_key;

/*
 * Our Mach bindings have a problem in that they might need to attach
 * the runtime after the the user tls keys have been destroyed.
 *
 * This happens when a bound object is retained by NSThread, which is
 * released very late in the TLS cleanup process.
 *
 * At that point, attaching the runtime leaves us in no position to
 * detach it later using TLS destructors as pthread is done with user
 * keys. This leaves us with a dead thread registered, which can cause
 * all sorts of terrible problems.
 *
 * The tipical crash is when another thread is created at the exact
 * same address of the previous one, cause thread registration to abort
 * due to duplicate entries.
 *
 * So what do we do here?
 * 
 * Experimentation showns that threadDictionary is destroied after the
 * problematic keys, so we add our dead letter object as an aditional
 * way to be notified of thread death.
 */
static void
mono_dead_letter_dealloc (id self, SEL _cmd)
{
	struct objc_super super;
	super.receiver = self;
#if !defined(__cplusplus) && !__OBJC2__
	super.class = nsobject;
#else
	super.super_class = nsobject;
#endif
	void (*objc_msgSendSuper_op)(struct objc_super *, SEL) = (void (*)(struct objc_super *, SEL)) objc_msgSendSuper;
	objc_msgSendSuper_op (&super, dealloc);

	mono_thread_info_detach ();
}

void
mono_threads_install_dead_letter (void)
{
	id cur, dict;

	/*
	 * See the 'Dispatch Objective-C Messages Using the Method Function’s Prototype' section in
	 * the '64-Bit Transition Guide for Cocoa Touch' as to why this is required.
	 *
	 * It doesn't hurt on other architectures either, so no need to #ifdef it only for ARM64.
	 */

	id (*id_objc_msgSend)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;
	id (*id_objc_msgSend_id)(id, SEL, id) = (id (*)(id, SEL, id)) objc_msgSend;
	void (*objc_msgSend_id_id)(id, SEL, id, id) = (void (*)(id, SEL, id, id)) objc_msgSend;

	cur = id_objc_msgSend ((id)nsthread, currentThread);
	if (!cur)
		return;
	dict = id_objc_msgSend (cur, threadDictionary);
	if (dict && id_objc_msgSend_id (dict, objectForKey, mono_dead_letter_key) == nil) {
		id value = id_objc_msgSend (id_objc_msgSend ((id)mono_dead_letter_class, alloc), init);

		objc_msgSend_id_id (dict, setObjectForKey, value, mono_dead_letter_key);

		id_objc_msgSend (value, release);
	}
}

void
mono_threads_init_dead_letter (void)
{
	id nsstring = (id) objc_getClass ("NSString");
	id nsautoreleasepool = (id) objc_getClass ("NSAutoreleasePool");
	SEL stringWithUTF8String = sel_registerName ("stringWithUTF8String:");
	SEL retain = sel_registerName ("retain");
	id pool;

	nsthread = (Class)objc_getClass ("NSThread");
	nsobject = (Class)objc_getClass ("NSObject");

	init = sel_registerName ("init");
	alloc = sel_registerName ("alloc");
	release = sel_registerName ("release");
	dealloc = sel_registerName ("dealloc");
	

	currentThread = sel_registerName ("currentThread");
	threadDictionary = sel_registerName ("threadDictionary");
	setObjectForKey = sel_registerName ("setObject:forKey:");
	objectForKey = sel_registerName ("objectForKey:");

	// define the dead letter class
	mono_dead_letter_class = objc_allocateClassPair (nsobject, "MonoDeadLetter", 0);
	class_addMethod (mono_dead_letter_class, dealloc, (IMP)mono_dead_letter_dealloc, "v@:");
	objc_registerClassPair (mono_dead_letter_class);

	id (*id_objc_msgSend)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;

	// create the dict key
	pool = id_objc_msgSend (id_objc_msgSend (nsautoreleasepool, alloc), init);

	id (*objc_msgSend_char)(id, SEL, const char*) = (id (*)(id, SEL, const char*)) objc_msgSend;
	mono_dead_letter_key = objc_msgSend_char (nsstring, stringWithUTF8String, "mono-dead-letter");

	id_objc_msgSend (mono_dead_letter_key, retain);
	id_objc_msgSend (pool, release);
}
#endif
