/*
 * Copyright (c) 2008, 2009, 2010, 2011
 *   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.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <stdlib.h>

#import "OFAutoreleasePool.h"
#import "OFArray.h"

#import "OFInitializationFailedException.h"
#import "OFNotImplementedException.h"

#ifdef OF_THREADS
# import "threading.h"
static of_tlskey_t firstKey, lastKey;
#else
static OFAutoreleasePool *firstPool = nil, *lastPool = nil;
#endif

#define GROW_SIZE 16

@implementation OFAutoreleasePool
#ifdef OF_THREADS
+ (void)initialize
{
	if (self != [OFAutoreleasePool class])
		return;

	if (!of_tlskey_new(&firstKey) || !of_tlskey_new(&lastKey))
		@throw [OFInitializationFailedException newWithClass: self];
}
#endif

+ (void)addObject: (id)object
{
#ifdef OF_THREADS
	id lastPool = of_tlskey_get(lastKey);
#endif

	if (lastPool == nil) {
		@try {
			[[self alloc] init];
		} @catch (id e) {
			[object release];
			@throw e;
		}

#ifdef OF_THREADS
		lastPool = of_tlskey_get(lastKey);
#endif
	}

	if (lastPool == nil) {
		[object release];
		@throw [OFInitializationFailedException newWithClass: self];
	}

	@try {
		[lastPool addObject: object];
	} @catch (id e) {
		[object release];
		@throw e;
	}
}

+ (void)releaseAll
{
#ifdef OF_THREADS
	[of_tlskey_get(firstKey) release];
#else
	[firstPool release];
#endif
}

- init
{
	self = [super init];

	@try {
#ifdef OF_THREADS
		id firstPool = of_tlskey_get(firstKey);
		previousPool = of_tlskey_get(lastKey);

		if (!of_tlskey_set(lastKey, self))
			@throw [OFInitializationFailedException
			    newWithClass: isa];
#else
		previousPool = lastPool;
		lastPool = self;
#endif

		if (firstPool == nil) {
#ifdef OF_THREADS
			if (!of_tlskey_set(firstKey, self)) {
				of_tlskey_set(lastKey, previousPool);
				@throw [OFInitializationFailedException
				    newWithClass: isa];
			}
#else
			firstPool = self;
#endif
		}

		if (previousPool != nil)
			previousPool->nextPool = self;

		size = GROW_SIZE;
		objects = [self allocMemoryForNItems: GROW_SIZE
					      ofSize: sizeof(id)];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)addObject: (id)object
{
	if (count + 1 > size) {
		objects = [self resizeMemory: objects
				    toNItems: size + GROW_SIZE
				      ofSize: sizeof(id)];
		size += GROW_SIZE;
	}

	objects[count] = object;
	count++;
}

- (void)releaseObjects
{
	size_t i;

	[nextPool releaseObjects];

	for (i = 0; i < count; i++)
		[objects[i] release];

	count = 0;
}

- (void)release
{
	[self dealloc];
}

- (void)drain
{
	[self dealloc];
}

- (void)dealloc
{
	size_t i;

	[nextPool dealloc];

	for (i = 0; i < count; i++)
		[objects[i] release];

	/*
	 * If of_tlskey_set fails, this is a real problem. The best we can do
	 * is to not change the pool below the current pool and stop
	 * deallocation. This way, new objects will be added to the current
	 * pool, but released when the pool below gets released - and maybe
	 * the pool itself will be released as well then, because maybe
	 * of_tlskey_set will work this time.
	 */
#ifdef OF_THREADS
	if (!of_tlskey_set(lastKey, previousPool))
		return;
#else
	lastPool = previousPool;
#endif

	if (previousPool != nil)
		previousPool->nextPool = nil;

	/*
	 * If of_tlskey_set fails here, this is even worse, as this will
	 * definitely be a memory leak. But this should never happen anyway.
	 */
#ifdef OF_THREADS
	if (of_tlskey_get(firstKey) == self)
		if (!of_tlskey_set(firstKey, nil))
			return;
#else
	if (firstPool == self)
		firstPool = nil;
#endif

	[super dealloc];
}

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

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