/* 
   driver.m

   Copyright (C) 1995, 1996, 1997 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_STDLIB_H
# include <stdlib.h>
#else
extern int atoi(const char*);
#endif

#if NeXT_foundation_LIBRARY
typedef void NXTypedStream;
#endif

#include <ctype.h>

#include <Foundation/NSString.h>
#include <Foundation/NSException.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSObjCRuntime.h>

#if !NeXT_foundation_LIBRARY
#include <Foundation/NSProcessInfo.h>
#endif

#include <extensions/NSException.h>
#include <extensions/objc-runtime.h>

#include "tests.h"

extern struct {
    char* name;
    void (*function)(void);
} functions[];

extern struct {
    char* name;
    void (*function)(id);
} functions1[];

extern struct {
    char* name;
    void (*function)(id, id);
} functions2[];


@interface GoToBeginException : NSException
@end

static const char* method_get_nth_argument_type(struct objc_method* m, int arg)
{
    const char* types = objc_skip_argspec(m->method_types);

    if(arg > method_get_number_of_arguments(m))
	return NULL;

    while(arg--)
	types = objc_skip_argspec(types);

    types = objc_skip_type_qualifiers(types);
    return types;
}


enum { SIZE = 10240, WORDS_NO = 256 };

int split(char* line, char** words)
{
    int i;

    while(isspace(*line))
	line++;
    if(!*line)
	return 0;

    for(i = 0; i < WORDS_NO; i++) {
	words[i] = line;
	if(*line != '"') {
	    while(*line && !isspace(*line))
		line++;
	    if(!*line)
		break;
	    *line++ = 0;
	}
	else {
	    char* pos;

	    pos = words[i] = ++line;
	    while(*line) {
		if(*line == '"' && *(line - 1) != '\\')
		    break;
		else line++;
	    }
	    if(*line == '"')
		*line = 0;
	    for(line = pos; *line; line++, pos++) {
		if(*line == '\\') {
		    line++;
		    /* Handle special escaped characters */
		    switch(*line) {
			case 'n': *pos = '\n'; break;
			case 't': *pos = '\t'; break;
			default: *pos = *line; break;
		    }
		}
		else *pos = *line;
	    }
	    *pos = 0;
	    line++;
	}
	while(*line && isspace(*line))
	    line++;
	if(!*line)
	    break;
    }

    return i + 1;
}

int do_action(void)
{
    char line[SIZE];
    char* words[WORDS_NO];
    int nword;

    while(1) {
	printf("prompt: ");
	fflush(stdout);
	if(!gets(line))
	    return END_OF_INPUT;
	nword = split(line, words);
	if(!nword)
	    continue;
	if(!strcmp(words[0], "nothing"))
	    return DO_NOTHING;
	else if(!strcmp(words[0], "return"))
	    return FUNCTION_RETURN;
	else if(!strcmp(words[0], "throw")) {
	    if(nword != 2) {
		puts("usage: throw exception-object");
	    }
	    else {
		THROW((id)atoi(words[1]));
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "call")) {
	    if(nword != 2) {
		puts("usage: call function");
	    }
	    else {
		int i;
		for(i = 0; functions[i].name; i++)
		    if(!strcmp(functions[i].name, words[1])) {
			functions[i].function();
			break;
		    }
		if(!functions[i].name)
		    puts("function not found");
	    }
	}
	else if(!strcmp(words[0], "call1")) {
	    if(nword != 3) {
		puts("usage: call1 function arg");
	    }
	    else {
		int i;
		for(i = 0; functions1[i].name; i++)
		    if(!strcmp(functions1[i].name, words[1])) {
			functions1[i].function((id)atoi(words[2]));
			break;
		    }
		if(!functions1[i].name)
		    puts("function not found");
	    }
	}
	else if(!strcmp(words[0], "call2")) {
	    if(nword != 4) {
		puts("usage: call2 function arg1 arg2");
	    }
	    else {
		int i;
		for(i = 0; functions2[i].name; i++)
		    if(!strcmp(functions2[i].name, words[1])) {
			functions2[i].function((id)atoi(words[2]),
							(id)atoi(words[3]));
			break;
		    }
		if(!functions2[i].name)
		    puts("function not found");
	    }
	}
	else if(!strcmp(words[0], "class")) {
	    if(nword != 2) {
		puts("usage: class class-name");
	    }
	    else {
		id class = objc_get_class(words[1]);
		printf("%u\n", (unsigned)class);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "selector")) {
	    if(nword != 2) {
		puts("usage: selector selector-name");
	    }
	    else {
		SEL selector = sel_get_any_uid(words[1]);
		printf("%u\n", (unsigned)selector);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "send")) {
	    if(nword != 3) {
		puts("usage: send object-address selector-name");
	    }
	    else {
		id object = (id)atoi(words[1]);
		IMP imp;
		SEL selector;
		id result;

		if(!object) {
		    puts("0");
		    return DO_NOTHING;
		}
		selector = sel_get_any_uid(words[2]);
		if(!selector) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		imp = objc_msg_lookup(object, selector);
		if(!imp) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		result = (*imp)(object, selector);
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "send1")) {
	    if(nword != 4) {
		puts("usage: send1 object-address selector-name arg1");
	    }
	    else {
		id object = (id)atoi(words[1]);
		SEL selector;
		IMP imp = NULL;
		id result;
		struct objc_method* method;
		const char* type;

		if(!object) {
		    puts("0");
		    return DO_NOTHING;
		}
		selector = sel_get_any_uid(words[2]);
		if(!selector) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		method = object_is_instance(object)
		    ? class_get_instance_method(((id)object)->class_pointer,
						selector)
		    : class_get_class_method(((id)object)->class_pointer,
						selector);
		if(method)
		    imp = method->method_imp;
		if(!imp) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		type = method_get_nth_argument_type(method, 2);
		if(!type) {
		    puts("invalid number of arguments for selector");
		    return DO_NOTHING;
		}
		if(*type == _C_FLT) {
		    float arg1;
		    sscanf(words[3], "%f", &arg1);
		    result = (*(id (*)(id,SEL,float,...))imp)
				(object, selector, arg1);
		}
		else if(*type == _C_DBL) {
		    double arg1;
		    sscanf(words[3], "%lf", &arg1);
		    result = (*(id (*)(id,SEL,double,...))imp)
				(object, selector, arg1);
		}
		else {
		    id arg1 = (id)atoi(words[3]);
		    result = (*(id (*)(id,SEL,id,...))imp)
				(object, selector, arg1);
		}
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "send2")) {
	    if(nword != 5) {
		puts("usage: send2 object-address selector-name arg1 arg2");
	    }
	    else {
		id object = (id)atoi(words[1]);
		SEL selector;
		IMP imp = NULL;
		id result;
		struct objc_method* method;
		const char *type1, *type2;

		if(!object) {
		    puts("0");
		    return DO_NOTHING;
		}
		selector = sel_get_any_uid(words[2]);
		if(!selector) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		method = object_is_instance(object)
		    ? class_get_instance_method(((id)object)->class_pointer,
						selector)
		    : class_get_class_method(((id)object)->class_pointer,
						selector);
		if(method)
		    imp = method->method_imp;
		if(!imp) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		type1 = method_get_nth_argument_type(method, 2);
		if(!type1) {
		    puts("invalid number of arguments for selector");
		    return DO_NOTHING;
		}
		type2 = method_get_nth_argument_type(method, 3);
		if(!type2) {
		    puts("invalid number of arguments for selector");
		    return DO_NOTHING;
		}
		if(*type1 == _C_FLT) {
		    float arg1;
		    sscanf(words[3], "%f", &arg1);

		    if(*type2 == _C_FLT) {
			float arg2;
			sscanf(words[4], "%f", &arg2);
			result = (*(id (*)(id,SEL,float,float,...))imp)
				    (object, selector, arg1, arg2);
		    }
		    else if(*type2 == _C_DBL) {
			double arg2;
			sscanf(words[4], "%lf", &arg2);
			result = (*(id (*)(id,SEL,float,double,...))imp)
				    (object, selector, arg1, arg2);
		    }
		    else {
			id arg2 = (id)atoi(words[4]);
			result = (*(id (*)(id,SEL,float,id,...))imp)
				    (object, selector, arg1, arg2);
		    }
		}
		else if(*type1 == _C_DBL) {
		    double arg1;
		    sscanf(words[3], "%lf", &arg1);

		    if(*type2 == _C_FLT) {
			float arg2;
			sscanf(words[4], "%f", &arg2);
			result = (*(id (*)(id,SEL,double,float,...))imp)
				    (object, selector, arg1, arg2);
		    }
		    else if(*type2 == _C_DBL) {
			double arg2;
			sscanf(words[4], "%lf", &arg2);
			result = (*(id (*)(id,SEL,double,double,...))imp)
				    (object, selector, arg1, arg2);
		    }
		    else {
			id arg2 = (id)atoi(words[4]);
			result = (*(id (*)(id,SEL,double,id,...))imp)
				    (object, selector, arg1, arg2);
		    }
		}
		else {
		    id arg1 = (id)atoi(words[3]);

		    if(*type2 == _C_FLT) {
			float arg2;
			sscanf(words[4], "%f", &arg2);
			result = (*(id (*)(id,SEL,id,float,...))imp)
				    (object, selector, arg1, arg2);
		    }
		    else if(*type2 == _C_DBL) {
			double arg2;
			sscanf(words[4], "%lf", &arg2);
			result = (*(id (*)(id,SEL,id,double,...))imp)
				    (object, selector, arg1, arg2);
		    }
		    else {
			id arg2 = (id)atoi(words[4]);
			result = (*(id (*)(id,SEL,id,id,...))imp)
				    (object, selector, arg1, arg2);
		    }
		}
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "send3")) {
	    if(nword != 6) {
		puts("usage: send3 object-address selector-name arg1 arg2 arg3");
	    }
	    else {
		id object = (id)atoi(words[1]);
		SEL selector;
		IMP imp = NULL;
		struct objc_method* method;
		id arg1, arg2, arg3, result;

		if(!object) {
		    puts("0");
		    return DO_NOTHING;
		}
		selector = sel_get_any_uid(words[2]);
		if(!selector) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		method = object_is_instance(object)
		    ? class_get_instance_method(((id)object)->class_pointer,
						selector)
		    : class_get_class_method(((id)object)->class_pointer,
						selector);
		if(method)
		    imp = method->method_imp;
		if(!imp) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		arg1 = (id)atoi(words[3]);
		arg2 = (id)atoi(words[4]);
		arg3 = (id)atoi(words[5]);

		result = (*(id (*)(id,SEL,id,id,id))imp)
			    (object, selector, arg1, arg2, arg3);
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "send4")) {
	    if(nword != 7) {
		puts("usage: send4 object-address selector-name arg1 arg2 arg3 arg4");
	    }
	    else {
		id object = (id)atoi(words[1]);
		SEL selector;
		IMP imp = NULL;
		struct objc_method* method;
		id arg1, arg2, arg3, arg4, result;

		if(!object) {
		    puts("0");
		    return DO_NOTHING;
		}
		selector = sel_get_any_uid(words[2]);
		if(!selector) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		method = object_is_instance(object)
		    ? class_get_instance_method(((id)object)->class_pointer,
						selector)
		    : class_get_class_method(((id)object)->class_pointer,
						selector);
		if(method)
		    imp = method->method_imp;
		if(!imp) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		arg1 = (id)atoi(words[3]);
		arg2 = (id)atoi(words[4]);
		arg3 = (id)atoi(words[5]);
		arg4 = (id)atoi(words[6]);

		result = (*(id (*)(id,SEL,id,id,id,id))imp)
			    (object, selector, arg1, arg2, arg3, arg4);
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "send5")) {
	    if(nword != 8) {
		puts("usage: send5 object-address selector-name arg1 arg2 arg3 arg4 arg5");
	    }
	    else {
		id object = (id)atoi(words[1]);
		SEL selector;
		IMP imp = NULL;
		struct objc_method* method;
		id arg1, arg2, arg3, arg4, arg5, result;

		if(!object) {
		    puts("0");
		    return DO_NOTHING;
		}
		selector = sel_get_any_uid(words[2]);
		if(!selector) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		method = object_is_instance(object)
		    ? class_get_instance_method(((id)object)->class_pointer,
						selector)
		    : class_get_class_method(((id)object)->class_pointer,
						selector);
		if(method)
		    imp = method->method_imp;
		if(!imp) {
		    printf("unknown method: %s\n", words[2]);
		    return DO_NOTHING;
		}
		arg1 = (id)atoi(words[3]);
		arg2 = (id)atoi(words[4]);
		arg3 = (id)atoi(words[5]);
		arg4 = (id)atoi(words[6]);
		arg5 = (id)atoi(words[7]);

		result = (*(id (*)(id,SEL,id,id,id,id,id))imp)
			    (object, selector, arg1, arg2, arg3, arg4, arg5);
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "cstring")) {
	    if(nword != 2) {
		puts("usage: cstring \"...\"");
	    }
	    else {
		id string = [NSString stringWithCString:words[1]];
		const char* result = [string cString];
		printf("%u\n", (unsigned)result);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "string")) {
	    if(nword != 2) {
		puts("usage: string \"...\"");
	    }
	    else {
		id string = [NSString stringWithCString:words[1]];
		printf("%u\n", (unsigned)string);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "mutable_string")) {
	    if(nword != 2) {
		puts("usage: mutable_string \"...\"");
	    }
	    else {
		id string = [NSMutableString stringWithCString:words[1]];
		printf("%u\n", (unsigned)string);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "puts")) {
	    if(nword != 2) {
		puts("usage: puts cstring-address");
	    }
	    else {
		char* string = (char*)atoi(words[1]);
		puts(string ? string : "");
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "cstringdup")) {
	    if(nword != 3) {
		puts("usage: cstringdup cstring-address cstring-length");
	    }
	    else {
		char* new;
		char* string = (char*)atoi(words[1]);
		int length = (int)atoi(words[2]);
		if (length == 0)
		    length = strlen(string)+1;
		new = NSZoneMalloc(NULL, sizeof(char*)*length);
		memcpy(new, string, length);
		printf("%u\n", (unsigned)new);
		return DO_NOTHING;
	    }
	}
	else if(!strcmp(words[0], "version")) {
	    puts("Test driver 1.2");
	    exit(0);
	}
	else if(!strcmp(words[0], "BREAK"))
	    return EXCEPTION_BREAK;
	else if(!strcmp(words[0], "break"))
	    return BREAK_LOOP;
	else if(!strcmp(words[0], "quit"))
	    exit(0);
	else puts("unknown command");
    }
    return DO_NOTHING;
}

@implementation GoToBeginException
@end /* GoToBeginException */

int main(int argc, char** argv, char** env)
{
    extern void init_test();

#if LIB_FOUNDATION_LIBRARY
    [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif

    init_test();

    [NSAutoreleasePool enableDoubleReleaseCheck:YES];
    while(1) {
	volatile id pool = [NSAutoreleasePool new];

	TRY {
	    switch(do_action()) {
		case DO_NOTHING:	break;
		case EXCEPTION_BREAK:	BREAK;
		case FUNCTION_RETURN:
		case END_OF_INPUT:	exit(0);
	    }
	} END_TRY
	CATCH(GoToBeginException) {
	}
	OTHERWISE {
	    printf("caught unexpected exception %s with reason:\n%s\n",
		    [NSStringFromClass([localException class]) cString],
		    [[localException errorString] cString]);
	} END_CATCH

	[pool release];
    }
    return 0;
}
