/* 
   NSAutoreleasePool.m

   Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>

   This file is part of libFoundation.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

#include <Foundation/common.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSThread.h>
#include <Foundation/NSLock.h>

#include <Foundation/NSException.h>
#include <Foundation/exceptions/GeneralExceptions.h>

@implementation NSAutoreleasePool

/* 
 * Autorelease default parameters
 */

enum { CHUNK_SIZE = 1024 };

/*
 * Static variables
 */

// default pool (this should be per thread variable)
static NSAutoreleasePool* defaultPool = nil;
static BOOL isMultithreaded = NO;

// call trash when adding object multiple of non 0 trashLimit
static unsigned	trashLimit = 0;

// really send release to objects in pools
BOOL __autoreleaseEnableRelease = YES;

// checks at add time if object is too many times in autorelease pools
BOOL __autoreleaseEnableCheck = NO;
BOOL __autoreleaseEnableWarning = YES;

/* 
 * Instance initialization 
 */

+ (void)taskNowMultiThreaded:notification
{
    NSThread* thread = [NSThread currentThread];
    NSAutoreleasePool* pool;
    
    for (pool = defaultPool; pool; pool = pool->parentPool)
	pool->ownerThread = thread;
    
    [thread setThreadDefaultAutoreleasePool:defaultPool];
    defaultPool = nil;
    isMultithreaded = YES;
}

- init
{
    if (isMultithreaded) {
	ownerThread = [NSThread currentThread];
	parentPool = [ownerThread threadDefaultAutoreleasePool];
	[ownerThread setThreadDefaultAutoreleasePool:self];
    }
    else {
	parentPool = defaultPool;
	defaultPool = self;
	ownerThread = nil;
    }
    countOfObjects = 0;
    firstChunk = currentChunk = NULL;
    return self;
}

/* 
 * Instance deallocation 
 */

- (void)dealloc
{
    int i;
    NSAutoreleasePoolChunk*	ch;
    NSAutoreleasePool* pool;

    // What happens if someone pushes a pool in release ?
    // One idea would be to add a pool in its parent and
    // to extract if on deallocation but this would be inefficient
    // Another would be to check again to release child pools after
    // releasing all the objects in current pool

    // first release from top of autorelease stack to self
    while (YES) {
	if (isMultithreaded) {
	    if (ownerThread != [NSThread currentThread])
		THROW([[InvalidUseOfMethodException alloc] initWithFormat:
		    @"cannot release a NSAutoreleasePool in a different "
		    @"thread than that it was initialized in"]);
	    pool = [ownerThread threadDefaultAutoreleasePool];
	}
	else
	    pool = defaultPool;
	
	if (pool != self) {
	    [pool release];
	    continue;
	}
	else
	    break;
    }
    
    // send release to objects in pool
    if (__autoreleaseEnableRelease)
    {
	// first empty the pools (this may add new pools)
	for (ch = firstChunk; ch; ch = ch->next)
	    for (i = 0; i < ch->used; i++) 
	    {
		// warning: this may add objects in current pool
		// what happens if someone pushes a pool ?
		// one idea would be to add a pool in its parent and
		// to extract if on deallocation but this would be inefficient
		id obj = ch->objects[i];
		ch->objects[i] = 0;
		[obj release];
	    }
	// now pools are invalid an we should free them 
	// should we maintain a cache of allocated pools ?
	for (ch = firstChunk; ch;)
	{
	    NSAutoreleasePoolChunk* tmp = ch->next;
	    Free(ch);
	    ch = tmp;
	}
    }
    
    // set default pool
    if (isMultithreaded)
	[ownerThread setThreadDefaultAutoreleasePool:parentPool];
    else
	defaultPool = parentPool;
    
    [super dealloc];
}

/* 
 * Notes that aObject should be released when the pool 
 * at the current top of the stack is freed 
 * This is called by NSObject -autorelease.
 */

+ (void)addObject:aObject
{
    NSAutoreleasePool* pool = isMultithreaded ? 
	[[NSThread currentThread] threadDefaultAutoreleasePool] : defaultPool;
    
#if 0
    // check if there is a pool in effect
    if (!pool && __autoreleaseEnableWarning) {
	fprintf(stderr, 
	    "Autorelease[0x%08x] with no pool in effect\n", 
	    (int)aObject);
    }
#endif

    // check if retainCount is Ok
    if (__autoreleaseEnableCheck) {
	int toCome = [self autoreleaseCountForObject:aObject];
	if (toCome+1 > [aObject retainCount]) {
	    fprintf(stderr, 
		"Autorelease[0x%08x] release check for object has %d "
		" references and %d pending calls to release in autorelease\n", 
		(int)aObject, [aObject retainCount], toCome);
	    return;
	}
    }
    [pool addObject:aObject];
}

/* 
 * Notes that aObject must be released when pool is freed 
 */

- (void)addObject:aObject
{	
    // try to add in current chunk and alloc new chunk if neceessary
    if (!firstChunk || currentChunk->used >= currentChunk->size)
    {
	NSAutoreleasePoolChunk* ch;
	
	ch = Calloc(sizeof(NSAutoreleasePoolChunk)+CHUNK_SIZE*sizeof(id*), 1);
	ch->size = CHUNK_SIZE;
	ch->used = 0;
	ch->next = NULL;
	
	if (!firstChunk)
	    firstChunk = currentChunk = ch;
	else
	    currentChunk->next = ch, currentChunk = ch;
    }
    // add in currentChunk
    currentChunk->objects[(currentChunk->used)++] = aObject;
    countOfObjects++;
    
    // check threshold
    if (trashLimit) 
	if (countOfObjects % trashLimit == 0) 
	    [self trash];
}

/* 
 * Default pool 
 */

+ defaultPool
{
    return (isMultithreaded ? 
	[[NSThread currentThread] threadDefaultAutoreleasePool] : defaultPool);
}

/*
 * METHODS FOR DEBUGGING
 */

/* 
 * Counts how many times aObject willl receive -release due to all 
 * NSAutoreleasePools 
 */

+ (unsigned)autoreleaseCountForObject:aObject
{
    int count = 0;
    NSAutoreleasePool *pool;
    for (pool = defaultPool; pool; pool = pool->parentPool) {
	count += [pool autoreleaseCountForObject:aObject];
    }
    return count;
}

/* 
 * Counts how many times aObject willl receive -release due to this pool  
 */

- (unsigned)autoreleaseCountForObject:anObject
{
    int i, count = 0;
    NSAutoreleasePoolChunk*	ch;

    for (ch = firstChunk; ch; ch = ch->next)
	for (i = 0; i < ch->used; i++)
	    if (ch->objects[i] == anObject)
		count++;
    return count;
}

/* 
 * When enabled, -release and -autorelease calls are checking whether 
 * this object has been released too many times. This is done by searching 
 * all the pools, and makes programs run very slowly; 
 * It is off by default 
 */

+ (void)enableDoubleReleaseCheck:(BOOL)enable
{
    __autoreleaseEnableCheck = enable;
}

/* 
 * When disabled, nothing added to pools is really released; 
 * By default is enabled 
 */

+ (void)enableRelease:(BOOL)enable
{
    __autoreleaseEnableRelease = enable;
}

/* 
 * When enables call -trash on add if countToBeReleased % trashLimit = 0 
 */

+ (void)setPoolCountThreshhold:(unsigned int)trash
{
    trashLimit = trash;
}

/* 
 * Called on exceding trashLimit 
 */

- trash
{
    return self;
}

@end /* NSAutoreleasePool */

/*
 * Class that handles C pointers realease sending them Free()
 */

@implementation NSAutoreleasedPointer

+ (id)autoreleasePointer:(void*)address
{
    return [[[self alloc] initWithAddress:address] autorelease];
}

- initWithAddress:(void*)address
{
    theAddress = address;
    return self;
}

- (void)dealloc
{
    Free(theAddress);
    [super dealloc];
}

@end /* NSAutoreleasedPointer */
