/* TBinder.m
 * Written By:  Thomas Burkholder
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 */

#import "TBinder.h"

@implementation TBinder

int returnsDouble(id oClass, SEL aSelector)
{
	struct objc_method *p = class_getInstanceMethod(oClass,aSelector);
	
	if ((p->method_types) && (p->method_types[0]=='d'))
		return 1;
	return 0;
}

int takesDouble(id oClass, SEL aSelector)
{
	// modifiers always look like : "@16@8:12d16"
	struct objc_method *p = class_getInstanceMethod(oClass,aSelector);
	
	if ((p->method_types) && (p->method_types[8]=='d'))
		return 1;
	return 0;
}

int returnsFloat(id oClass, SEL aSelector)
{
	// modifiers always look like : "@16@8:12d16"
	struct objc_method *p = class_getInstanceMethod(oClass,aSelector);
	
	if ((p->method_types) && (p->method_types[0]=='f'))
		return 1;
	return 0;
}

int takesFloat(id oClass, SEL aSelector)
{
	struct objc_method *p = class_getInstanceMethod(oClass,aSelector);
	
	if ((p->method_types) && (p->method_types[8]=='f'))
		return 1;
	return 0;
}

- init
{
	[super init];
	dataSource = nil;
	interface = nil;
	interfaceAccessor = (SEL)0;
	interfaceModifier = (SEL)0;
	dataSourceAccessor = (SEL)0;
	dataSourceModifier = (SEL)0;
	flags.verify = NO;
	flags.autoUpdate = YES;
	return self;
}

- setTarget:anObject { target = anObject; return self; }
- target { return target; }
- setAction:(SEL)anAction { action = anAction; return self; }
- (SEL)action { return action; }
- setDataSource:anObject { dataSource = anObject; return self; }
- dataSource { return dataSource; }
- setInterface:anObject { interface = anObject; return self; }
- interface { return interface; }
- setInterfaceAccessor:(SEL)aSel { interfaceAccessor = aSel; return self; }
- (SEL)interfaceAccessor { return interfaceAccessor; }
- setInterfaceModifier:(SEL)aSel { interfaceModifier = aSel; return self; }
- (SEL)interfaceModifier { return interfaceModifier; }
- setDataSourceAccessor:(SEL)aSel { dataSourceAccessor = aSel; return self; }
- (SEL)dataSourceAccessor { return dataSourceAccessor; }
- setDataSourceModifier:(SEL)aSel { dataSourceModifier = aSel; return self; }
- (SEL)dataSourceModifier { return dataSourceModifier; }
- setVerify:(BOOL)yn { flags.verify = yn; return self; }
- (BOOL)verify { return flags.verify; }
- setAutoUpdate:(BOOL)yn { flags.autoUpdate = yn; return self; }
- (BOOL)autoUpdate { return flags.autoUpdate; }

- updateInterface:sender
{
	double (*doubleAcc)(id,SEL);
	float (*floatAcc)(id,SEL);
	id (*doubleMod)(id,SEL,double);
	id (*floatMod)(id,SEL,float);
	float f;
	double d;

	// objc_msgSend is stupid about returning float and double...
	if (returnsDouble([dataSource class], dataSourceAccessor)) {
		doubleAcc = (double (*)(id,SEL))[dataSource methodFor:dataSourceAccessor];
		d = doubleAcc(dataSource, dataSourceAccessor);
		if (takesDouble([interface class], interfaceModifier)) {
			doubleMod = (id (*)(id,SEL,double))[interface methodFor:interfaceModifier];
			doubleMod(interface,interfaceModifier,d);
		} else if (takesFloat([interface class], interfaceModifier)) {
			floatMod = (id (*)(id,SEL,float))[interface methodFor:interfaceModifier];
			floatMod(interface,interfaceModifier,(float)d);
		} else {  // passing a float or a double to something that won't take it.
			fprintf(stderr,"TBinder: attempt to bind double to non-float/double\n");
		}
	} else if (returnsFloat([dataSource class], dataSourceAccessor)) {
		floatAcc = (float (*)(id,SEL))[dataSource methodFor:dataSourceAccessor];
		f = floatAcc(dataSource, dataSourceAccessor);
		if (takesFloat([interface class], interfaceModifier)) {
			floatMod = (id (*)(id,SEL,float))[interface methodFor:interfaceModifier];
			floatMod(interface,interfaceModifier,f);
		} else if (takesDouble([interface class],interfaceModifier)) {
			doubleMod = (id (*)(id,SEL,double))[interface methodFor:interfaceModifier];
			doubleMod(interface,interfaceModifier,(double)f);
		} else {
			fprintf(stderr,"TBinder: attempt to bind float to non-float/double\n");
		}
	} else {  // Should cover ordinary stuff.
		objc_msgSend(interface,interfaceModifier,[dataSource perform:dataSourceAccessor]);
	}
	return self;
}

- updateDataSource:sender
{
	double (*doubleAcc)(id,SEL);
	id (*doubleMod)(id,SEL,double);
	float (*floatAcc)(id,SEL);
	id (*floatMod)(id,SEL,float);
	float f;
	double d;

	if (target && action) {
		[target perform:action with:sender];
	}
	// objc_msgSend is stupid about returning float and double...
	if (returnsDouble([interface class], interfaceAccessor)) {
		doubleAcc = (double (*)(id,SEL))[interface methodFor:interfaceAccessor];
		d = doubleAcc(interface, interfaceAccessor);
		if (takesDouble([dataSource class],dataSourceModifier)) {
			doubleMod = (id (*)(id,SEL,double))[dataSource methodFor:dataSourceModifier];
			doubleMod(dataSource,dataSourceModifier,d);
		} else if (takesFloat([dataSource class],dataSourceModifier)) {
			floatMod = (id (*)(id,SEL,float))[dataSource methodFor:dataSourceModifier];
			floatMod(dataSource,dataSourceModifier,(float)d);
		} else {
			fprintf(stderr,"TBinder: attempt to bind double to non-float/double\n");
		}
	} else if (returnsFloat([interface class], interfaceAccessor)) {
		floatAcc = (float (*)(id,SEL))[interface methodFor:interfaceAccessor];
		f = floatAcc(interface, interfaceAccessor);
		if (takesFloat([dataSource class],dataSourceModifier)) {
			floatMod = (id (*)(id,SEL,float))[dataSource methodFor:dataSourceModifier];
			floatMod(dataSource,dataSourceModifier,f);
		} else if (takesDouble([dataSource class],dataSourceModifier)) {
			doubleMod = (id (*)(id,SEL,double))[dataSource methodFor:dataSourceModifier];
			doubleMod(dataSource,dataSourceModifier,(double)f);
		} else {
			fprintf(stderr,"TBinder: attempt to bind float to non-float/double\n");
		}
	} else {  // Should cover ordinary stuff.
		objc_msgSend(dataSource,dataSourceModifier,[interface perform:interfaceAccessor]);
	}
	if (flags.verify) {
		[self updateInterface:sender];
	}
	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];
	dataSource = NXReadObject(stream);
	interface = NXReadObject(stream);
	target = NXReadObject(stream);
	NXReadTypes(stream,":::::c",&action,&interfaceAccessor,
				&interfaceModifier,&dataSourceAccessor,
				&dataSourceModifier, &flags);
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteObject(stream, dataSource);
	NXWriteObject(stream, interface);
	NXWriteObject(stream, target);
	NXWriteTypes(stream,":::::c",&action,&interfaceAccessor,
				&interfaceModifier,&dataSourceAccessor,
				&dataSourceModifier,&flags);
	return self;
}

@end
