/* 
   NSObjectAllocation.m

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

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
	   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 <config.h>

#if HAVE_LIBC_H
# include <libc.h>
#else
# include <unistd.h>
#endif

#if HAVE_STRING_H
# include <string.h>
#endif

#if HAVE_MEMORY_H
# include <memory.h>
#endif

#if !HAVE_MEMCPY
# define memcpy(d, s, n)       bcopy((s), (d), (n))
# define memmove(d, s, n)      bcopy((s), (d), (n))
#endif

#if HAVE_STDLIB_H
# include <stdlib.h>
#else
extern void* malloc();
extern void* calloc();
extern void* realloc();
extern void free();
extern atoi();
extern atol();
#endif

#include <assert.h>

#include <Foundation/common.h>
#include <Foundation/NSZone.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/exceptions/GeneralExceptions.h>

#include <extensions/objc-runtime.h>

/*
 * When an object is deallocated, its class pointer points to the FREED_OBJECT
 * class.
 */

@interface FREED_OBJECT
@end


/*
 * Refence counting can be done in two ways:
 *	(1) increasing the size of NSObject by allocating a "hidden" int
 *      before the pointer. This has the disadvantage that the pointers
 *      returned by malloc differ from those kept by the program, making
 *      the use of NeXT's MallocDebug program unusable :-)
 *  (2) keeping a global hash with object pointers as keys and reference
 *      counts as values.
 *
 * This mecanisms are controlled by one compile time defines and one 
 * environment variable as follows:
 *  OBJECT_REFCOUNT	(define)
 *	1			=> mecanism is controlled by hash
 *	0			=> mechanism is controlled by extra ref-count
 *	*			=> mecanism controlled by environment variable
 *  OBJECT_REFCOUNT (environment variable)
 *	defined		=> mecanism is controlled by hash
 *	undefined	=> mechanism is controlled by extra ref-count
 *
 */

/*
 * OBJ2PTR(p) : id object --> real allocated pointer used by Malloc/Free
 * PTR2HSH(p) : id object --> pointer kept as key in hashtable
 */

#define OBJ2PTR(p) ((struct RefObjectLayout*)((unsigned*)p-1))
#define PTR2HSH(p) ((void*)((unsigned*)p-1))

struct RefObjectLayout {
    unsigned	ref_count;	/*  <---- Malloc/Free pointer */
    Class	class_pointer;	/*  <---- id pointer  */
    char	extra[0];
};

struct HashObjectLayout {
    Class	class_pointer;	/*  <---- id pointer == Malloc/Free pointer */
    char	extra[0];
};

/*
* Global variables
*/

static BOOL objectRefInit = NO;

static NSMapTable* objectRefsHash;
static BOOL objectRefIsHash;
static Class freedObjectClass = nil;

static void init_refcounting(void)
{

#if defined(OBJECT_REFCOUNT) && OBJECT_REFCOUNT == 1
    objectRefIsHash = YES;
#elif defined(OBJECT_REFCOUNT) && OBJECT_REFCOUNT == 0
    objectRefIsHash = NO;
#else 
    /* Undefined at compile-time. Use OBJECT_REFCOUNT environment variable */
    objectRefIsHash = getenv("OBJECT_REFCOUNT") ? YES : NO;
#endif

/*
    fprintf(stderr, "NSObjectAllocation: using %s refcounting\n", 
	    objectRefIsHash ? "hashtable" : "extra-allocated");
*/
    if (objectRefIsHash)
	objectRefsHash = NSCreateMapTableWithZone(
		NSNonOwnedPointerMapKeyCallBacks,
		NSIntMapValueCallBacks, 
		64, nil);

    objectRefInit = YES;
    freedObjectClass = objc_get_class("FREED_OBJECT");
}

/* 
 * Allocate an Object 
 */

NSObject *NSAllocateObject(Class aClass, unsigned extraBytes, NSZone *zone)
{
    assert(CLS_ISCLASS(aClass));

    if (!objectRefInit)
	init_refcounting();

    if (objectRefIsHash)
    {
	struct HashObjectLayout* p;
	
	p = NSZoneCalloc(zone, 1, sizeof(struct HashObjectLayout) + 
		aClass->instance_size + extraBytes);

	p->class_pointer = aClass;
	NSMapInsert(objectRefsHash, PTR2HSH(p), (void*)1);
	
	return (NSObject*)p;
    }
    else
    {
	struct RefObjectLayout* p;
	
	p = NSZoneCalloc(zone, 1, sizeof(struct RefObjectLayout) + 
		aClass->instance_size + extraBytes);

	p->class_pointer = aClass;
	p->ref_count = 1;
	
	return (NSObject*)(&(p->class_pointer));
    }
    
    return nil;
}

NSObject *NSCopyObject(NSObject *anObject, unsigned extraBytes, NSZone *zone)
{
    id copy = NSAllocateObject(((id)anObject)->class_pointer,
			    extraBytes, zone);
    unsigned size = ((id)anObject)->class_pointer->instance_size + 
				extraBytes;

    memcpy(copy, anObject, size);

    return copy;
}

NSZone* NSZoneFromObject(NSObject *anObject)
{
    if (!objectRefInit)
	init_refcounting();

    if (objectRefIsHash)
    {
	return NSZoneFromPointer(anObject);
    }
    else
    {
	return NSZoneFromPointer(OBJ2PTR(anObject));
    }
}

void NSDeallocateObject(NSObject *anObject)
{
    void* p;

    /* Set the class of anObject to FREED_OBJECT. The further messages to this
       object will cause an error to occur. */
    ((id)anObject)->class_pointer = freedObjectClass;

    if (!objectRefInit)
	init_refcounting();

    if (objectRefIsHash)
    {
	p = anObject;
	NSMapRemove(objectRefsHash, PTR2HSH(anObject));
    }
    else
    {
	p = OBJ2PTR(anObject);
    }

    NSZoneFree(NSZoneFromPointer(p), p);
}

/* 
 * Retain / Release NSObject 
 */

BOOL NSShouldRetainWithZone(NSObject* anObject, NSZone* requestedZone)
{
    return	requestedZone == NULL ||
		requestedZone == NSDefaultMallocZone() ||
		requestedZone == [anObject zone];
}

/* 
 * Modify the Number of References to an Object 
 */

BOOL NSDecrementExtraRefCountWasZero(id anObject)
{
    if (!objectRefInit)
	    init_refcounting();

    if (objectRefIsHash)
    {
	void* p = PTR2HSH(anObject);
	int   r = (unsigned)NSMapGet(objectRefsHash, p);
	
	if (!r)
	    return YES;

	NSMapInsert(objectRefsHash, p, (void*)(--r));
	return (r == 0);
    }
    else
    {
	struct RefObjectLayout* p = OBJ2PTR(anObject);
	int r = p->ref_count;

	if (!r)
	    return YES;

	r = --(p->ref_count);
	return (r == 0);
    }
}

void NSIncrementExtraRefCount(id anObject)
{
    if (!objectRefInit)
	init_refcounting();

    if (objectRefIsHash)
    {
	void* p = PTR2HSH(anObject);
	int   r = (unsigned)NSMapGet(objectRefsHash, p);
	
	NSMapInsert(objectRefsHash, p, (void*)(r+1));
    }
    else
    {
	struct RefObjectLayout* p = OBJ2PTR(anObject);

	(p->ref_count)++;
    }
}

/* Obtain the Number of References to an Object */
unsigned NSGetExtraRefCount(id anObject)
{
    int r;

    if (!objectRefInit)
	init_refcounting();

    if (objectRefIsHash)
    {
	void* p = PTR2HSH(anObject);
	
	r = (unsigned)NSMapGet(objectRefsHash, p);
    }
    else
    {
	struct RefObjectLayout* p = OBJ2PTR(anObject);
	r = p->ref_count;
    }
    return r;
}

@implementation FREED_OBJECT

#if GNU_RUNTIME
- (void)doesNotRecognize:(SEL)aSelector
#else
- (void)doesNotRecognizeSelector:(SEL)aSelector
#endif
{
    id exception = [[ObjcRuntimeException alloc]
		    initWithFormat:@"message '%s' sent to freed object %p", 
		    sel_get_name(aSelector), (void*)self];
    THROW(exception);
}

@end
