/* 
   Copyright (C) 1996 
	Ovidiu Predescu <ovidiu@bx.logicnet.ro>
	Mircea Oancea <mircea@jupiter.elcom.pub.ro>

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

   This file is part of the FoundationExtensions library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <config.h>

#include "common.h"
#include <extensions/objc-runtime.h>
#include <extensions/GCArray.h>
#include <extensions/GCObject.h>
#include <extensions/GarbageCollector.h>
#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>

/*
 * TODO: copyWithZone:
 */

@implementation GCArray

+ (void)initialize
{
    static BOOL initialized = NO;

    if(!initialized) {
	initialized = YES;
	class_add_behavior(self, [GCObject class]);
    }
}

- (id)initWithObjects:(id*)objects count:(unsigned int)count
{
    int i;

    items = Calloc(count, sizeof(id));
    isGarbageCollectable = Calloc(count, sizeof(BOOL));
    itemsCount = count;
    for (i=0; i < count; i++)
	if (!(items[i] = [objects[i] retain]))
	    THROW([[InvalidArgumentException alloc] 
		    initWithReason:@"Nil object to be added in array"]);
	else isGarbageCollectable[i] = [objects[i] isGarbageCollectable];
    return self;
}

- (id)initWithArray:(NSArray*)anotherArray
{
    int i, count = [anotherArray count];

    items = Calloc(count, sizeof(id));
    isGarbageCollectable = Calloc(count, sizeof(BOOL));
    itemsCount = count;
    for (i=0; i < itemsCount; i++) {
	items[i] = [[anotherArray objectAtIndex:i] retain];
	isGarbageCollectable[i] = [items[i] isGarbageCollectable];
    }
    return self;
}

- (void)dealloc
{
    unsigned int index;

    if([GarbageCollector isGarbageCollecting]) {
	for (index = 0; index < itemsCount; index++)
	    if(!isGarbageCollectable[index])
		[items[index] release];
    }
    else {
	for (index = 0; index < itemsCount; index++)
	    [items[index] release];
    }

    Free(items);
    Free(isGarbageCollectable);
    [super dealloc];
}

- (id)copy
{
    return [self copyWithZone:NSDefaultMallocZone()];
}

- (id)copyWithZone:(NSZone*)zone
{
    if ([self zone] == zone)
	return [self retain];
    return [[isa alloc] initWithObjects:items count:itemsCount];
}

- (id)mutableCopy
{
    return [self mutableCopyWithZone:NSDefaultMallocZone()];
}

- (id)mutableCopyWithZone:(NSZone*)zone
{
    return [[GCMutableArray allocWithZone:zone] 
	    initWithObjects:items count:itemsCount];
}

- (id)objectAtIndex:(unsigned int)index
{
    if (index >= itemsCount)
	THROW([[RangeException alloc] 
		initWithReason:@"objectAtIndex: in NSArray" 
		size:itemsCount index:index]);
    return items[index];
}

- (unsigned int)count
{
    return itemsCount;
}

- (unsigned int)indexOfObjectIdenticalTo:(id)anObject
{
    int i;
    for (i = 0; i < itemsCount; i++)
	if (items[i] == anObject)
		return i;
    return NSNotFound;
}

- (void)gcDecrementRefCountOfContainedObjects
{
    int i, count;

    for (i = 0, count = [self count]; i < count; i++)
	if (isGarbageCollectable[i])
	    [[self objectAtIndex:i] gcDecrementRefCount];
}

- (BOOL)gcIncrementRefCountOfContainedObjects
{
    int i, count;

    if ([(id)self gcAlreadyVisited])
	return NO;
    [(id)self gcSetVisited:YES];

    for (i = 0, count = [self count]; i < count; i++)
	if(isGarbageCollectable[i]) {
	    id object = [self objectAtIndex:i];
	    [object gcIncrementRefCount];
	    [object gcIncrementRefCountOfContainedObjects];
	}
    return YES;
}

@end /* GCArray */


@implementation GCMutableArray

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized) {
	initialized = YES;
	class_add_behavior(self, [GCArray class]);
    }
}

- (id)init
{
    return [self initWithCapacity:1];
}

- (id)initWithCapacity:(unsigned int)aNumItems
{
    items = Calloc(aNumItems, sizeof(id));
    isGarbageCollectable = Calloc(aNumItems, sizeof(BOOL));
    maxItems = aNumItems;
    itemsCount = 0;
    return self;
}

- (id)initWithObjects:(id *)objects count:(unsigned int)count
{
    int i;

    items = Calloc(count, sizeof(id));
    isGarbageCollectable = Calloc(count, sizeof(BOOL));
    maxItems = itemsCount = count;

    for (i=0; i < count; i++)
	if (!(items[i] = [objects[i] retain]))
	    THROW([[InvalidArgumentException alloc] 
		    initWithReason:@"Nil object to be added in array"]);
	else isGarbageCollectable[i] = [objects[i] isGarbageCollectable];
    return self;
}

- (id)copyWithZone:(NSZone*)zone
{
    return [[GCArray allocWithZone:zone] 
	    initWithObjects:items count:itemsCount];
}

- (id)mutableCopyWithZone:(NSZone*)zone
{
    return [[GCMutableArray allocWithZone:zone] 
	    initWithObjects:items count:itemsCount];
}

- (void)insertObject:(id)anObject atIndex:(unsigned int)index
{
    unsigned int i;
    if (!anObject)
	THROW([[InvalidArgumentException alloc] 
		initWithReason:@"Nil object to be added in array"]);
    if (index > itemsCount)
	THROW([[RangeException alloc] 
		initWithReason:@"insertObject:atIndex: in GCMutableArray" 
		size:itemsCount index:index]);
    if (itemsCount == maxItems) {
	if (maxItems) {
	    maxItems += (maxItems >> 1) ? (maxItems >>1) : 1;
	}
	else {
	    maxItems = 1;
	}
	items = (id*)Realloc(items, sizeof(id) * maxItems);
	isGarbageCollectable = (BOOL*)Realloc(isGarbageCollectable,
					      sizeof(BOOL) * maxItems);
    }
    for(i = itemsCount; i > index; i--) {
	items[i] = items[i - 1];
	isGarbageCollectable[i] = isGarbageCollectable[i - 1];
    }
    items[index] = [anObject retain];
    isGarbageCollectable[index] = [anObject isGarbageCollectable];
    itemsCount++;
}

- (void)addObject:(id)anObject
{
    [self insertObject:anObject atIndex:itemsCount];
}

- (void)replaceObjectAtIndex:(unsigned int)index  withObject:(id)anObject
{
    if (!anObject)
	THROW([[InvalidArgumentException alloc] 
		initWithReason:@"Nil object to be added in array"]);
    if (index >= itemsCount)
	THROW([[RangeException alloc] 
		initWithReason:@"GCMutableArray replaceObjectAtIndex" 
		size:itemsCount index:index]);
    [anObject retain];
    [items[index] release];
    items[index] = anObject;
    isGarbageCollectable[index] = [anObject isGarbageCollectable];
}

- (void)removeObjectsFrom:(unsigned int)index
	count:(unsigned int)count
{
    int i;
    if (index + count > itemsCount)
	THROW([[RangeException alloc]
		initWithReason:@"removeObjectsFrom:count: in GCMutableArray"
		size:itemsCount index:index]);
    if (!count)
	return;
    for (i = index; i < index + count; i++)
	[items[i] release];

    for (i = index + count; i < itemsCount; i++, index++) {
	items[index] = items[i];
	isGarbageCollectable[index] = isGarbageCollectable[i];
    }
    for (; index < itemsCount; index++)
	items[index] = (id)0x3;

    itemsCount -= count;
}

- (void)removeAllObjects
{
    [self removeObjectsFrom:0 count:itemsCount];
}

- (void)removeLastObject
{
    [self removeObjectsFrom:(itemsCount - 1) count:1];
}

- (void)removeObjectAtIndex:(unsigned int)index
{
    [self removeObjectsFrom:index count:1];
}

@end /* GCMutableArray */
