#import "UKMock.h"
//#import <UnitKit/UnitKit.h>
#define UKFail() 
#include <math.h>

typedef struct
{
	@defs(UKMock);
} UKMockStruct;

@interface UKMockProxy : NSObject{
@public
	UKMockStruct *mock;
}
@end

@implementation UKMockProxy
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
{
	return [mock->realClass instanceMethodSignatureForSelector:aSelector];
}
- (void) forwardInvocation: (NSInvocation *)anInvocation
{
//	[anInvocation setReturnValue:NULL];
	[mock->invocations addObject:anInvocation];
}
@end

@implementation  UKMock 
/*
  Class realClass;
  NSMutableArray *invocations;
  int playbackState;
  id trampoline;
*/
- (id) initForClass: (Class)aClass
{
	if (nil == (self = [self init])) { return nil; }
	realClass = aClass;
	invocations = [NSMutableArray array];
	playbackState = 0;
	trampoline = [[UKMockProxy alloc] init];
	((UKMockProxy*)trampoline)->mock = (UKMockStruct*)self;
	return self;
}
- (id) expect
{
	return trampoline;
}
- (void) andReturn: (void*)aValue
{
	NSInvocation * inv = [invocations lastObject];
	if (nil == inv)
	{
		[NSException raise:@"UKMockInvalidReturn" 
		            format:@"Return value specified with no expected invocation"];
	}
	[inv setReturnValue:aValue];
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
{
	return [realClass instanceMethodSignatureForSelector:aSelector];
}
#define TEST(x, reason, ...) if (!(x)) { NSLog(reason, ##__VA_ARGS__); UKFail(); }
#define ARG_TEST(c, t, eq) case c:\
{\
	t to, from;\
	[inv getArgument:&to atIndex:i];\
	[anInvocation getArgument:&from atIndex:i];\
	TEST(eq, @"Argument %d of %@ has incorrect value", i - 2,\
		   	NSStringFromSelector([inv selector]));\
	break;\
}
#define ARG_TEST_PRIMITIVE(c, t) ARG_TEST(c, t, (to == from))
- (void) forwardInvocation: (NSInvocation *)anInvocation
{
	NSInvocation *inv = [invocations objectAtIndex:playbackState++];
	NSMethodSignature *sig = [inv methodSignature];
	TEST([sig isEqual:[anInvocation methodSignature]], @"Selectors do not match");
	for (unsigned i = 2, e = [sig numberOfArguments] ; i < e ; i++)
	{
		const char * type = [sig getArgumentTypeAtIndex:i];
		switch(*type)
		{
			ARG_TEST_PRIMITIVE('c', char);
			ARG_TEST_PRIMITIVE('i', int);
			ARG_TEST_PRIMITIVE('s', short);
			ARG_TEST_PRIMITIVE('l', long);
			ARG_TEST_PRIMITIVE('q', long long);
			ARG_TEST_PRIMITIVE('C', unsigned char);
			ARG_TEST_PRIMITIVE('I', unsigned int);
			ARG_TEST_PRIMITIVE('S', unsigned short);
			ARG_TEST_PRIMITIVE('L', unsigned long);
			ARG_TEST_PRIMITIVE('Q', unsigned long long);
			ARG_TEST('#', Class, [to isEqual:from]);
			ARG_TEST('@', id, [to isEqual:from]);
			ARG_TEST('f', float, fabsf(to - from) < 0.001f);
			ARG_TEST('d', float, fabs(to - from) < 0.001);
			default:
				//TODO: Allow comparison functions for structs to be registered
				NSLog(@"Warning, value of argument %d (type %s) not checked", 
						i - 2, type);
		}
	}
	void * buffer = alloca([sig methodReturnLength]);
	NS_DURING
	[inv getReturnValue:buffer];
	[anInvocation setReturnValue:buffer];
	NS_HANDLER
	NS_ENDHANDLER
}
@end
