/* 
   NSFFCallInvocation.m

   Copyright (C) 2000, MDlink online service center GmbH, Helge Hess
   All rights reserved.
   
   Author: Helge Hess <helge.hess@mdlink.de>
   
   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.
*/
// $Id: FFCallInvocation.m 827 2005-06-03 14:18:27Z helge $

#include "FFCallInvocation.h"
#include <Foundation/NSException.h>

@interface NSInvocation(PrivateMethods)
- (id)initWithSignature:(NSMethodSignature *)_signature;
@end

@interface FFCallInvocation(PrivateMethods)
- (void)_releaseArguments;
@end

@implementation FFCallInvocation

#if OBJC_METHOD_LOOKUP_HOOKS

static void *cb;

static IMP ffObjCMsgLookup(id self, SEL _cmd)
{
    /* invoked during method dispatch */
    return cb;
}
static IMP ffClassGetMethod(Class clazz, SEL _cmd)
{
    /* invoked during method_get */
    return cb;
}

static void freeCallback(void)
{
    if (cb) { free_callback(cb); cb = NULL; }
}

+ (void)load
{
    cb = alloc_callback(&NSInvocationCallbackDispatcher,
                        @selector(forwardInvocation:));
    atexit(&freeCallback);
    
    __objc_msg_lookup       = ffObjCMsgLookup;
    __objc_class_get_method = ffClassGetMethod;
}
#endif

+ (id)invocationWithMethodSignature:(NSMethodSignature *)_signature
{
    id o;
    o = [[self alloc] initWithSignature:_signature];
    AUTORELEASE(o);
    return o;
}

- (id)init
{
    abort();
    return nil;
}
- (id)initWithSignature:(NSMethodSignature *)_signature
{
    NSAssert(_signature, @"method signature required !");
    
    self->types = [_signature types];
    
    if ((self = [super initWithSignature:_signature])) {
        /* setup argument buffers */
        register const char *ltypes;
        unsigned total, count;
        
        ltypes = self->types;
    
        /* alloc return buffer */
        self->retvalLen = objc_sizeof_type(objc_skip_type_qualifiers(ltypes));
        self->retvalue  = malloc(self->retvalLen);
    
        ltypes = objc_skip_argspec(ltypes); // skip retval
        ltypes = objc_skip_argspec(ltypes); // skip self
        ltypes = objc_skip_argspec(ltypes); // skip _cmd
    
        /* calc total buffer size & argument count */
        for (total = 0, count = 0; *ltypes; ltypes = objc_skip_argspec(ltypes)) {
            total += objc_sizeof_type(ltypes);
            count++;
        }
    
        /* reset type-ptr to arguments */
        ltypes = self->types;
        ltypes = objc_skip_argspec(ltypes); // skip retval
        ltypes = objc_skip_argspec(ltypes); // skip self
        ltypes = objc_skip_argspec(ltypes); // skip _cmd
    
        /* allocate arg buffer */
        self->buffer   = malloc(total);
        self->idxToPtr = malloc(sizeof(void *)   * count);
        self->idxToLen = malloc(sizeof(unsigned) * count);
        for (total = 0, count = 0; *ltypes; ltypes = objc_skip_argspec(ltypes)) {
            self->idxToPtr[count] = self->buffer + total;
            self->idxToLen[count] = objc_sizeof_type(ltypes);
            total += self->idxToLen[count];
            count++;
        }
    }
    return self;
}

- (void)dealloc
{
    [self _releaseArguments];
    
    if (self->retvalue) free(self->retvalue);
    if (self->buffer)   free(self->buffer);
    if (self->idxToPtr) free(self->idxToPtr);
    if (self->idxToLen) free(self->idxToLen);
    [super dealloc];
}

static inline void
_setArg(FFCallInvocation *self, SEL _cmd, void *_value, int _idx)
{
    switch (_idx) {
        case 0:
            [self setTarget:*(id *)_value];
            break;
        case 1:
            [self setSelector:*(SEL *)_value];
        default:
            if (self->argumentsRetained) {
                ; // not supported yet
            }
            _idx -= 2;
            memcpy(self->idxToPtr[_idx], _value, self->idxToLen[_idx]);
            break;
    }
}

static inline void
_getArg(FFCallInvocation *self, SEL _cmd, void *_value, int _idx)
{
    switch (_idx) {
        case 0:
            *(id *)_value = [self target];
            break;
        case 1:
            *(SEL *)_value = [self selector];
            break;
        default:
            _idx -= 2;
            memcpy(_value, self->idxToPtr[_idx], self->idxToLen[_idx]);
            break;
    }
}

static inline void _getRetVal(FFCallInvocation *self, SEL _cmd, void *_value)
{
    memcpy(_value, self->retvalue, self->retvalLen);
}

- (void)setArgument:(void *)_value atIndex:(int)_idx
{
    _setArg(self, _cmd, _value, _idx);
}
- (void)getArgument:(void *)_value atIndex:(int)_idx
{
    _getArg(self, _cmd, _value, _idx);
}

- (void)_releaseArguments
{
    const char *ltypes;
    unsigned   idx;
    
    if (!self->argumentsRetained) return;
    self->argumentsRetained = NO;
    
    ltypes = self->types;
    if ((*ltypes == _C_ID) || (*ltypes == _C_CLASS))
        RELEASE(*(id *)self->retvalue);
    
    ltypes = objc_skip_argspec(ltypes); // skip retval
    ltypes = objc_skip_argspec(ltypes); // skip 'self'
    ltypes = objc_skip_argspec(ltypes); // skip _cmd
    RELEASE(self->target);

    for (idx = 2; *ltypes; idx++) {
        if ((*ltypes == _C_ID) || (*ltypes == _C_CLASS)) {
            id object;

            _getArg(self, _cmd, &object, idx);
            RELEASE(object);
            break;
        }
        ltypes = objc_skip_argspec(ltypes);
    }
}

- (void)retainArguments
{
    const char *ltypes;
    unsigned   idx;
  
    if (self->argumentsRetained) return;
    self->argumentsRetained = YES;
    
    ltypes = self->types;
    if ((*ltypes == _C_ID) || (*ltypes == _C_CLASS))
        RETAIN(*(id *)self->retvalue);
    
    ltypes = objc_skip_argspec(ltypes); // skip retval
    ltypes = objc_skip_argspec(ltypes); // skip 'self'
    ltypes = objc_skip_argspec(ltypes); // skip _cmd
    RETAIN(self->target);
  
    for (idx = 2; *ltypes; idx++) {
        if ((*ltypes == _C_ID) || (*ltypes == _C_CLASS)) {
            id object;

            _getArg(self, _cmd, &object, idx);
            RETAIN(object);
            break;
        }
        ltypes = objc_skip_argspec(ltypes);
    }
}

- (void)setReturnValue:(void *)_value
{
    if (self->argumentsRetained) {
        switch (*(self->types)) {
            case _C_ID:
            case _C_CLASS: {
                id old = *(id *)self->retvalue;
                RETAIN(*(id *)_value);
                RELEASE(old);
                break;
            }
        }
    }
    memcpy(self->retvalue, _value, self->retvalLen);
}
- (void)getReturnValue:(void *)_value
{
    _getRetVal(self, _cmd, _value);
}

/* invalidation */

- (void)invalidate
{
    self->isValid = NO;
    [self _releaseArguments];
}

/* invocation */

- (void)invokeWithTarget:(id)_target
{
    IMP        method;
    const char *ltypes;
    av_alist   alist;
    unsigned   idx;
    
    /* get the Objective-C type signature */
  
    ltypes  = self->types;
    
    /* find the function implementing the method */
    
    method =
        method_get_imp(class_get_instance_method(*(Class *)_target, self->selector));
    
    /* if the method couldn't be found, forward the invocation */
    
    if (method == NULL) {
        [_target forwardInvocation:self];
        return;
    }
    
    /* start with return type */
  
    switch (*ltypes) {
        case _C_VOID:
            av_start_void(alist, method);
            break;
    
        case _C_ID:
            av_start_ptr(alist, method, id, self->retvalue);
            break;
        case _C_CLASS:
            av_start_ptr(alist, method, Class, self->retvalue);
            break;
        case _C_SEL:
            av_start_ptr(alist, method, SEL, self->retvalue);
            break;
        case _C_CHARPTR:
            av_start_ptr(alist, method, char *, self->retvalue);
            break;
        case _C_PTR:
            av_start_ptr(alist, method, void *, self->retvalue);
            break;
    
        case _C_CHR:
            av_start_char(alist, method, self->retvalue);
            break;
        case _C_UCHR:
            av_start_uchar(alist, method, self->retvalue);
            break;
    
        case _C_SHT:
            av_start_short(alist, method, self->retvalue);
            break;
        case _C_USHT:
            av_start_ushort(alist, method, self->retvalue);
            break;

        case _C_LNG:
        case _C_INT:
            av_start_int(alist, method, self->retvalue);
            break;
        case _C_ULNG:
        case _C_UINT:
            av_start_uint(alist, method, self->retvalue);
            break;
    
        case _C_FLT:
            av_start_float(alist, method, self->retvalue);
            break;
        case _C_DBL:
            av_start_double(alist, method, self->retvalue);
            break;
      
        default:
            fprintf(stderr, "Unsupported return type: '%s' !\n", ltypes);
            fflush(stderr);
            abort();
    }
  
    /* push target & _cmd arguments */
  
    av_ptr(alist, id,  _target);
    av_ptr(alist, SEL, self->selector);
  
    ltypes = objc_skip_argspec(ltypes); // skip retval
    ltypes = objc_skip_argspec(ltypes); // skip self
    ltypes = objc_skip_argspec(ltypes); // skip _cmd

    /* process method arguments */
  
    for (idx = 0; *ltypes; idx++) {
        register void     *ptr;
        register unsigned len;

        ptr = self->idxToPtr[idx];
        len = self->idxToLen[idx];
    
        switch (*ltypes) {
            case _C_CLASS:
            case _C_ID: {
                id o;
                memcpy(&o, ptr, len);
                av_ptr(alist, id, o);
                break;
            }
            case _C_SEL: {
                SEL s;
                memcpy(&s, ptr, len);
                av_ptr(alist, SEL, s);
                break;
            }

            case _C_CHARPTR:
            case _C_PTR: {
                void *p;
                memcpy(&p, ptr, len);
                av_ptr(alist, void *, p);
                break;
            }
      
            case _C_CHR: {
                char c;
                memcpy(&c, ptr, len);
                av_char(alist, c);
                break;
            }
            case _C_UCHR: {
                unsigned char c;
                memcpy(&c, ptr, len);
                av_uchar(alist, c);
                break;
            }
      
            case _C_SHT: {
                short c;
                memcpy(&c, ptr, len);
                av_short(alist, c);
                break;
            }
            case _C_USHT: {
                unsigned short c;
                memcpy(&c, ptr, len);
                av_ushort(alist, c);
                break;
            }

            case _C_LNG:
            case _C_INT: {
                int c;
                memcpy(&c, ptr, len);
                av_int(alist, c);
                break;
            }
            case _C_ULNG:
            case _C_UINT: {
                unsigned int c;
                memcpy(&c, ptr, len);
                av_uint(alist, c);
                break;
            }
      
            case _C_FLT: {
                float c;
                memcpy(&c, ptr, len);
                av_float(alist, c);
                break;
            }
            case _C_DBL: {
                double c;
                memcpy(&c, ptr, len);
                av_double(alist, c);
                break;
            }
      
            default:
                fprintf(stderr, "Unsupported argument type: '%s' !\n", ltypes);
                fflush(stderr);
                abort();
        }
    
        ltypes = objc_skip_argspec(ltypes);
    }

    /* invoke method */
    av_call(alist);
}

- (NSString *)description
{
    char buf[1024];
    sprintf(buf, "<%s[0x%08X]: target=0x%08X selector=%s sig=%s>",
            class_get_class_name([self class]), (unsigned)self,
            (unsigned)self->target, sel_get_name(self->selector),
            self->types);
  return [NSString stringWithCString:buf];
}

/* a dispatcher function for use with ffcall's callback */

void NSInvocationCallbackDispatcher(void *dispatchSelector, va_alist args)
{
    static Class FFCallInvocationClass = Nil;
    id                returnValue;
    id                self;
    SEL               _cmd;
    NSMethodSignature *signature;
    FFCallInvocation  *invocation;
    const char *types;
    unsigned idx;
    
    if (FFCallInvocationClass == Nil)
        FFCallInvocationClass = [FFCallInvocation class];
    
    va_start_ptr(args, id);
    self = va_arg_ptr(args, id);
    _cmd = va_arg_ptr(args, SEL);
    
    /* method reflection */
    
    signature = [self methodSignatureForSelector:_cmd];
    NSAssert(signature, @"missing signature for selector '%@'",
             NSStringFromSelector(_cmd));

    // NSLog(@"sel %@ sig %@", NSStringFromSelector(_cmd), signature);
    
    /* setup invocation object */
    
    invocation =
        [[FFCallInvocationClass alloc] initWithSignature:signature];
    
    [invocation setTarget:self];
    [invocation setSelector:_cmd];
    
    types = invocation->types;
    types = objc_skip_argspec(types);
    types = objc_skip_argspec(types);
    types = objc_skip_argspec(types);
    
    for (idx = 2; *types; idx++) {
        switch (*types) {
            case _C_CLASS:
            case _C_ID: {
                id o = va_arg_ptr(args, id);
                _setArg(invocation, _cmd, &o, idx);
                break;
            }
            
            case _C_SEL: {
                SEL s = va_arg_ptr(args, SEL);
                _setArg(invocation, _cmd, &s, idx);
                break;
            }
            case _C_PTR: {
                void *ptr = va_arg_ptr(args, void *);
                _setArg(invocation, _cmd, &ptr, idx);
                break;
            }
            case _C_CHARPTR: {
                char *ptr = va_arg_ptr(args, char *);
                _setArg(invocation, _cmd, &ptr, idx);
                break;
            }

            case _C_CHR: {
                char s = va_arg_char(args);
                _setArg(invocation, _cmd, &s, idx);
                break;
            }
            case _C_UCHR: {
                unsigned char s = va_arg_uchar(args);
                _setArg(invocation, _cmd, &s, idx);
                break;
            }
            
            case _C_SHT: {
                short s = va_arg_short(args);
                _setArg(invocation, _cmd, &s, idx);
                break;
            }
            case _C_USHT: {
                unsigned short s = va_arg_ushort(args);
                _setArg(invocation, _cmd, &s, idx);
                break;
            }
            
            case _C_INT: {
                int i = va_arg_int(args);
                _setArg(invocation, _cmd, &i, idx);
                break;
            }
            case _C_UINT: {
                unsigned int i = va_arg_uint(args);
                _setArg(invocation, _cmd, &i, idx);
                break;
            }
        
            case _C_FLT: {
                float f = va_arg_float(args);
                _setArg(invocation, _cmd, &f, idx);
                break;
            }
            case _C_DBL: {
                double d = va_arg_double(args);
                _setArg(invocation, _cmd, &d, idx);
                break;
            }
            
            default:
                fprintf(stderr, "Unsupported argument type: '%s' !\n", types);
                fflush(stderr);
                abort();
        }
      
        types = objc_skip_argspec(types);
    }
    
    /* forward */
    
    //[self performSelector:dispatchSelector withObject:invocation];
    [self forwardInvocation:invocation];
    
    /* apply return value */
    _getRetVal(invocation, _cmd, &returnValue);
    
    /* invalidate invocation object */
    [invocation invalidate];
    RELEASE(invocation);
    
    /* return from dispatcher */
    va_return_ptr(args, id, returnValue);
}

@end /* FFCallInvocation */

/*
  Local Variables:
  c-basic-offset: 4
  tab-width: 8
  End:
*/
