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

#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

#include <Foundation/common.h>
#include <Foundation/NSException.h>
#include <Foundation/NSInvocation.h>
#include <Foundation/exceptions/GeneralExceptions.h>
#include <Foundation/exceptions/NSInvocationExceptions.h>

#include <extensions/objc-runtime.h>

/*
 * Will set *seltype to point to the type of the nth argument, excluding
 * from type the type specifiers.
 */
static void* get_nth_argument(arglist_t argframe, NSArgumentInfo argInfo)
{
    if(*argInfo.type == '+')
	return argframe->arg_regs + argInfo.offset;
    else
	return argframe->arg_ptr +
		(argInfo.offset - OBJC_FORWARDING_STACK_OFFSET);
}

@implementation NSInvocation

+ (NSInvocation*)invocationWithMethodSignature:(NSMethodSignature*)sig
{
    NSInvocation* invocation = [[NSInvocation alloc] autorelease];
    invocation->signature = [sig retain];
    return invocation;
}

- (void)_releaseArguments
{
    if(argumentsRetained && frame && signature) {
	int index, numberOfArguments = [signature numberOfArguments];

	argumentsRetained = NO;
	for(index = 0; index < numberOfArguments; index++) {
	    NSArgumentInfo argInfo = [signature argumentInfoAtIndex:index];
	    if(*argInfo.type == _C_CHARPTR) {
		char* str;
		[self getArgument:&str atIndex:index];
		Free(str);
	    }
	    else if(*argInfo.type == _C_ID) {
		id object;
		[self getArgument:&object atIndex:index];
		[object release];
	    }
	}
    }
}

- (void)_verifySignature
{
    const char* types;
    struct objc_method_description* mth;

    if(!target)
	THROW([NullTargetException new]);

    if(!selector)
	THROW([NullSelectorException new]);

    mth = (struct objc_method_description*)
	(CLS_ISCLASS(((struct objc_class*)target)->class_pointer) ?
	      class_get_instance_method(
		    ((struct objc_class*)target)->class_pointer, selector)
	    : class_get_class_method(
		    ((struct objc_class*)target)->class_pointer, selector));
    selector = mth ? mth->name : (SEL)0;

    if(!selector)
	THROW([NullSelectorException new]);

    types = mth->types;
    if(!types)
	THROW([[CouldntGetTypeForSelector alloc] initForSelector:selector]);

    if(signature) {
	if(!sel_types_match(types, [signature types]))
	    THROW([[TypesDontMatchException alloc]
			    initWithTypes:types :[signature types]]);
    }
    else signature = [[NSMethodSignature signatureWithObjCTypes:types] retain];
}

- (void)dealloc
{
    [self _releaseArguments];
    if(frame && ownsFrame) {
	Free(((arglist_t)frame)->arg_ptr);
	Free(frame);
    }
    Free(returnFrame);
    if(ownsReturnValue)
	Free(returnValue);
    [signature release];
    [super dealloc];
}

- (BOOL)argumentsRetained		{ return argumentsRetained; }
- (NSMethodSignature*)methodSignature	{ return signature; }
- (void)setSelector:(SEL)_selector	{ selector = _selector; }
- (SEL)selector				{ return selector; }
- (void)setTarget:(id)_target		{ target = _target; }
- (id)target				{ return target; }

- (void)getArgument:(void*)argumentLocation
    atIndex:(int)index
{
    NSAssert(signature, @"You must previously set the signature object");
    NSAssert(frame, @"You must previously set the arguments frame");

    if(index >= [signature numberOfArguments]) {
	THROW([[IndexOutOfRangeException alloc]
		    initForSize:[signature numberOfArguments] index:index]);
	return;
    }
    else {
	NSArgumentInfo argInfo = [signature argumentInfoAtIndex:index];
	void* frameData = get_nth_argument(frame, argInfo);

#if WORDS_BIGENDIAN
	if(argInfo.size < sizeof(void*))
	    memcpy(argumentLocation,
		    ((char*)frameData) + sizeof(void*) - argInfo.size,
		    argInfo.size);
	else
#endif
	memcpy(argumentLocation, frameData, argInfo.size);
    }		
}

- (void)getReturnValue:(void*)retLoc
{
    const char* retType;
    int retLength;

    NSAssert(signature, @"You must previously set the signature object");
    NSAssert(frame, @"You must previously set the arguments frame");
    NSAssert(frame, @"You must previously call the invoke or invokeWithTarget: methods");

    retType = [signature methodReturnType];
    if(*retType != _C_VOID) {
	retLength = [signature methodReturnLength];
	memcpy(retLoc, returnValue, retLength);
    }
}

- (void)retainArguments
{
    int index, numberOfArguments;

    NSAssert(signature, @"You must previously set the signature object");
    NSAssert(frame, @"You must previously set the arguments frame");

    if(argumentsRetained)
	return;

    argumentsRetained = YES;
    numberOfArguments = [signature numberOfArguments];
    for(index = 2; index < numberOfArguments; index++) {
	NSArgumentInfo argInfo = [signature argumentInfoAtIndex:index];
	if(*argInfo.type == _C_CHARPTR) {
	    char* str;
	    [self getArgument:&str atIndex:index];
	    str = Strdup(str);
	    [self setArgument:&str atIndex:index];
	}
	else if(*argInfo.type == _C_ID) {
	    id object;
	    [self getArgument:&object atIndex:index];
	    [object retain];
	}
    }
}

- (void)setArgument:(void*)argumentLocation
    atIndex:(int)index
{
    /* If the argument to be set is not the target, verify the signature */
    if(index)
	[self _verifySignature];

    if(!frame) {
	/* Set up a new frame */
	int stack_argsize = [signature sizeOfStackArguments];

	frame = (arglist_t)NSZoneMalloc([self zone], APPLY_ARGS_SIZE);
	frame->arg_ptr = stack_argsize ? 
	    NSZoneMalloc([self zone], stack_argsize) : NULL;
	ownsFrame = YES;
    }

    if(index >= [signature numberOfArguments])
	THROW([[IndexOutOfRangeException alloc]
		    initForSize:[signature numberOfArguments] index:index]);
    else {
	NSArgumentInfo argInfo = [signature argumentInfoAtIndex:index];
	void* frameData = get_nth_argument(frame, argInfo);

	memcpy(frameData, argumentLocation, argInfo.size);
    }		
}

- (void)setReturnValue:(void*)retLoc
{
    const char* retType;
    int retSize;

    NSAssert(signature, @"You must previously set the signature object");
    NSAssert(frame, @"You must previously set the arguments frame");
    NSAssert(returnValue, @"You must previously call the invoke or invokeWithTarget: methods");

    retType = [signature methodReturnType];
    retSize = [signature methodReturnLength];

    if(*retType != _C_VOID) {
#if 0 && WORDS_BIGENDIAN
	if(retSize < sizeof(void*)) {
	    *(void**)returnValue = 0;
	    memcpy(returnValue, ((char*)retLoc) + sizeof(void*) - retSize,
			    retSize);
	}
	else
#endif
#if WORDS_BIGENDIAN
	if(retSize < sizeof(void*))
	    retSize = sizeof(void*);
#endif
	memcpy(returnValue, retLoc, retSize);
    }
    FUNCTION_SET_VALUE(retType, frame, returnFrame, returnValue);
}

- (void)invoke
{
    [self invokeWithTarget:target];
}

- (void)invokeWithTarget:(id)_target
{
    id old_target = target;
    retval_t retframe;
    const char* retType;
    IMP imp;
    int stack_argsize, retSize;

    /*  Set the target. We use _target in the following messages to self,
	because the some methods assume a valid target. */
    target = _target;
    [self _verifySignature];

    if(!frame && [signature numberOfArguments] > 2)
	THROW([FrameIsNotSetupException new]);

    stack_argsize = [signature sizeOfStackArguments];

    if(!frame) {
	stack_argsize = [signature sizeOfStackArguments];
	frame = (arglist_t)NSZoneMalloc([self zone], APPLY_ARGS_SIZE);
	frame->arg_ptr = stack_argsize ? 
	    NSZoneMalloc([self zone], stack_argsize) : NULL;
	ownsFrame = YES;
    }

    if(!returnFrame)
	returnFrame = NSZoneMalloc([self zone], APPLY_RESULT_SIZE + 8);

    [self setArgument:&_target atIndex:0];
    [self setArgument:&selector atIndex:1];

    retType = [signature methodReturnType];
    retSize = MAX([signature methodReturnLength], sizeof(void*));

    if(!ownsFrame) {
	/*  Was called from the forward:: method. If the original method
	    returns a struct by value, then we must set the returnValue to
	    the address of the structure value. Otherwise the returnValue
	    will still have the null value. */
	returnValue = GET_STRUCT_VALUE_ADDRESS(frame, retType);
    }
    if(!returnValue) {
	ownsReturnValue = YES;
	returnValue = NSZoneMalloc([self zone], retSize);
	SET_STRUCT_VALUE_ADDRESS(frame, returnValue, retType);
    }

    /* Restore the old target. */
    target = old_target;

    imp = method_get_imp(object_is_instance(_target) ?
	      class_get_instance_method(
		    ((struct objc_class*)_target)->class_pointer, selector)
	    : class_get_class_method(
		    ((struct objc_class*)_target)->class_pointer, selector));
    Assert(imp);
    retframe = __builtin_apply((void(*)(void))imp, frame, stack_argsize);

    memcpy(returnFrame, retframe, APPLY_RESULT_SIZE);
    FUNCTION_VALUE(retType, frame, retframe, returnValue);
}

@end /* NSInvocation */


@implementation NSInvocation (Extensions)

- (void)setArgumentFrame:(void*)_frame
{
    frame = _frame;
    ownsFrame = NO;
}

- (retval_t)returnFrame			{ return returnFrame; }
- (void*)returnValue			{ return returnValue; }

@end /* NSInvocation (Extensions) */
