/* 
   NSDistantObject.m

   Class which defines proxies for objects in other applications

   Copyright (C) 1997 Free Software Foundation, Inc.

   Author:  Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
   Rewrite: Richard Frith-Macdonald <richard@brainstorm.co.u>

   changed to encode/decode NSInvocations:
   Dr. H. Nikolaus Schaller <hns@computer.org>
   Date: October 2003
 
   complete rewrite:
   Dr. H. Nikolaus Schaller <hns@computer.org>
   Date: Jan 2006
 
   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
   */

// CODE NOT TESTED

#import <Foundation/NSRunLoop.h>
#import <Foundation/NSConnection.h>
#import <Foundation/NSDistantObject.h>
#import <Foundation/NSPort.h>
#import <Foundation/NSPortCoder.h>
#import <Foundation/NSPortMessage.h>
#import <Foundation/NSHashTable.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSData.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSString.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSException.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSInvocation.h>
#import <Foundation/NSMethodSignature.h>

#import "NSPrivate.h"

@implementation NSDistantObject

// this object forwards messages to the peer

+ (NSDistantObject*) proxyWithLocal:(id)anObject
						 connection:(NSConnection*)aConnection;
{ // this is initialization for vending objects
	return [[[self alloc] initWithLocal:anObject connection:aConnection] autorelease];
}

+ (NSDistantObject*) proxyWithTarget:(id)anObject
						  connection:(NSConnection*)aConnection;
{ // remoteObject is an id in another thread or another applicationŐs address space!
	return [[[self alloc] initWithTarget:anObject connection:aConnection] autorelease];
}

- (NSConnection*) connectionForProxy; { return _connection; }

- (id) initWithLocal:(id)anObject connection:(NSConnection*)aConnection;
{ // this is initialization for vending objects
	id obj=[aConnection _getLocal:anObject];
	NSLog(@"NSDistantObject: initWithLocal:%p connection:%@", anObject, aConnection);
	if(obj)
		{
		[self release];
		return anObject;	// already exists, return existing distant object instead
		}
	_connection=aConnection;
	_object=[anObject retain];
	[aConnection _mapLocal:self forRef:anObject];
	return self;
}

- (id) _localObject; { return _object; }

- (id) initWithTarget:(id)anObject connection:(NSConnection*)aConnection;
{ // remoteObject is an id in another thread or another applicationŐs address space!
	id obj=[aConnection _getRemote:anObject];
	NSLog(@"NSDistantObject: initWithTarget:%p connection:%@", anObject, aConnection);
	if(obj)
		{
		[self release];
		return anObject;	// already exists, return existing distant object instead
		}
	_connection=aConnection;
	_target=anObject;
	[aConnection _mapRemote:self forRef:anObject];
	return self;
}

- (void) setProtocolForProxy:(Protocol*)aProtocol;
{
#if 0
	struct x { int cnt; char *name; } *p=(struct x *) aProtocol;
	NSLog(@"setProtocolForProxy:%08x", aProtocol);
	NSLog(@"setProtocolForProxy:%08x", p->name);
	NSLog(@"setProtocolForProxy:%s", p->name);
#endif
	_protocol=aProtocol;	// protocols are sort of static objects so we don't have to retain
}

- (NSString*) description
{ // we should use [_protocol name] but that is currently broken
	struct x { int cnt; char *name; } *p=(struct x *) _protocol;
	return [NSString stringWithFormat: @"<%@ %lx>\nconnection=%@\nobject=%@\ntarget=%p\nprotocol=??",	// FIXME: should use %s for protocol!?
		NSStringFromClass([self class]),
		(unsigned long)self,
		_connection,
		_object,
		_target,
		_protocol?p->name:""];
}

+ (BOOL) respondsToSelector:(SEL)aSelector;
{
	NIMP;
	return NO;
}

- (void) dealloc;
{
	if(_target)
		{ // send a request over the connection to remove the proxy there!
		NSLog(@"*** should send a -dealloc to the remote side: %@", self);
		}
	[_connection _mapLocal:nil forRef:_object];
	[_connection _mapRemote:nil forRef:_target];
	[_object release];
	[super dealloc];
}

- (void) forwardInvocation:(NSInvocation *) invocation;
{ // this encodes the invocation, transmits and waits for a response - exceptions may be rised during communication
#if 0
	NSLog(@"NSDistantObject -forwardInvocation: %@ though %@", invocation, _connection);
#endif
	[_connection sendInvocation:invocation];
}

- (NSMethodSignature *) methodSignatureForSelector:(SEL)aSelector;
{
	static NSInvocation *i;	// cached invocation
	NSMethodSignature *ret;
#if 1
	NSLog(@"[NSDistantObject methodSignatureForSelector:\"%@\"]", NSStringFromSelector(aSelector));
#endif
	//	NSLog(@"%s %s %08x", sel_get_name(aSelector), sel_get_name(@selector(_forwardMethodSignatureForSelector:)), @selector(_forwardMethodSignatureForSelector:));
	if(SEL_EQ(aSelector, @selector(methodSignatureForSelector:)))
		{ // asking for my own signature! Must be a system-wide constant to avoid recursion
		return [NSObject instanceMethodSignatureForSelector:aSelector];	// ask NSObject
		}
	
	// FIXME: use a local Cache for method signatures of remote objects!!!
	// might depend on server we are communicating with - so index by selector&[connection sendPort]
#if 1
	NSLog(@"[NSDistantObject methodSignatureForSelector:] _object=%@ _protocol=%@", _object, _protocol);
#endif
	if(_object)
		return [_object methodSignatureForSelector:aSelector];	// ask local object
	if(_protocol)
		{ // we know the protocol
		const char *types = 0;
#ifndef __APPLE__
		struct objc_method_description *mth;
		if ((mth = [_protocol descriptionForInstanceMethod: aSelector]) == 0)
			mth = [_protocol descriptionForClassMethod: aSelector];
		if (mth != 0)
			types = mth->types;
		if (types == 0)
			return nil;
#endif
		return [NSMethodSignature signatureWithObjCTypes: types];	// convert into an NSMethodSignature
		}
#if 1
	NSLog(@"No protocol defined for NSDistantObject - so try forwarding the message");
#endif
	if(!i)
		{ // initialize cached invocation
		i=[[NSInvocation invocationWithMethodSignature:[NSObject instanceMethodSignatureForSelector:_cmd]] retain];	// use my own signature
		[i setSelector:_cmd];
		// should check [[NSObject instanceMethodSignatureForSelector: _cmd] methodReturnLength] == sizeof(ret)
		}
	// FIXME:
	// shouldn't we better ask the distant object for the @encode() as an NSString and
	// convert it into a local NSMethodSignature
	// instead of trying to encode a remote methodSignature???
	[i setTarget:self];
	[i setArgument:&aSelector atIndex:2];	// set as the argument the selector we want to know about
	[self forwardInvocation:i];				// and process
	[i getReturnValue:&ret];				// fetch from invocation
	return ret;
}

- (BOOL) respondsToSelector:(SEL)aSelector
{
	if(class_get_instance_method([NSDistantObject class], aSelector) != METHOD_NULL)
		return YES;	// we do directly respond
	if(_protocol)
		{ // check if protocol responds
		if([_protocol _descriptionForInstanceMethod:aSelector])
			return YES;
		if([_protocol _descriptionForClassMethod:aSelector])
			return YES;
		}
	if([_object respondsToSelector:aSelector])
		return YES;	// yes, the protocol says so
	return [super respondsToSelector:aSelector];	// we must ask the remote side
}

- (Class) classForPortCoder; { return [self class]; }

- (id) replacementObjectForPortCoder:(NSPortCoder*)coder { return self; }	// don't replace by another proxy

- (void) encodeWithCoder:(NSCoder *) coder;
{
	NSLog(@"%@ encodeWithCoder (ref=%p)", NSStringFromClass(isa), _target);
	// FIXME: how does this work for 64bit objects
	[coder encodeValueOfObjCType:@encode(unsigned long) at:&_target];
}

- (id) initWithCoder:(NSCoder *) coder;
{
	NSConnection *c=[(NSPortCoder *)coder connection];
	id ref;		// internal reference
	[coder decodeValueOfObjCType:@encode(unsigned long) at:&ref];
	NSLog(@"%@ decodeWithCoder (ref=%p)", NSStringFromClass(isa), ref);
	if(ref == nil)
		{ // remote asks for rootProxy, i.e. our root object
		[self release];
		return [[c rootObject] retain];
		}
	return [self initWithTarget:ref connection:c];	// look up or create
}

@end

@implementation NSProtocolChecker

+ (id) protocolCheckerWithTarget:(NSObject *)anObject protocol:(Protocol *)aProtocol;
{
	return [[[self alloc] initWithTarget:anObject protocol:aProtocol] autorelease];
}

- (id) initWithTarget:(NSObject *)anObject protocol:(Protocol *)aProtocol;
{
	if((self=[super init]) && !_protocol)	// don't allow remote side to send a new initWithTarget method to me...
		{
		_protocol=aProtocol;
		_target=[anObject retain];
		}
	return self;
}

- (void) dealloc;
{
	[_target release];
	[super dealloc];
}

- (Protocol *) protocol; { return _protocol; }
- (NSObject *) target; { return _target; }

- (BOOL) respondsToSelector:(SEL)aSelector;
{
	return [_protocol _descriptionForInstanceMethod:aSelector] != nil;
}

- (void) forwardInvocation:(NSInvocation *) invocation;
{
	if(![self respondsToSelector:[invocation selector]]) 
		[NSException raise:@"NSProtocolCheckerException" format:@"protocol does not permit to call %@", NSStringFromSelector([invocation selector])];
	[invocation invokeWithTarget:_target];
}

- (NSMethodSignature *) methodSignatureForSelector:(SEL)aSelector;
{ // ask protocol
	return [_protocol _descriptionForInstanceMethod:aSelector];
}
	
@end
