/* 
   GarbageCollector.m

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

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>

   This file is part of the Foundation Extensions Library.

   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.
*/

#import <Foundation/NSArray.h>
#include <extensions/objc-runtime.h>
#include <extensions/GCObject.h>
#include <extensions/GarbageCollector.h>

@interface __DummyGCObject : GCObject
@end

@implementation __DummyGCObject
+ allocWithZone:(NSZone*)zone
{
    return NSAllocateObject(self, 0, zone);
}

- (void)dealloc
{
}
@end


/* The GCDoubleLinkedList is a double linked list which contains as the first
    element a dummy GCObject. This object is always the head of the list. A
    new element is introduced immediately after the head. This way we don't
    need to keep track of the head of the list when we remove an element. */
@interface GCDoubleLinkedList : NSObject
{
    id list;
}
- (void)addObject:(id)anObject;
- (void)removeObject:(id)anObject;
- (id)firstObject;
- (void)removeAllObjects;
@end

@implementation GCDoubleLinkedList
- init
{
    list = [__DummyGCObject new];
    return self;
}

- (void)addObject:(id)anObject
{
    id next = [list gcNextObject];

    [list gcSetNextObject:anObject];
    [anObject gcSetNextObject:next];
    [next gcSetPreviousObject:anObject];
    [anObject gcSetPreviousObject:list];
}

- (void)removeObject:(id)anObject
{
    id prev = [anObject gcPreviousObject];
    id next = [anObject gcNextObject];

    [prev gcSetNextObject:next];
    [next gcSetPreviousObject:prev];
}

- (id)firstObject
{
    return [list gcNextObject];
}

- (void)removeAllObjects
{
    [list gcSetNextObject:nil];
}
@end


@implementation GarbageCollector

static id gcObjectsToBeVisited;
static BOOL isGarbageCollecting = NO;

+ (void)initialize
{
    gcObjectsToBeVisited = [GCDoubleLinkedList new];
}

+ (void)collectGarbages
{
    id object;

    isGarbageCollecting = YES;

    /*  First pass. All the objects in the gcObjectsToBeVisited list
        receive the -decrementRefCount message. Each object should decrement
	the ref count of all objects contained. */
    object = [gcObjectsToBeVisited firstObject];
    while(object) {
	[object gcDecrementRefCountOfContainedObjects];
	[object gcSetVisited:NO];
	object = [object gcNextObject];
    };

    /*  Second pass. All the objects in the gcObjectsToBeVisited list that have
	the refCount greater than 0 receive the -incrementRefCount message.
	Each object should increment the ref count of all objects contained.
	Then it should send the -incrementRefCount message to all objects
	contained. */
    object = [gcObjectsToBeVisited firstObject];
    while(object) {
	if([object retainCount])
	    [object gcIncrementRefCountOfContainedObjects];
	object = [object gcNextObject];
    }

    /*  Third pass. All the objects that still have the refCount equal with 0
	are part of cyclic graphs and none of the objects from this graph
	are held by some object outside graph. These objects receive the
	-dealloc message. In this method they should send the -dealloc message
	to objects that are garbage collectable. An object could be asked if
	it is garbage collectable by sending it the -isGarbageCollectable
	message. */
    object = [gcObjectsToBeVisited firstObject];
    while(object) {
	if([object retainCount] == 0) {
	    id nextObject = [object gcNextObject];

	    /*  Remove object from gcObjectsToBeVisited list. We have to keep
		the old nextObject because after removing the object from list
		its nextObject is altered. */
	    [gcObjectsToBeVisited removeObject:object];
	    [object dealloc];
	    object = nextObject;
	}
	else object = [object gcNextObject];
    }

    isGarbageCollecting = NO;
}

+ (void)addObject:(id)anObject
{
    [gcObjectsToBeVisited addObject:anObject];
}

+ (void)objectWillBeDeallocated:(id)anObject
{
    /*  We can remove without fear the object from its list because the head of
	the list is always the same. */
    id prev = [anObject gcPreviousObject];
    id next = [anObject gcNextObject];
    [prev gcSetNextObject:next];
    [next gcSetPreviousObject:prev];
}

+ (BOOL)isGarbageCollecting		{ return isGarbageCollecting; }

@end
