/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "unistd_wrapper.h"

#ifdef OF_APPLE_RUNTIME
# include <dlfcn.h>
#endif

#ifdef HAVE_GETRANDOM
# include <sys/random.h>
#endif

#import "OFObject.h"
#import "OFArray.h"
#ifdef OF_HAVE_ATOMIC_OPS
# import "OFAtomic.h"
#endif
#import "OFLocale.h"
#import "OFMethodSignature.h"
#import "OFRunLoop.h"
#if !defined(OF_HAVE_ATOMIC_OPS) && defined(OF_HAVE_THREADS)
# import "OFPlainMutex.h"	/* For OFSpinlock */
#endif
#import "OFStdIOStream.h"
#import "OFString.h"
#import "OFThread.h"
#import "OFTimer.h"
#import "OFValue.h"

#import "OFAllocFailedException.h"
#import "OFEnumerationMutationException.h"
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFNotImplementedException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"

#if defined(OF_APPLE_RUNTIME) && __OBJC2__
# import <objc/objc-exception.h>
#elif defined(OF_OBJFW_RUNTIME)
# import "ObjFWRT.h"
#endif

#ifdef OF_WINDOWS
# include <windows.h>
#endif

#ifdef OF_AMIGAOS
# define Class IntuitionClass
# include <proto/exec.h>
# undef Class
#endif

#ifdef OF_APPLE_RUNTIME
extern id _Nullable _objc_rootAutorelease(id _Nullable object);
#endif
#if defined(OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR)
extern id OFForward(id, SEL, ...);
extern struct Stret OFForward_stret(id, SEL, ...);
#else
# define OFForward OFMethodNotFound
# define OFForward_stret OFMethodNotFound_stret
#endif

#ifdef OF_WINDOWS
static BOOLEAN NTAPI (*RtlGenRandomFuncPtr)(PVOID, ULONG);
#endif

struct PreIvars {
#ifdef OF_MSDOS
	ptrdiff_t offset;
#endif
	int retainCount;
#if !defined(OF_HAVE_ATOMIC_OPS) && !defined(OF_AMIGAOS)
	OFSpinlock retainCountSpinlock;
#endif
};

#define PRE_IVARS_ALIGN \
	OFRoundUpToPowerOf2(sizeof(struct PreIvars), OF_BIGGEST_ALIGNMENT)
#define PRE_IVARS ((struct PreIvars *)(void *)((char *)self - PRE_IVARS_ALIGN))

static struct {
	Class isa;
} allocFailedException;

unsigned long OFHashSeed;

void *
OFAllocMemory(size_t count, size_t size)
{
	void *pointer;

	if OF_UNLIKELY (count == 0 || size == 0)
		return NULL;

	if OF_UNLIKELY (count > SIZE_MAX / size)
		@throw [OFOutOfRangeException exception];

	if OF_UNLIKELY ((pointer = malloc(count * size)) == NULL)
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: size];

	return pointer;
}

void *
OFAllocZeroedMemory(size_t count, size_t size)
{
	void *pointer;

	if OF_UNLIKELY (count == 0 || size == 0)
		return NULL;

	/* Not all calloc implementations check for overflow. */
	if OF_UNLIKELY (count > SIZE_MAX / size)
		@throw [OFOutOfRangeException exception];

	if OF_UNLIKELY ((pointer = calloc(count, size)) == NULL)
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: size];

	return pointer;
}

void *
OFResizeMemory(void *pointer, size_t count, size_t size)
{
	if OF_UNLIKELY (count == 0 || size == 0) {
		free(pointer);
		return NULL;
	}

	if OF_UNLIKELY (count > SIZE_MAX / size)
		@throw [OFOutOfRangeException exception];

	if OF_UNLIKELY ((pointer = realloc(pointer, count * size)) == NULL)
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: size];

	return pointer;
}

void
OFFreeMemory(void *pointer)
{
	free(pointer);
}

#ifdef OF_MSDOS
/* Unfortunately, DJGPP's memalign() is broken. */

static void *
alignedAlloc(size_t size, size_t alignment, ptrdiff_t *offset)
{
	char *ptr, *aligned;

	if ((ptr = malloc(size + alignment)) == NULL)
		return NULL;

	aligned = (char *)OFRoundUpToPowerOf2(alignment, (uintptr_t)ptr);
	*offset = aligned - ptr;

	return aligned;
}

static void
alignedFree(void *ptr, ptrdiff_t offset)
{
	if (ptr == NULL)
		return;

	free((void *)((uintptr_t)ptr - offset));
}
#endif

#if (!defined(HAVE_ARC4RANDOM) && !defined(HAVE_GETRANDOM)) || \
    defined(OF_WINDOWS)
static OFOnceControl randomOnceControl = OFOnceControlInitValue;

static void
initRandom(void)
{
	struct timeval tv;

# ifdef OF_WINDOWS
	HANDLE handle;

	if ((handle = GetModuleHandleA("advapi32.dll")) != NULL &&
	    (RtlGenRandomFuncPtr = (BOOLEAN NTAPI (*)(PVOID, ULONG))
	    GetProcAddress(handle, "SystemFunction036")) != NULL)
		return;
# endif

# ifdef HAVE_RANDOM
	gettimeofday(&tv, NULL);
	srandom((unsigned)(tv.tv_sec ^ tv.tv_usec));
# else
	gettimeofday(&tv, NULL);
	srand((unsigned)(tv.tv_sec ^ tv.tv_usec));
# endif
}
#endif

uint16_t
OFRandom16(void)
{
#if defined(HAVE_ARC4RANDOM)
	return arc4random();
#elif defined(HAVE_GETRANDOM)
	uint16_t buffer;

	OFEnsure(getrandom(&buffer, sizeof(buffer), 0) == sizeof(buffer));

	return buffer;
#else
	OFOnce(&randomOnceControl, initRandom);

# ifdef OF_WINDOWS
	if (RtlGenRandomFuncPtr != NULL) {
		uint16_t buffer;
		OFEnsure(RtlGenRandomFuncPtr(&buffer, sizeof(buffer)));
		return buffer;
	}
# endif

# ifdef HAVE_RANDOM
	return random() & 0xFFFF;
# else
	return rand() & 0xFFFF;
# endif
#endif
}

uint32_t
OFRandom32(void)
{
#if defined(HAVE_ARC4RANDOM)
	return arc4random();
#elif defined(HAVE_GETRANDOM)
	uint32_t buffer;

	OFEnsure(getrandom(&buffer, sizeof(buffer), 0) == sizeof(buffer));

	return buffer;
#else
# ifdef OF_WINDOWS
	OFOnce(&randomOnceControl, initRandom);

	if (RtlGenRandomFuncPtr != NULL) {
		uint32_t buffer;
		OFEnsure(RtlGenRandomFuncPtr(&buffer, sizeof(buffer)));
		return buffer;
	}
# endif

	return ((uint32_t)OFRandom16() << 16) | OFRandom16();
#endif
}

uint64_t
OFRandom64(void)
{
#if defined(HAVE_ARC4RANDOM_BUF)
	uint64_t buffer;

	arc4random_buf(&buffer, sizeof(buffer));

	return buffer;
#elif defined(HAVE_GETRANDOM)
	uint64_t buffer;

	OFEnsure(getrandom(&buffer, sizeof(buffer), 0) == sizeof(buffer));

	return buffer;
#else
# ifdef OF_WINDOWS
	OFOnce(&randomOnceControl, initRandom);

	if (RtlGenRandomFuncPtr != NULL) {
		uint64_t buffer;
		OFEnsure(RtlGenRandomFuncPtr(&buffer, sizeof(buffer)));
		return buffer;
	}
# endif

	return ((uint64_t)OFRandom32() << 32) | OFRandom32();
#endif
}

void
OFHashInit(unsigned long *hash)
{
	*hash = OFHashSeed;
}

static const char *
typeEncodingForSelector(Class class, SEL selector)
{
	Method method;

	if ((method = class_getInstanceMethod(class, selector)) == NULL)
		return NULL;

	return method_getTypeEncoding(method);
}

#if !defined(OF_APPLE_RUNTIME) || defined(__OBJC2__)
static void
uncaughtExceptionHandler(id exception)
{
	OFArray OF_GENERIC(OFValue *) *stackTraceAddresses = nil;
	OFArray OF_GENERIC(OFString *) *stackTraceSymbols = nil;
	OFStringEncoding encoding = [OFLocale encoding];

	OFLog(@"Runtime error: Unhandled exception:");
	OFLog(@"%@", exception);

	if ([exception respondsToSelector: @selector(stackTraceAddresses)])
		stackTraceAddresses = [exception stackTraceAddresses];

	if (stackTraceAddresses != nil) {
		size_t count = stackTraceAddresses.count;

		if ([exception respondsToSelector:
		    @selector(stackTraceSymbols)])
			stackTraceSymbols = [exception stackTraceSymbols];

		if (stackTraceSymbols.count != count)
			stackTraceSymbols = nil;

		OFLog(@"");
		OFLog(@"Stack trace:");

		if (stackTraceSymbols != nil) {
			for (size_t i = 0; i < count; i++) {
				void *address = [[stackTraceAddresses
				    objectAtIndex: i] pointerValue];
				const char *symbol = [[stackTraceSymbols
				    objectAtIndex: i]
				    cStringWithEncoding: encoding];

				OFLog(@"  %p  %s", address, symbol);
			}
		} else {
			for (size_t i = 0; i < count; i++) {
				void *address = [[stackTraceAddresses
				    objectAtIndex: i] pointerValue];

				OFLog(@"  %p", address);
			}
		}
	}

	abort();
}
#endif

static void
enumerationMutationHandler(id object)
{
	@throw [OFEnumerationMutationException exceptionWithObject: object];
}

void OF_NO_RETURN_FUNC
OFMethodNotFound(id object, SEL selector)
{
	[object doesNotRecognizeSelector: selector];

	/*
	 * Just in case doesNotRecognizeSelector: returned, even though it must
	 * never return.
	 */
	abort();

	OF_UNREACHABLE
}

void OF_NO_RETURN_FUNC
OFMethodNotFound_stret(void *stret, id object, SEL selector)
{
	OFMethodNotFound(object, selector);
}

id
OFAllocObject(Class class, size_t extraSize, size_t extraAlignment,
    void **extra)
{
	OFObject *instance;
	size_t instanceSize;
#ifdef OF_MSDOS
	ptrdiff_t offset;
#endif

	instanceSize = class_getInstanceSize(class);

	if OF_UNLIKELY (extraAlignment > 1)
		extraAlignment = OFRoundUpToPowerOf2(extraAlignment,
		    PRE_IVARS_ALIGN + instanceSize) -
		    PRE_IVARS_ALIGN - instanceSize;

#if defined(OF_WINDOWS)
	instance = __mingw_aligned_malloc(PRE_IVARS_ALIGN + instanceSize +
	    extraAlignment + extraSize, OF_BIGGEST_ALIGNMENT);
#elif defined(OF_MSDOS)
	instance = alignedAlloc(PRE_IVARS_ALIGN + instanceSize +
	    extraAlignment + extraSize, OF_BIGGEST_ALIGNMENT, &offset);
#elif defined(OF_SOLARIS)
	if (posix_memalign((void **)&instance, OF_BIGGEST_ALIGNMENT,
	    PRE_IVARS_ALIGN + instanceSize + extraAlignment + extraSize) != 0)
		instance = NULL;
#else
	instance = malloc(PRE_IVARS_ALIGN + instanceSize +
	    extraAlignment + extraSize);
#endif

	if OF_UNLIKELY (instance == nil) {
		object_setClass((id)&allocFailedException,
		    [OFAllocFailedException class]);
		@throw (id)&allocFailedException;
	}

#ifdef OF_MSDOS
	((struct PreIvars *)instance)->offset = offset;
#endif
	((struct PreIvars *)instance)->retainCount = 1;

#if !defined(OF_HAVE_ATOMIC_OPS) && !defined(OF_AMIGAOS)
	if OF_UNLIKELY (OFSpinlockNew(
	    &((struct PreIvars *)instance)->retainCountSpinlock) != 0) {
# if defined(OF_WINDOWS)
		__mingw_aligned_free(instance);
# elif defined(OF_MSDOS)
		alignedFree(instance, offset);
# else
		free(instance);
# endif
		@throw [OFInitializationFailedException
		    exceptionWithClass: class];
	}
#endif

	instance = (OFObject *)(void *)((char *)instance + PRE_IVARS_ALIGN);

	memset(instance, 0, instanceSize + extraAlignment + extraSize);

	if (!objc_constructInstance(class, instance)) {
#if !defined(OF_HAVE_ATOMIC_OPS) && !defined(OF_AMIGAOS)
		OFSpinlockFree(&((struct PreIvars *)(void *)
		    ((char *)instance - PRE_IVARS_ALIGN))->retainCountSpinlock);
#endif
#if defined(OF_WINDOWS)
		__mingw_aligned_free((char *)instance - PRE_IVARS_ALIGN);
#elif defined(OF_MSDOS)
		alignedFree((char *)instance - PRE_IVARS_ALIGN, offset);
#else
		free((char *)instance - PRE_IVARS_ALIGN);
#endif
		@throw [OFInitializationFailedException
		    exceptionWithClass: class];
	}

	if OF_UNLIKELY (extra != NULL)
		*extra = (char *)instance + instanceSize + extraAlignment;

	return instance;
}

const char *
_NSPrintForDebugger(id object)
{
	return [[object description] cStringWithEncoding: [OFLocale encoding]];
}

/* References for static linking */
void OF_VISIBILITY_HIDDEN
_references_to_categories_of_OFObject(void)
{
	_OFObject_KeyValueCoding_reference = 1;
}

@implementation OFObject
+ (void)load
{
#if !defined(OF_APPLE_RUNTIME) || defined(__OBJC2__)
	objc_setUncaughtExceptionHandler(uncaughtExceptionHandler);
#endif

#if defined(OF_APPLE_RUNTIME)
	/*
	 * If the NSFoundationVersionNumber symbol is defined, we are linked
	 * against Foundation. Since CoreFoundation sets its own forward
	 * handler on load, we should not set ours, as this will break
	 * Foundation.
	 *
	 * Unfortunately, there is no way to check if a forward handler has
	 * already been set, so this is the best we can do.
	 */
	if (dlsym(RTLD_DEFAULT, "NSFoundationVersionNumber") == NULL)
		objc_setForwardHandler((void *)&OFForward,
		    (void *)&OFForward_stret);
#else
	objc_setForwardHandler((IMP)&OFForward, (IMP)&OFForward_stret);
#endif

	objc_setEnumerationMutationHandler(enumerationMutationHandler);

	do {
		OFHashSeed = OFRandom32();
	} while (OFHashSeed == 0);

#ifdef OF_OBJFW_RUNTIME
	objc_setTaggedPointerSecret(sizeof(uintptr_t) == 4
	    ? (uintptr_t)OFRandom32() : (uintptr_t)OFRandom64());
#endif
}

+ (void)unload
{
}

+ (void)initialize
{
}

+ (instancetype)alloc
{
	return OFAllocObject(self, 0, 0, NULL);
}

+ (Class)class
{
	return self;
}

+ (OFString *)className
{
	return [OFString stringWithCString: class_getName(self)
				  encoding: OFStringEncodingASCII];
}

+ (bool)isSubclassOfClass: (Class)class
{
	for (Class iter = self; iter != Nil; iter = class_getSuperclass(iter))
		if (iter == class)
			return true;

	return false;
}

+ (Class)superclass
{
	return class_getSuperclass(self);
}

+ (bool)instancesRespondToSelector: (SEL)selector
{
	return class_respondsToSelector(self, selector);
}

+ (bool)conformsToProtocol: (Protocol *)protocol
{
	for (Class iter = self; iter != Nil; iter = class_getSuperclass(iter))
		if (class_conformsToProtocol(iter, protocol))
			return true;

	return false;
}

+ (IMP)instanceMethodForSelector: (SEL)selector
{
	return class_getMethodImplementation(self, selector);
}

+ (OFMethodSignature *)instanceMethodSignatureForSelector: (SEL)selector
{
	const char *typeEncoding = typeEncodingForSelector(self, selector);

	if (typeEncoding == NULL)
		return nil;

	return [OFMethodSignature signatureWithObjCTypes: typeEncoding];
}

+ (OFString *)description
{
	return [self className];
}

+ (IMP)replaceClassMethod: (SEL)selector withMethodFromClass: (Class)class
{
	IMP method = [class methodForSelector: selector];

	if (method == NULL)
		@throw [OFInvalidArgumentException exception];

	return class_replaceMethod(object_getClass(self), selector, method,
	    typeEncodingForSelector(object_getClass(class), selector));
}

+ (IMP)replaceInstanceMethod: (SEL)selector withMethodFromClass: (Class)class
{
	IMP method = [class instanceMethodForSelector: selector];

	if (method == NULL)
		@throw [OFInvalidArgumentException exception];

	return class_replaceMethod(self, selector, method,
	    typeEncodingForSelector(class, selector));
}

+ (void)inheritMethodsFromClass: (Class)class
{
	Class superclass = [self superclass];
	Method *methodList;
	unsigned int count;

	if ([self isSubclassOfClass: class])
		return;

	methodList = class_copyMethodList(object_getClass(class), &count);
	@try {
		for (unsigned int i = 0; i < count; i++) {
			SEL selector = method_getName(methodList[i]);

			/*
			 * Don't replace methods implemented in the receiving
			 * class.
			 */
			if ([self methodForSelector: selector] !=
			    [superclass methodForSelector: selector])
				continue;

			[self replaceClassMethod: selector
			     withMethodFromClass: class];
		}
	} @finally {
		free(methodList);
	}

	methodList = class_copyMethodList(class, &count);
	@try {
		for (unsigned int i = 0; i < count; i++) {
			SEL selector = method_getName(methodList[i]);

			/*
			 * Don't replace methods implemented in the receiving
			 * class.
			 */
			if ([self instanceMethodForSelector: selector] !=
			    [superclass instanceMethodForSelector: selector])
				continue;

			[self replaceInstanceMethod: selector
				withMethodFromClass: class];
		}
	} @finally {
		free(methodList);
	}

	[self inheritMethodsFromClass: superclass];
}

+ (bool)resolveClassMethod: (SEL)selector
{
	return false;
}

+ (bool)resolveInstanceMethod: (SEL)selector
{
	return false;
}

- (instancetype)init
{
	return self;
}

- (Class)class
{
	return object_getClass(self);
}

- (Class)superclass
{
	return class_getSuperclass(object_getClass(self));
}

- (OFString *)className
{
	return [OFString stringWithCString: object_getClassName(self)
				  encoding: OFStringEncodingASCII];
}

- (bool)isKindOfClass: (Class)class
{
	for (Class iter = object_getClass(self); iter != Nil;
	    iter = class_getSuperclass(iter))
		if (iter == class)
			return true;

	return false;
}

- (bool)isMemberOfClass: (Class)class
{
	return (object_getClass(self) == class);
}

- (bool)respondsToSelector: (SEL)selector
{
	return class_respondsToSelector(object_getClass(self), selector);
}

- (bool)conformsToProtocol: (Protocol *)protocol
{
	return [object_getClass(self) conformsToProtocol: protocol];
}

- (IMP)methodForSelector: (SEL)selector
{
	return class_getMethodImplementation(object_getClass(self), selector);
}

- (id)performSelector: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME)
	id (*imp)(id, SEL) = (id (*)(id, SEL))objc_msg_lookup(self, selector);
#elif defined(OF_APPLE_RUNTIME)
	id (*imp)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
#endif

	return imp(self, selector);
}

- (id)performSelector: (SEL)selector withObject: (id)object
{
#if defined(OF_OBJFW_RUNTIME)
	id (*imp)(id, SEL, id) =
	    (id (*)(id, SEL, id))objc_msg_lookup(self, selector);
#elif defined(OF_APPLE_RUNTIME)
	id (*imp)(id, SEL, id) = (id (*)(id, SEL, id))objc_msgSend;
#endif

	return imp(self, selector, object);
}

- (id)performSelector: (SEL)selector
	   withObject: (id)object1
	   withObject: (id)object2
{
#if defined(OF_OBJFW_RUNTIME)
	id (*imp)(id, SEL, id, id) =
	    (id (*)(id, SEL, id, id))objc_msg_lookup(self, selector);
#elif defined(OF_APPLE_RUNTIME)
	id (*imp)(id, SEL, id, id) = (id (*)(id, SEL, id, id))objc_msgSend;
#endif

	return imp(self, selector, object1, object2);
}

- (id)performSelector: (SEL)selector
	   withObject: (id)object1
	   withObject: (id)object2
	   withObject: (id)object3
{
#if defined(OF_OBJFW_RUNTIME)
	id (*imp)(id, SEL, id, id, id) =
	    (id (*)(id, SEL, id, id, id))objc_msg_lookup(self, selector);
#elif defined(OF_APPLE_RUNTIME)
	id (*imp)(id, SEL, id, id, id) =
	    (id (*)(id, SEL, id, id, id))objc_msgSend;
#endif

	return imp(self, selector, object1, object2, object3);
}

- (id)performSelector: (SEL)selector
	   withObject: (id)object1
	   withObject: (id)object2
	   withObject: (id)object3
	   withObject: (id)object4
{
#if defined(OF_OBJFW_RUNTIME)
	id (*imp)(id, SEL, id, id, id, id) =
	    (id (*)(id, SEL, id, id, id, id))objc_msg_lookup(self, selector);
#elif defined(OF_APPLE_RUNTIME)
	id (*imp)(id, SEL, id, id, id, id) =
	    (id (*)(id, SEL, id, id, id, id))objc_msgSend;
#endif

	return imp(self, selector, object1, object2, object3, object4);
}

- (void)performSelector: (SEL)selector afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[OFTimer scheduledTimerWithTimeInterval: delay
					 target: self
				       selector: selector
					repeats: false];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	     withObject: (id)object
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[OFTimer scheduledTimerWithTimeInterval: delay
					 target: self
				       selector: selector
					 object: object
					repeats: false];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	     withObject: (id)object1
	     withObject: (id)object2
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[OFTimer scheduledTimerWithTimeInterval: delay
					 target: self
				       selector: selector
					 object: object1
					 object: object2
					repeats: false];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	     withObject: (id)object1
	     withObject: (id)object2
	     withObject: (id)object3
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[OFTimer scheduledTimerWithTimeInterval: delay
					 target: self
				       selector: selector
					 object: object1
					 object: object2
					 object: object3
					repeats: false];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	     withObject: (id)object1
	     withObject: (id)object2
	     withObject: (id)object3
	     withObject: (id)object4
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[OFTimer scheduledTimerWithTimeInterval: delay
					 target: self
				       selector: selector
					 object: object1
					 object: object2
					 object: object3
					 object: object4
					repeats: false];

	objc_autoreleasePoolPop(pool);
}

#ifdef OF_HAVE_THREADS
- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	  waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						repeats: false];
	[thread.runLoop addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object
	  waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object
						repeats: false];
	[thread.runLoop addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object1
	     withObject: (id)object2
	  waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object1
						 object: object2
						repeats: false];
	[thread.runLoop addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object1
	     withObject: (id)object2
	     withObject: (id)object3
	  waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object1
						 object: object2
						 object: object3
						repeats: false];
	[thread.runLoop addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object1
	     withObject: (id)object2
	     withObject: (id)object3
	     withObject: (id)object4
	  waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object1
						 object: object2
						 object: object3
						 object: object4
						repeats: false];
	[thread.runLoop addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelectorOnMainThread: (SEL)selector
		      waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						repeats: false];
	[[OFRunLoop mainRunLoop] addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelectorOnMainThread: (SEL)selector
			 withObject: (id)object
		      waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object
						repeats: false];
	[[OFRunLoop mainRunLoop] addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelectorOnMainThread: (SEL)selector
			 withObject: (id)object1
			 withObject: (id)object2
		      waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object1
						 object: object2
						repeats: false];
	[[OFRunLoop mainRunLoop] addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelectorOnMainThread: (SEL)selector
			 withObject: (id)object1
			 withObject: (id)object2
			 withObject: (id)object3
		      waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object1
						 object: object2
						 object: object3
						repeats: false];
	[[OFRunLoop mainRunLoop] addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelectorOnMainThread: (SEL)selector
			 withObject: (id)object1
			 withObject: (id)object2
			 withObject: (id)object3
			 withObject: (id)object4
		      waitUntilDone: (bool)waitUntilDone
{
	void *pool = objc_autoreleasePoolPush();
	OFTimer *timer = [OFTimer timerWithTimeInterval: 0
						 target: self
					       selector: selector
						 object: object1
						 object: object2
						 object: object3
						 object: object4
						repeats: false];
	[[OFRunLoop mainRunLoop] addTimer: timer];

	if (waitUntilDone)
		[timer waitUntilDone];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[thread.runLoop addTimer: [OFTimer timerWithTimeInterval: delay
							  target: self
							selector: selector
							 repeats: false]];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[thread.runLoop addTimer: [OFTimer timerWithTimeInterval: delay
							  target: self
							selector: selector
							  object: object
							 repeats: false]];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object1
	     withObject: (id)object2
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[thread.runLoop addTimer: [OFTimer timerWithTimeInterval: delay
							  target: self
							selector: selector
							  object: object1
							  object: object2
							 repeats: false]];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object1
	     withObject: (id)object2
	     withObject: (id)object3
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[thread.runLoop addTimer: [OFTimer timerWithTimeInterval: delay
							  target: self
							selector: selector
							  object: object1
							  object: object2
							  object: object3
							 repeats: false]];

	objc_autoreleasePoolPop(pool);
}

- (void)performSelector: (SEL)selector
	       onThread: (OFThread *)thread
	     withObject: (id)object1
	     withObject: (id)object2
	     withObject: (id)object3
	     withObject: (id)object4
	     afterDelay: (OFTimeInterval)delay
{
	void *pool = objc_autoreleasePoolPush();

	[thread.runLoop addTimer: [OFTimer timerWithTimeInterval: delay
							  target: self
							selector: selector
							  object: object1
							  object: object2
							  object: object3
							  object: object4
							 repeats: false]];

	objc_autoreleasePoolPop(pool);
}
#endif

- (OFMethodSignature *)methodSignatureForSelector: (SEL)selector
{
	const char *typeEncoding =
	    typeEncodingForSelector(object_getClass(self), selector);

	if (typeEncoding == NULL)
		return nil;

	return [OFMethodSignature signatureWithObjCTypes: typeEncoding];
}

- (bool)isEqual: (id)object
{
	return (object == self);
}

- (unsigned long)hash
{
	uintptr_t ptr = (uintptr_t)self;
	unsigned long hash;

	OFHashInit(&hash);

	for (size_t i = 0; i < sizeof(ptr); i++) {
		OFHashAddByte(&hash, ptr & 0xFF);
		ptr >>= 8;
	}

	OFHashFinalize(&hash);

	return hash;
}

- (OFString *)description
{
	/* Classes containing data should reimplement this! */

	return [OFString stringWithFormat: @"<%@>", self.className];
}

- (id)forwardingTargetForSelector: (SEL)selector
{
	return nil;
}

- (void)doesNotRecognizeSelector: (SEL)selector
{
	@throw [OFNotImplementedException exceptionWithSelector: selector
							 object: self];
}

- (instancetype)retain
{
#if defined(OF_HAVE_ATOMIC_OPS)
	OFAtomicIntIncrease(&PRE_IVARS->retainCount);
#elif defined(OF_AMIGAOS)
	/*
	 * On AmigaOS, we can only have one CPU. As increasing a variable is a
	 * single instruction on M68K, we don't need Forbid() / Permit() on
	 * M68K.
	 */
# ifndef OF_AMIGAOS_M68K
	Forbid();
# endif
	PRE_IVARS->retainCount++;
# ifndef OF_AMIGAOS_M68K
	Permit();
# endif
#else
	OFEnsure(OFSpinlockLock(&PRE_IVARS->retainCountSpinlock) == 0);
	PRE_IVARS->retainCount++;
	OFEnsure(OFSpinlockUnlock(&PRE_IVARS->retainCountSpinlock) == 0);
#endif

	return self;
}

- (unsigned int)retainCount
{
	OFAssert(PRE_IVARS->retainCount >= 0);
	return PRE_IVARS->retainCount;
}

- (void)release
{
#if defined(OF_HAVE_ATOMIC_OPS)
	OFReleaseMemoryBarrier();

	if (OFAtomicIntDecrease(&PRE_IVARS->retainCount) <= 0) {
		OFAcquireMemoryBarrier();

		[self dealloc];
	}
#elif defined(OF_AMIGAOS)
	int retainCount;

	Forbid();
	retainCount = --PRE_IVARS->retainCount;
	Permit();

	if (retainCount == 0)
		[self dealloc];
#else
	int retainCount;

	OFEnsure(OFSpinlockLock(&PRE_IVARS->retainCountSpinlock) == 0);
	retainCount = --PRE_IVARS->retainCount;
	OFEnsure(OFSpinlockUnlock(&PRE_IVARS->retainCountSpinlock) == 0);

	if (retainCount == 0)
		[self dealloc];
#endif
}

- (instancetype)autorelease
{
	return _objc_rootAutorelease(self);
}

- (instancetype)self
{
	return self;
}

- (bool)isProxy
{
	return false;
}

- (bool)allowsWeakReference
{
	return true;
}

- (bool)retainWeakReference
{
	[self retain];

	return true;
}

- (void)dealloc
{
	objc_destructInstance(self);

#if !defined(OF_HAVE_ATOMIC_OPS) && !defined(OF_AMIGAOS)
	OFSpinlockFree(&PRE_IVARS->retainCountSpinlock);
#endif

#if defined(OF_WINDOWS)
	__mingw_aligned_free((char *)self - PRE_IVARS_ALIGN);
#elif defined(OF_MSDOS)
	alignedFree((char *)self - PRE_IVARS_ALIGN, PRE_IVARS->offset);
#else
	free((char *)self - PRE_IVARS_ALIGN);
#endif
}

/* Required to use properties with the Apple runtime */
- (id)copyWithZone: (void *)zone
{
	if OF_UNLIKELY (zone != NULL) {
		[self doesNotRecognizeSelector: _cmd];
		abort();
	}

	return [(id)self copy];
}

- (id)mutableCopyWithZone: (void *)zone
{
	if OF_UNLIKELY (zone != NULL) {
		[self doesNotRecognizeSelector: _cmd];
		abort();
	}

	return [(id)self mutableCopy];
}

/*
 * The following are needed as the root class is the superclass of the root
 * class's metaclass and thus instance methods can be sent to class objects as
 * well.
 */

+ (id)retain
{
	return self;
}

+ (id)autorelease
{
	return self;
}

+ (unsigned int)retainCount
{
	return OFMaxRetainCount;
}

+ (void)release
{
}

+ (void)dealloc
{
	OF_UNRECOGNIZED_SELECTOR
}

+ (id)copy
{
	return self;
}

+ (id)mutableCopyWithZone: (void *)zone
{
	OF_UNRECOGNIZED_SELECTOR
}

/* Required to use ObjFW from Swift */
+ (instancetype)allocWithZone: (void *)zone
{
	if OF_UNLIKELY (zone != NULL) {
		[self doesNotRecognizeSelector: _cmd];
		abort();
	}

	return [self alloc];
}
@end
