//
//  NativeMethods.m
//  CamelBones
//
//  Copyright (c) 2004 Sherm Pendley. All rights reserved.
//

#import "Conversions.h"
#import "NativeMethods.h"
#import "Structs.h"

// Call a native class or object method
SV * CBCallNativeMethod(id target, SEL sel, SV *args) {
    // Define a Perl context
    dTHX;

    int i = 0;
    SV *ret = newSViv(0);

    NSMethodSignature *ms = [target methodSignatureForSelector: sel];
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature: ms];
    NSString *returnType = [NSString stringWithUTF8String: [ms methodReturnType]];
    NSString *selName = NSStringFromSelector(sel);

    [inv setTarget: target];
    [inv setSelector: sel];

    for(i=2; i < [ms numberOfArguments]; i++) {
        NSString *argType = [NSString stringWithUTF8String: [ms getArgumentTypeAtIndex: i]];
        AV *av = (AV *)SvRV(args);
        SV **sv = av_fetch(av, i-2, 0);
        SV *argSV = sv ? *sv : NULL;

        if (!argSV) {
            break;
        }

        if ([argType hasPrefix: @"@"]) {
            id arg = CBDerefSVtoID(argSV);
            [inv setArgument: &arg atIndex: i];

        } else if ([argType hasPrefix: @"i"]) {
            int intArg = SvIV(argSV);
            [inv setArgument: &intArg atIndex: i];

        } else if ([argType hasPrefix: @"I"]) {
            unsigned int unsignedArg = SvUV(argSV);
            [inv setArgument: &unsignedArg atIndex: i];

        } else if ([argType hasPrefix: @"c"]) {
            char charArg = SvIV(argSV);
            [inv setArgument: &charArg atIndex: i];

        } else if ([argType hasPrefix: @"C"]) {
            unsigned char unsignedCharArg = SvUV(argSV);
            [inv setArgument: &unsignedCharArg atIndex: i];

        } else if ([argType hasPrefix: @"f"]) {
            float floatArg = SvNV(argSV);
            [inv setArgument: &floatArg atIndex: i];

        } else if ([argType hasPrefix: @"d"]) {
            double doubleArg = SvNV(argSV);
            [inv setArgument: &doubleArg atIndex: i];

        } else if ([argType hasPrefix: @"*"] || [argType hasPrefix: @"r*"]) {
            if (SvPOK(argSV)) {
                char *arg = SvPV(argSV, PL_na);
                [inv setArgument: &arg atIndex: i];
            } else {
                NSLog(@"Invalid argument type found in position %d of call to %@ - string expected", i-2, selName);
            }

        } else if ([argType hasPrefix: @"{"]) {
            if ([argType rangeOfString: @"NSPoint"].location < 3) {
                NSPoint pointArg = CBPointFromSV(argSV);
                [inv setArgument: &pointArg atIndex: i];

            } else if ([argType rangeOfString: @"NSRange"].location < 3) {
                NSRange rangeArg = CBRangeFromSV(argSV);
                [inv setArgument: &rangeArg atIndex: i];

            } else if ([argType rangeOfString: @"NSRect"].location < 3) {
                NSRect rectArg = CBRectFromSV(argSV);
                [inv setArgument: &rectArg atIndex: i];

            } else if ([argType rangeOfString: @"NSSize"].location < 3) {
                NSSize sizeArg = CBSizeFromSV(argSV);
                [inv setArgument: &sizeArg atIndex: i];

            } else {
                NSLog(@"Unhandled struct type %@ found in position %d of call to %@", argType, i-2, selName);
            }

        } else if ([argType hasPrefix: @":"]) {
            NSString *stringArg = [NSString stringWithUTF8String: SvPV(argSV, PL_na)];
            SEL selArg = NSSelectorFromString(stringArg);
            [inv setArgument: &selArg atIndex: i];

		} else if ([argType hasPrefix: @"^v"]) {
			// TODO: Think about this...
			void *ptrArg = (void*)SvIV(argSV);
			[inv setArgument: &ptrArg atIndex: i];
			
        } else {
            NSLog(@"Unhandled argument type %@ in position %d of call to %@", argType, i-2, selName);
        }
    }

    // Magic happens here. :-)
    [inv invoke];

    if ([returnType hasPrefix: @"v"]) {
        // Nothing to do for void return type
        ret = &PL_sv_undef;

    } else if ([returnType hasPrefix: @"@"]) {
        id retID;
        [inv getReturnValue: &retID];
        ret = CBDerefIDtoSV(retID);

    } else if ([returnType hasPrefix: @"c"]) {
        char retChar;
        [inv getReturnValue: &retChar];
        ret = newSViv(retChar);

    } else if ([returnType hasPrefix: @"C"]) {
        unsigned char retChar;
        [inv getReturnValue: &retChar];
        ret = newSVuv(retChar);

    } else if ([returnType hasPrefix: @"i"]) {
        int retInt;
        [inv getReturnValue: &retInt];
        ret = newSViv(retInt);

    } else if ([returnType hasPrefix: @"I"]) {
        unsigned int retUnsigned;
        [inv getReturnValue: &retUnsigned];
        ret = newSVuv(retUnsigned);

    } else if ([returnType hasPrefix: @"f"]) {
        float retFloat;
        [inv getReturnValue: &retFloat];
        ret = newSVnv(retFloat);

    } else if ([returnType hasPrefix: @"d"]) {
        double retDouble;
        [inv getReturnValue: &retDouble];
        ret = newSVnv(retDouble);
    
    } else if ([returnType hasPrefix: @"^v"] || [returnType hasPrefix: @"r^v"]) {
    	void *retPtr;
    	[inv getReturnValue: &retPtr];
    	ret = newSViv((int)(retPtr));

    } else if ([returnType hasPrefix: @"r*"] || [returnType hasPrefix: @"*"]) {
        char *retString;
        [inv getReturnValue: &retString];
        sv_setpv_mg(ret, retString);

    } else if ([returnType hasPrefix: @"{"]) {
        if ([returnType rangeOfString: @"NSPoint"].location < 3) {
            NSPoint retPoint;
            [inv getReturnValue: &retPoint];
            ret = CBPointToSV(retPoint);
        } else if ([returnType rangeOfString: @"NSRange"].location < 3) {
            NSRange retRange;
            [inv getReturnValue: &retRange];
            ret = CBRangeToSV(retRange);
        } else if ([returnType rangeOfString: @"NSRect"].location < 3) {
            NSRect retRect;
            [inv getReturnValue: &retRect];
            ret = CBRectToSV(retRect);
        } else if ([returnType rangeOfString: @"NSSize"].location < 3) {
            NSSize retSize;
            [inv getReturnValue: &retSize];
            ret = CBSizeToSV(retSize);
        } else {
            NSLog(@"Unhandled struct type %@ returned from call to %@", returnType, selName);
        }

    } else {
        NSLog(@"Unhandled return type %@ from call to %@", returnType, selName);
    }

    return ret;
}

