/*
 * Copyright (c) 2008 - 2009
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE included in
 * the packaging of this file.
 */

#include "config.h"

#import "OFThread.h"
#import "OFList.h"
#import "OFAutoreleasePool.h"
#import "OFExceptions.h"

#import "threading.h"

static OFList *tlskeys;

static id
call_main(id obj)
{
	/*
	 * Nasty workaround for thread implementations which can't return a
	 * value on join.
	 */
	((OFThread*)obj)->retval = [obj main];

	[OFTLSKey callAllDestructors];
	[OFAutoreleasePool releaseAll];

	return 0;
}

@implementation OFThread
+ threadWithObject: (id)obj
{
	return [[[self alloc] initWithObject: obj] autorelease];
}

+ setObject: (id)obj
  forTLSKey: (OFTLSKey*)key
{
	id old = of_tlskey_get(key->key);

	if (!of_tlskey_set(key->key, [obj retain]))
		@throw [OFInvalidArgumentException newWithClass: self
						       selector: _cmd];

	[old release];

	return self;
}

+ (id)objectForTLSKey: (OFTLSKey*)key
{
	return [[of_tlskey_get(key->key) retain] autorelease];
}

- init
{
	@throw [OFNotImplementedException newWithClass: isa
					      selector: _cmd];
}

- initWithObject: (id)obj
{
	self = [super init];
	object = [obj copy];

	if (!of_thread_new(&thread, call_main, self)) {
		Class c = isa;
		[object release];
		[super dealloc];
		@throw [OFInitializationFailedException newWithClass: c];
	}

	running = YES;

	return self;
}

- main
{
	return nil;
}

- join
{
	of_thread_join(thread);
	running = NO;

	return retval;
}

- (void)dealloc
{
	/*
	 * No need to handle errors - if canceling the thread fails, we can't
	 * do anything anyway. Most likely, it finished already or was already
	 * canceled.
	 */
	if (running)
		of_thread_cancel(thread);

	[object release];
	[super dealloc];
}
@end

@implementation OFTLSKey
+ (void)initialize
{
	if (self == [OFTLSKey class])
		tlskeys = [[OFList alloc] init];
}

+ tlsKey
{
	return [[[self alloc] init] autorelease];
}

+ tlsKeyWithDestructor: (void(*)(id))destructor
{
	return [[[self alloc] initWithDestructor: destructor] autorelease];
}

+ (void)callAllDestructors
{
	of_list_object_t *iter;

	@synchronized (tlskeys) {
		for (iter = [tlskeys first]; iter != NULL; iter = iter->next)
			((OFTLSKey*)iter->object)->destructor(iter->object);
	}
}

- init
{
	self = [super init];

	if (!of_tlskey_new(&key)) {
		Class c = isa;
		[super dealloc];
		@throw [OFInitializationFailedException newWithClass: c];
	}

	destructor = NULL;

	@synchronized (tlskeys) {
		@try {
			listobj = [tlskeys append: self];
		} @catch (OFException *e) {
			/*
			 * We can't use [super dealloc] on OS X here.
			 * Compiler bug? Anyway, [self dealloc] will do here
			 * as we check listobj != NULL in dealloc.
			 */
			listobj = NULL;
			[self dealloc];
			@throw e;
		}
	}

	return self;
}

- initWithDestructor: (void(*)(id))destructor_
{
	self = [self init];

	destructor = destructor_;

	return self;
}

- (void)dealloc
{
	if (destructor != NULL)
		destructor(self);

	of_tlskey_free(key);

	@synchronized (tlskeys) {
		/* In case we called [self dealloc] in init */
		if (listobj != NULL)
			[tlskeys remove: listobj];
	}

	[super dealloc];
}
@end

@implementation OFMutex
+ mutex
{
	return [[[self alloc] init] autorelease];
}

- init
{
	self = [super init];

	if (!of_mutex_new(&mutex)) {
		Class c = isa;
		[self dealloc];
		@throw [OFInitializationFailedException newWithClass: c];
	}

	return self;
}

- lock
{
	if (!of_mutex_lock(&mutex))
		@throw [OFMutexLockFailedException newWithClass: isa];

	return self;
}

- unlock
{
	if (!of_mutex_unlock(&mutex))
		@throw [OFMutexUnlockFailedException newWithClass: isa];

	return self;
}

- (void)dealloc
{
	of_mutex_free(&mutex);

	[super dealloc];
}
@end
