/* 
   NSObject.m

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

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.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>
#include <stdio.h>

#include <Foundation/common.h>
#include <Foundation/NSObject.h>
#include <Foundation/NSZone.h>
#include <Foundation/NSException.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSString.h>
#include <Foundation/NSMethodSignature.h>
#include <Foundation/NSInvocation.h>
#include <extensions/objc-runtime.h>
#include <Foundation/exceptions/FoundationExceptions.h>

#include "exceptions/FoundationExceptions.h"

@implementation NSObject

static BOOL 
objc_runtime_exception(id object, int code, const char* fmt, va_list ap)
{
    THROW([[ObjcRuntimeException alloc] initWithFormat:
		    @"Objective-C runtime error: %@",
		    Avsprintf([NSString stringWithCString:fmt], ap)]);
    return NO;
}

/* Initializing the Class */

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

    if(!initialized) {
	initialized = YES;
	[NSZone initialize];
	objc_set_error_handler(objc_runtime_exception);
    }
}

/* Creating and Destroying Instances */

+ (id)alloc
{
    return [self allocWithZone:NULL];
}

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

+ (id)new
{
    return [[self alloc] init];
}

- (void)dealloc
{
    NSDeallocateObject(self);
}

+ (void)dealloc
{
}

- (id)init
{
    return self;
}

/* Testing Class Functionality */

+ (BOOL)instancesRespondToSelector:(SEL)aSelector
{
    return class_get_instance_method(self, aSelector) != METHOD_NULL;
}

/* Testing Protocol Conformance */

+ (BOOL)conformsToProtocol:(Protocol*)aProtocol
{
    int i;
    struct objc_protocol_list *protos;

    for(protos = ((struct objc_class*)self)->protocols;
	    protos; protos = protos->next) {
	for(i = 0; i < protos->count; i++)
	    if([protos->list[i] conformsTo:aProtocol])
		return YES;
    }

    if([self superclass])
	return [[self superclass] conformsToProtocol: aProtocol];
    else return NO;
}

- (BOOL)conformsToProtocol:(Protocol*)aProtocol
{
    int i;
    struct objc_protocol_list *protos;

    for(protos = ((struct objc_class*)self)->class_pointer->protocols;
	    protos; protos = protos->next) {
	for(i = 0; i < protos->count; i++)
	    if([protos->list[i] conformsTo:aProtocol])
		return YES;
    }

    if([self superclass])
	return [[self superclass] conformsToProtocol: aProtocol];
    else return NO;
}

/* Identifying Class and Superclass */

- (Class)class
{
    return object_get_class(self);
}

- (Class)superclass
{
    return object_get_super_class(self);
}

/* Testing Class Functionality */

- (BOOL)respondsToSelector: (SEL)aSelector
{
    return (object_is_instance(self) ?
	  (class_get_instance_method(self->isa, aSelector) != METHOD_NULL)
	: (class_get_class_method(self->isa, aSelector) != METHOD_NULL));
}

/* Managing Reference Counts */

- autorelease
{
    [NSAutoreleasePool addObject:self];
    return self;
}

+ autorelease
{
    return self;
}

- (oneway void)release
{
    extern BOOL __autoreleaseEnableCheck;

    // check if retainCount is Ok
    if (__autoreleaseEnableCheck) {
	int toCome = [NSAutoreleasePool autoreleaseCountForObject:self];
	if (toCome+1 > [self retainCount]) {
	    NSLog(@"Release[%p] release check for object has %d references "
	    	  @"and %d pending calls to release in autorelease pools\n", 
		  self, [self retainCount], toCome);
	    return;
	}
    }
    if(NSDecrementExtraRefCountWasZero(self))
	[self dealloc];
}

+ (oneway void)release
{
}

- (id)retain
{
    NSIncrementExtraRefCount(self);
    return self;
}

+ (id)retain
{
    return self;
}

- (unsigned int)retainCount
{
    return NSGetExtraRefCount(self);
}

+ (unsigned int)retainCount
{
    return (unsigned)-1;
}

/* Obtaining Method Information */

+ (IMP)instanceMethodForSelector:(SEL)aSelector
{
    return method_get_imp(class_get_instance_method(self, aSelector));
}

- (IMP)methodForSelector:(SEL)aSelector
{
    return method_get_imp(object_is_instance(self) ?
			      class_get_instance_method(self->isa, aSelector)
			    : class_get_class_method(self->isa, aSelector));
}

+ (NSMethodSignature*)instanceMethodSignatureForSelector:(SEL)aSelector
{
    struct objc_method* mth = class_get_instance_method(self, aSelector);

    return mth ? [NSMethodSignature signatureWithObjCTypes:mth->method_types]
		: nil;
}

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    struct objc_method* mth =
	    (object_is_instance(self) ?
		  class_get_instance_method(self->isa, aSelector)
		: class_get_class_method(self->isa, aSelector));

    return mth ? [NSMethodSignature signatureWithObjCTypes:mth->method_types]
		: nil;
}

/* Describing the Object */

- (NSString*)description
{
    /* Don't use -[NSString stringWithFormat:] method because it can cause
       infinite recursion. */
    char buffer[512];

    sprintf (buffer, "<%s %p>", (char*)object_get_class_name(self), self);
    return [NSString stringWithCString:buffer];
}

+ (NSString*)description
{
    /* Don't use -[NSString stringWithFormat:] method because it can cause
       infinite recursion. */
    char buffer[512];

    sprintf (buffer, "<class %s>", (char*)object_get_class_name(self));
    return [NSString stringWithCString:buffer];
}

/* Obtaining a string representation */

- (NSString*)stringRepresentation
{
    return [self description];
}

+ (void)poseAsClass:(Class)aClass
{
    class_pose_as(self, aClass);
}

/* Error Handling */

- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    /* Don't use initWithFormat: here because it can cause infinite
       recursion. */
    char buffer[512];

    sprintf (buffer, "%s does not recognize %s",
	      object_get_class_name(self), sel_get_name(aSelector));
    THROW([[ObjcRuntimeException alloc]
	      setReason:[NSString stringWithCString:buffer]]);
}

/* Sending Deferred Messages */

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
  selector:(SEL)aSelector
  object:(id)anObject
{
    // TODO
}

- (void)performSelector:(SEL)aSelector
  object:(id)anObject
  afterDelay:(NSTimeInterval)delay
{
    // TODO
}

/* Forwarding Messages */

- (void)forwardInvocation:(NSInvocation*)anInvocation
{
    return [self doesNotRecognizeSelector:[anInvocation selector]];
}

/* Archiving */

- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder
{
    return self;
}

- (Class)classForArchiver
{
    return [self classForCoder];
}

- (Class)classForCoder
{
    return [self class];
}

- (id)replacementObjectForArchiver:(NSArchiver*)anArchiver
{
    return [self replacementObjectForCoder:(NSCoder*)anArchiver];
}

- (id)replacementObjectForCoder:(NSCoder*)anEncoder
{
    return self;
}

+ (void)setVersion:(int)version
{
    class_set_version(self, version);
}

+ (int)version
{
    return class_get_version(self);
}

- (unsigned int)hash
{
    return (unsigned int)self;
}

/* Identifying and Comparing Instances */

- (BOOL)isEqual:(id)anObject
{
    return self == anObject;
}

- (id)self
{
    return self;
}

/* Determining Allocation Zones */

- (NSZone*)zone
{
    return NSZoneFromObject(self);
}

/* Sending Messages Determined at Run Time */

- (id)perform:(SEL)aSelector
{
    IMP msg = aSelector ? objc_msg_lookup(self, aSelector) : NULL;

    if(!msg) {
	THROW([[ObjcRuntimeException alloc] initWithFormat:
	    @"invalid selector `%s' passed to %s",
	    sel_get_name(aSelector), sel_get_name(_cmd)]);
    }
    return (*msg)(self, aSelector);
}

- (id)perform:(SEL)aSelector withObject:(id)anObject
{
    IMP msg = aSelector ? objc_msg_lookup(self, aSelector) : NULL;

    if(!msg) {
	THROW([[ObjcRuntimeException alloc]
	    initWithFormat:@"invalid selector `%s' passed to %s",
	    sel_get_name(aSelector), sel_get_name(_cmd)]);
    }
    return (*msg)(self, aSelector, anObject);
}

- (id)perform:(SEL)aSelector withObject:(id)anObject
  withObject:(id)anotherObject
{
    IMP msg = aSelector ? objc_msg_lookup(self, aSelector) : NULL;

    if(!msg) {
	THROW([[ObjcRuntimeException alloc]
	    initWithFormat:@"invalid selector `%s' passed to %s",
	    sel_get_name(aSelector), sel_get_name(_cmd)]);
    }
    return (*msg)(self, aSelector, anObject, anotherObject);
}

/* Identifying Proxies */

- (BOOL)isProxy
{
    return NO;
}

/* Testing Inheritance Relationships */

- (BOOL)isKindOfClass:(Class)aClass
{
    Class class;

    for(class = self->isa; class != Nil; class = class_get_super_class(class))
	if(class == aClass)
	    return YES;
    return NO;
}

- (BOOL)isMemberOfClass:(Class)aClass
{
    return self->isa == aClass;
}

/* NSCopying/NSMutableCopying shortcuts */

- (id)copy
{
    return [(id<NSCopying>)self copyWithZone:NULL];
}

- (id)mutableCopy
{
    return [(id<NSMutableCopying>)self mutableCopyWithZone:NULL];
}

@end /* NSObject */


@implementation NSObject (GNU)

- (Class)transmuteClassTo:(Class)aClassObject
{
    if(object_is_instance(self) && class_is_class(aClassObject)
		&& (class_get_instance_size(aClassObject)
				== class_get_instance_size(isa))
		&& [self isKindOfClass:aClassObject]) {
	Class old_isa = isa;
	isa = aClassObject;
	return old_isa;
    }
    return nil;
}

- subclassResponsibility:(SEL)aSel
{
    id exception = [[ObjcRuntimeException alloc]
			    initWithFormat:@"subclass should override %s",
			    sel_get_name(aSel)];
    THROW(exception);
    return self;
}

- shouldNotImplement:(SEL)aSel
{
    id exception = [[ObjcRuntimeException alloc]
			    initWithFormat:@"%s should not implement %s",
			    object_get_class_name(self), sel_get_name(aSel)];
    THROW(exception);
    return self;
}

- notImplemented:(SEL)aSel
{
    id exception = [[ObjcRuntimeException alloc]
			    initWithFormat:@"%s does not implement %s",
			    object_get_class_name(self), sel_get_name(aSel)];
    THROW(exception);
    return self;
}

- (retval_t)forward:(SEL)aSel :(arglist_t)argFrame
{
    void* result;
    NSInvocation* invocation;
#if defined(NeXT_RUNTIME) && !defined(BROKEN_BUILTIN_APPLY) && defined(i386)
    const char* retType;
#endif

#if NeXT_RUNTIME
    /*  On NeXT the argFrame represents the stack zone with all the arguments.
	We create a frame like that required by __builtin_apply. This is done
	by __builtin_apply_args. This builtin function also sets correctly the
	structure value return address if any. */
    arglist_t frame = __builtin_apply_args();

    frame->arg_ptr = (void*)argFrame;
    argFrame = frame;
#endif

    invocation = [[NSInvocation new] autorelease];
    [invocation setArgumentFrame:argFrame];
    [invocation setSelector:aSel];

    [self forwardInvocation:invocation];

    result = [invocation returnFrame];

#if GNU_RUNTIME
    return result;
#else /* NeXT_RUNTIME */
# if !defined(BROKEN_BUILTIN_APPLY) && defined(i386)
    /* Special hack to avoid pushing the poped float value back to the fp
       stack on i386 machines. This happens with NeXT runtime and 2.7.2
       compiler. If the result value is floating point don't call
       __builtin_return anymore. */
    retType = [[invocation methodSignature] methodReturnType];
    if(*retType == _C_FLT || *retType == _C_DBL) {
	long double value = *(long double*)(((char*)result) + 8);
	asm("fld %0" : : "f" (value));
    }
    else
# endif
    __builtin_return(result);
#endif /* NeXT_RUNTIME */
}

@end /* NSObject (GNU) */


@implementation NSObject (GNUDebugger)

/* The following two methods are necessary to make GDB to work correctly with
   Foundation objects */

- (BOOL)respondsTo: (SEL)aSelector
{
    return (object_is_instance(self) ?
	  (class_get_instance_method(self->isa, aSelector) != METHOD_NULL)
	: (class_get_class_method(self->isa, aSelector) != METHOD_NULL));
}

- (IMP)methodFor:(SEL)aSelector
{
    return method_get_imp(object_is_instance(self) ?
			      class_get_instance_method(self->isa, aSelector)
			    : class_get_class_method(self->isa, aSelector));
}

@end /* NSObject (GNUDebugger) */
