/*
   Project: Adun

   Copyright (C) 2005 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   This application is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This application is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include "AdunKernel/AdunSystemNode.h"

/**
Private category for handling relationships
*/
@interface AdSystemNode (SystemRelationshipHandling)
- (void) initRelationshipVariables;
- (void) deallocRelationshipVariables;
- (BOOL) validateRelationship: (AdRelationship*) relationshipObject 
	forSystems: (NSArray*) systems 
	error: (NSError**) error;
- (BOOL) validateRelationship: (AdRelationship*) aRelationship error: (NSError**) error;
- (BOOL) validateRelationships: (NSArray*) anArray error: (NSError**) error;
- (id) _interactionSystemForRelationship: (AdRelationship*) relationshipObject;
- (id) _relationshipForInteractionSystem: (AdInteractionSystem*) system;
- (NSArray*) _relationshipsForSystemWithName: (NSString*) aName ofType: (NSString*) relationshipType;
- (NSArray*) _relationshipsForSystemWithName: (NSString*) aName;
- (void) _addRelationship: (AdRelationship*) aRelationship;
- (void) _removeRelationship: (AdRelationship*) aRelationship;
@end

/**
Private category for handling system status
*/
@interface AdSystemNode (SystemStatusObservation)
- (void) initStatusVariables;
- (void) deallocStatusVariables;
- (void) removeSystemFromStatusObservation: (id) aSystem;
- (void) observeStatusOfSystem: (id) aSystem;
@end

@implementation AdSystemNode

- (BOOL) _validateNamesOfSystems: (NSArray*) anArray error: (NSError**) error
{
	NSMutableArray* nameArray = [NSMutableArray array];
	NSEnumerator* arrayEnum, *subsystemNamesEnum;
	NSString* aString;
	id system, name;

	//first check all the systems in anArray have unique names
	
	arrayEnum = [anArray objectEnumerator];
	while(system = [arrayEnum nextObject])
		if([nameArray containsObject: [system systemName]])
		{
			//create an error object
			
			aString = [NSString stringWithFormat: @"Name %@ invalid", 
					[system systemName]];
			*error = AdKnownExceptionError(10, aString, nil, nil);	
		
			return NO;
		}	
		else
			[nameArray addObject:[system systemName]];

	//now check against all systems already in the node

	subsystemNamesEnum = [systemNames keyEnumerator];
	while(name = [subsystemNamesEnum nextObject])
		if([nameArray containsObject: name])
		{
			//create an error object
			aString = [NSString stringWithFormat: @"Name %@ invalid", 
					name];
			*error = AdKnownExceptionError(10, aString, nil, nil);	
			
			return NO;
		}

	return YES;
}

/*******************

Object Creation and Maintainence

********************/

//Temporary method so interaction systems AdSystemNode creates can be initialised from the
//environment - wont be necessary after AdSystemNode itself can be inititalised from the environment
- (id) initWithSystems: (NSArray*) arrayOne relationships: (NSArray*) arrayTwo environment: (id) object
{
	environment = object;
	return [self initWithSystems: arrayOne relationships: arrayTwo];
}

//designated initialiser
//FIXME: Due to SCAAS still relying on solute solvent distinction 
//the solute system must be first in arrayOne at the moment

- (id) initWithSystems: (NSArray*) arrayOne relationships: (NSArray*) arrayTwo
{
	id subsystem, relationship, object;
	NSEnumerator* subsystemEnum, *relationshipEnum, *arrayOneEnum;
	NSError* error;
	NSException *exception;
	NSMutableDictionary* userInfo;

	//initialisation chain will change soon - this is a hack
	if((self = [super initWithEnvironment: environment observe: NO]))
	{		
		//check that the contents of arrayOne are AdSystems
		arrayOneEnum = [arrayOne objectEnumerator];
		while((object = [arrayOneEnum nextObject]))
			if(![object isKindOfClass: [AdSystem class]])
				[NSException raise: NSInvalidArgumentException
					format: @"Incompatible object type for subsystem (%@).",
					[object description]];
		error = nil;
		[self initRelationshipVariables];
		//check that each system has a unique name
		if([self _validateNamesOfSystems: arrayOne 
			error: &error])
		{
			systems = [arrayOne mutableCopy];
			//get the system names
			systemNames = [NSMutableDictionary new];
			subsystemEnum = [systems objectEnumerator];
			while(subsystem = [subsystemEnum nextObject])
			{
				[systemNames setObject: subsystem
					forKey: [subsystem systemName]];
				[systemRelationshipsDict setObject: [NSMutableArray array] 
					forKey: [subsystem systemName]];
			 }		
		}
		else
		{
			//autorelease ourself since no one will have a
			//reference to us
			[self autorelease];
			if(error != nil)
				userInfo = [NSDictionary dictionaryWithObject: error
						forKey: @"AdKnownExceptionError"];
			else
				userInfo = nil;
				
			exception = [NSException 
					exceptionWithName: NSInternalInconsistencyException
					reason: @"Detected system with non-unique name."
					userInfo: userInfo];
			[exception raise];
		}

		//we use validateRelationships:error as this will check the
		//relationships against the systems that were added above

		if([self validateRelationships: arrayTwo
			error: &error])
		{		
			//create interaction systems + insert contents into containers
			//based on relationships
			//Add contain relationships first to avoid having to keep
			//recreating AdInteractionSystems
			systemRelationships = [arrayTwo mutableCopy];
			relationshipEnum = [systemRelationships objectEnumerator];
			// _addRelationship: (private method) adds the relationship
			//and peforms the necessary interaction system creation or insertion
			//however it performs no status related actions or validation (unlike the
			//public addRelationship: method. 
			while(relationship = [relationshipEnum nextObject])
				if([[relationship relationship] isEqual: @"Contains"])
					[self _addRelationship: relationship];

			relationshipEnum = [systemRelationships objectEnumerator];
			while(relationship = [relationshipEnum nextObject])
				if([[relationship relationship] isEqual: @"Interacts"])
					[self _addRelationship: relationship];
		}
		else
		{
			[self autorelease];
			if(error != nil)
				userInfo = [NSDictionary dictionaryWithObject: error
						forKey: @"AdKnownExceptionError"];
			else
				userInfo = nil;

			exception = [NSException 
					exceptionWithName: NSInternalInconsistencyException
					reason: @"Invalid relationship supplied."
					userInfo: userInfo];
			[exception raise];
		}

		//add the names of created AdInteractionSystems to systemNames
		//and add the interaction systems themselves to systems
		[systems addObjectsFromArray: interactionSystems];
		subsystemEnum = [interactionSystems objectEnumerator];
		while(subsystem = [subsystemEnum nextObject])
			[systemNames setObject: subsystem forKey: [subsystem systemName]];

		//set up status variables
		[self initStatusVariables];
		subsystemEnum = [systems objectEnumerator];
		while(subsystem = [subsystemEnum nextObject])
			[self observeStatusOfSystem: subsystem];
	}

	return self;
}

- (id) init
{
	return [self initWithSystems: nil relationships: nil];
}

//environment initialisers

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(environment != nil)
	{
		environment = object;
		//retrieve init arguments from environment

	/*	options = [environment optionsForDomain: @"AdSystemNode"];
		[self registerWithEnvironment];
		options = [environment optionsForDomain: @"AdSystemNode"];
		return [self initWithSubsystems: [options valueForKey:@"Subsystems"]
				relationships: [options valueForKey:@"Relationships"]];*/
	}
	else
		return [self init];
}

- (id) initWithEnvironment: (id) object
{
	return [self initWithEnvironment: object observe: YES];
}

- (void) dealloc
{
	[self deallocStatusVariables];
	[self deallocRelationshipVariables];
	[systemRelationships release];
	[systemNames release];
	[systems release];
	[super dealloc];
}

//addition and removal of systems and relationships

- (void) removeSystemWithName: (NSString*) aName
{
	id subsystem, relationship;
	NSArray* relationships;
	NSEnumerator* relationshipEnum;

	//remove a system and all relationships its involved in.
	//behaviour of containers is object dependant.
	//if it is an interaction system this has the effect of removing
	//the interaction relationship

	subsystem = [systemNames valueForKey: aName];
	if([subsystem isKindOfClass: [AdInteractionSystem class]])
	{
		//get the relationship that created this interaction
		//and use removeRelationship: instead

		relationship = [self _relationshipForInteractionSystem: subsystem];
		[self _removeRelationship: relationship];
	}
	else
	{
		relationships = [self relationshipsForSystemWithName: aName];
		relationshipEnum = [relationships objectEnumerator];
		while(relationship = [relationshipEnum nextObject])
			[self removeRelationship: relationship];

		[self removeSystemFromStatusObservation: subsystem];
		[systemRelationshipsDict removeObjectForKey: [subsystem systemName]];
		[systemNames removeObjectForKey: aName];
		[systems removeObject: subsystem];
	 }	
}

- (void) addSystem: (AdSystem*) aSystem 
	withRelationships: (NSArray*) relationships
{
	NSError* error = nil;
	NSEnumerator* relationshipEnum;
	NSMutableDictionary* userInfo;
	NSException* exception;
	id relationship;

	if(aSystem == nil)
		return;

	if([self _validateNamesOfSystems: [NSArray arrayWithObject: aSystem]
		error: &error])
	{	
		[systems addObject: aSystem];
		[systemNames setObject: aSystem forKey: [aSystem systemName]];
	}
	else
	{
		userInfo = [NSDictionary dictionaryWithObject: error
				forKey: @"AdKnownExceptionError"];
		exception = [NSException 
				exceptionWithName: NSInternalInconsistencyException
				reason: @"Cannot add system - non-unique name"
				userInfo: nil];
		[exception raise];
	}
	
	[systemRelationshipsDict setObject: [NSMutableArray array] 
		forKey: [aSystem systemName]];

	if(relationships != nil)
	{
		if([self validateRelationships: relationships error: &error])
		{
			relationshipEnum = [relationships objectEnumerator];
			while(relationship = [relationshipEnum nextObject])
				[self addRelationship: relationship];
		}		
		else
		{
			//remove the system and raise exception
			[systemRelationshipsDict removeObjectForKey: [aSystem systemName]];
			[systemNames removeObjectForKey: [aSystem systemName]];
			[systems removeObject: aSystem];
			userInfo = [NSDictionary dictionaryWithObject: error
					forKey: @"AdKnownExceptionError"];
			exception = [NSException 
					exceptionWithName: NSInternalInconsistencyException
					reason: @"Cannot add system - invalid relationship supplied."
					userInfo: nil];
			[exception raise];
		}	
	}
	
	//add the system to the status observation mechanism
	[self observeStatusOfSystem: aSystem];
}

- (void) addRelationship: (AdRelationship*) relationshipObject
{
	id system;

	[self _addRelationship: relationshipObject];
	if([[relationshipObject relationship] isEqual: @"Interacts"])
	{
		system = [self _interactionSystemForRelationship: relationshipObject];
		[systemNames setObject: system forKey: [system systemName]];
		[self observeStatusOfSystem: system];
	}	
}

- (void) removeRelationship: (AdRelationship*) relationshipObject
{
	id system;

	if([[relationshipObject relationship] isEqual: @"Interacts"])
	{
		system = [self _interactionSystemForRelationship: relationshipObject];
		[systemNames removeObjectForKey: [system systemName]];
		[self removeSystemFromStatusObservation: system];
	}	

	[self _removeRelationship: relationshipObject];
}

- (void) updateSystemsRelatedWithSystem: (AdSystem*) object
{
	//If a system reloads its data we have to update
	//interaction systems and containers.

}

/**********************

Protocols Methods

***********************/

/*
 * Environment observation
 */

- (void) updateForKey: (NSString*) key value: (id) value object: (id) object
{
	//no updates as of yet
}

- (void) registerWithEnvironment
{
	//nothing to register
}

- (void) deregisterWithEnvironment
{
	//nothing to deregister
}

- (void) synchroniseWithEnvironment
{
	//nothing to retrieve
}

- (void) setEnvironment: (id) object
{
	[self deregisterWithEnvironment];
	object = environment;
	[self registerWithEnvironment];
}

- (void) frameUpdate
{
	[systems makeObjectsPerformSelector: @selector(frameUpdate)];
}

- (void) update
{
	[systems makeObjectsPerformSelector: @selector(update)];
}

/********************

Coding 

*********************/

- (void) encodeWithCoder: (NSCoder*) encoder
{
	[super encodeWithCoder: encoder];
	if([encoder allowsKeyedCoding])
	{
		NSDebugLLog(@"Encode", @"Encoding %@", [self description]);
		[encoder encodeObject: systemRelationships forKey: @"SystemRelationships"];
		[encoder encodeObject: systems forKey: @"Subsystems"];
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];
}

- (id) initWithCoder: (NSCoder*) decoder
{
	NSEnumerator* subsystemEnum, *relationshipEnum;
	id subsystem, relationship;

	self = [super initWithCoder: decoder];
	if([decoder allowsKeyedCoding])
	{
		systems = [[decoder decodeObjectForKey: @"Subsystems"] retain];
		systemRelationships = [decoder decodeObjectForKey: @"SystemRelationships"];
		[systemRelationships retain];
		
		[self initRelationshipVariables];
		systemNames = [NSMutableDictionary new];
		subsystemEnum = [systems objectEnumerator];
		while(subsystem = [subsystemEnum nextObject])
		{
			[systemNames setObject: subsystem
				forKey: [subsystem systemName]];
			[systemRelationshipsDict setObject: [NSMutableArray array] 
				forKey: [subsystem systemName]];
	   	}		

		relationshipEnum = [systemRelationships objectEnumerator];
		while(relationship = [relationshipEnum nextObject])
			if([[relationship relationship] isEqual: @"Contains"])
				[self _addRelationship: relationship];

		relationshipEnum = [systemRelationships objectEnumerator];
		while(relationship = [relationshipEnum nextObject])
			if([[relationship relationship] isEqual: @"Interacts"])
				[self _addRelationship: relationship];

		//set up status variables
		[self initStatusVariables];
		subsystemEnum = [systems objectEnumerator];
		while(subsystem = [subsystemEnum nextObject])
			[self observeStatusOfSystem: subsystem];

		environment = [AdEnvironment globalEnvironment];
		if(environment != nil)
		{
			[self synchroniseWithEnvironment];
			[self registerWithEnvironment];
		}
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];

	return self;
}

/**
  Accessing systems and relationships.
  Forward these to the methods of the private SystemRelationshipHandling category
**/

- (id) interactionSystemForRelationship: (AdRelationship*) relationshipObject
{
	return [self _interactionSystemForRelationship: relationshipObject];
}	

- (id) relationshipForInteractionSystem: (AdInteractionSystem*) interactionSystem
{
	return [self _relationshipForInteractionSystem: interactionSystem];
}	

- (NSArray*) relationshipsForSystemWithName: (NSString*) aName
{
	return [self _relationshipsForSystemWithName: aName];
}

- (NSArray*) relationshipsForSystemWithName: (NSString*) aName ofType: (NSString*) relationshipType
{
	return [self _relationshipsForSystemWithName: aName ofType: relationshipType];
}

- (id) systemWithName: (NSString*) name
{
	return [systemNames objectForKey: name];
}

- (BOOL) containsRelationship: (AdRelationship*) relationshipObject
{
	return [systemRelationships containsObject: relationshipObject];
}

- (NSArray*) allRelationships
{
	return [systemRelationships copy];
}


/*******************

Accessors

********************/

- (NSArray*) activeSystems
{
	return activeSystems;
}

- (NSArray*) passiveSystems
{
	return passiveSystems;
}

- (NSArray*) inactiveSystems
{
	return inactiveSystems;
}

- (NSArray*) systemsOfType: (NSString*) type withStatus: (NSString*) status
{
	NSArray* selectedSystems;
	NSMutableArray* derivedArray;
	NSEnumerator* systemEnum;
	Class class;
	id system;

	selectedSystems = [statusDict objectForKey: status];
	if(selectedSystems == nil)
		[NSException raise: NSInvalidArgumentException 
			format: @"Unknown status %@", status];
	
	class = nil;
	if([type isEqual: @"Interaction"])
		class = [AdInteractionSystem class];
	else if([type isEqual: @"Standard"])
		class = [AdSystem class];
	else	
		[NSException raise: NSInvalidArgumentException 
			format: @"Unknown type %@", type];

	derivedArray = [NSMutableArray array];
	systemEnum = [selectedSystems objectEnumerator];
	while(system = [systemEnum nextObject])
		if([system isKindOfClass: class])
			[derivedArray addObject: system];

	return derivedArray;
}

- (id) allSystems
{
	return systems;
}

- (id) interactionSystems
{
	return interactionSystems;
}

//FIXME Again with the assumptions - this method is in the process of being deprecated
//\note we need this for SCAAS
- (id) soluteSystem
{
	return [systems objectAtIndex: 0];
}

//FIXME Again with the assumptions
- (id) solventSystem
{
	return [systems objectAtIndex: 1];
}

@end

/**
Category containing methods use to monitor the
status of systems
*/

@implementation AdSystemNode (SystemStatusObservation)

- (void) initStatusVariables
{		
	activeSystems = [NSMutableArray new];
	passiveSystems = [NSMutableArray new];
	inactiveSystems = [NSMutableArray new];
	activeAdSystems = [NSMutableArray new];

	statusDict = [NSMutableDictionary new];
	[statusDict setObject: activeSystems forKey: @"Active"];
	[statusDict setObject: passiveSystems forKey: @"Passive"];
	[statusDict setObject: inactiveSystems forKey: @"Inactive"];
}

- (void) deallocStatusVariables
{
	[activeAdSystems release];
	[statusDict release];
	[activeSystems release];
	[inactiveSystems release];
	[passiveSystems release];
}

- (void) _updateSubsystemStatus: (NSNotification*) aNotification
{
	id system, interaction, relationship;
	NSEnumerator* interactionEnum;
	NSString* previousStatus, *currentStatus;
	NSMutableDictionary* infoDict;
	NSEnumerator* relationshipEnum;

	system = [aNotification object];
	previousStatus = [[aNotification userInfo] objectForKey: @"PreviousStatus"];	
	currentStatus = [[aNotification userInfo] objectForKey: @"CurrentStatus"];	

	[[statusDict objectForKey: previousStatus] removeObject: system];
	[[statusDict objectForKey: currentStatus] addObject: system];

	//NB. To avoid receiving this notification multiple times while performing the
	//status changes on any systems we should remove ourselves from
	//receiving notifications from them.
	
	if([system isKindOfClass: [AdSystem class]])
	{
		if([currentStatus isEqual: @"Active"])
			[activeAdSystems addObject: system];
		else
			[activeAdSystems removeObject: system];

		if([currentStatus isEqual: @"Inactive"])
		{
			//If an AdSystem is made inactive we must deactivate 
			//all the interaction systems its involved in.
		
			relationshipEnum = [[self relationshipsForSystemWithName: [system systemName]
								ofType: @"Interacts"] 
							objectEnumerator];
			while(relationship = [relationshipEnum nextObject])
			{
				interaction = [self _interactionSystemForRelationship: relationship];
				[notificationCenter removeObserver: self
					name: @"AdSystemStatusDidChangeNotification"
					object: interaction];
				[interaction setStatus: @"Inactive"];
				[activeSystems removeObject: interaction];
				[inactiveSystems addObject: interaction];
				[notificationCenter addObserver: self
					selector: @selector(_updateSubsystemStatus:)
					name: @"AdSystemStatusDidChangeNotification"
					object: interaction];
			}
		}
	}

	//Send an AdSystemStatusDidChangeNotification for the original system

	infoDict = [NSMutableDictionary dictionaryWithDictionary: [aNotification userInfo]];
	[infoDict setObject: system forKey: @"Subsystem"];

	[notificationCenter 
		postNotificationName: @"AdSystemStatusDidChangeNotification"
		object: self
		userInfo: infoDict];
}

- (void) observeStatusOfSystem: (id) aSystem
{
	NSString* status;

	[notificationCenter addObserver: self
		selector: @selector(_updateSubsystemStatus:)
		name: @"AdSystemStatusDidChangeNotification"
		object: aSystem];	

	//add to the correct array
	
	if([aSystem isKindOfClass: [AdSystem class]])
		status = [(AdSystem*)aSystem status];
	else
		status = [(AdInteractionSystem*)aSystem status];
		
	[[statusDict objectForKey: status]
		addObject: aSystem];

	if([aSystem isKindOfClass: [AdSystem class]])
		if([status isEqual: @"Active"])
			[activeAdSystems addObject: aSystem];
}

- (void) removeSystemFromStatusObservation: (id) aSystem
{
	id status;

	[notificationCenter removeObserver: self
		name: @"AdSystemStatusDidChangeNotification"
		object: self];
	
	if([aSystem isKindOfClass: [AdSystem class]])
		status = [(AdSystem*)aSystem status];
	else
		status = [(AdInteractionSystem*)aSystem status];

	[[statusDict objectForKey: status]
		removeObject: aSystem];
	
	if([activeAdSystems containsObject: aSystem])
		[activeAdSystems removeObject: aSystem];
}

@end

/**
Category containing basic methods for adding and removing relationships
*/

@implementation AdSystemNode (SystemRelationshipHandling)

- (void) initRelationshipVariables
{
	interactionSystems = [NSMutableArray new];
	interactionDataSources = [NSMutableArray new];
	systemRelationshipsDict = [NSMutableDictionary new];
	allowedRelationships = [[NSArray alloc] initWithObjects:
					@"Interacts",
					@"Contains",
					nil];
	//holds derivedSystemName:Relationship pairs
	//i.e. the relationship that caused the system to be created
	derivedSystemsDict = [NSMutableDictionary new];
}

- (void) deallocRelationshipVariables
{
	[interactionSystems release];
	[interactionDataSources release];
	[systemRelationshipsDict release];
	[allowedRelationships release];
	[derivedSystemsDict release];
}

- (id) _interactionSystemForRelationship: (AdRelationship*) relationshipObject
{
	NSArray* systems;
	NSEnumerator* systemEnum;
	id array, system;

	if([[relationshipObject relationship] isEqual: @"Interacts"])
	{
		//each interacts relationship can only created one interaction
		//system. Therefore we can use allKeysForObject on the 
		//derivedSystemsDict which should return an array with one
		//entry which is the interaction system

		array = [derivedSystemsDict allKeysForObject: relationshipObject];
		if([array count] == 1)
			return [systemNames objectForKey: [array objectAtIndex: 0]];
		else
			return nil;
	
	}
	else 
		return nil;
}

- (id) _relationshipForInteractionSystem: (AdInteractionSystem*) interaction
{
	return [derivedSystemsDict objectForKey: [interaction systemName]];
}

- (void) _removeRelationship: (AdRelationship*) relationshipObject 
{
	id system;

	//remove an interact or contains relationship
	//if the relationship doesnt exist do nothing		

	if(![systemRelationships containsObject: relationshipObject])
		return;

	if([[relationshipObject relationship] isEqual: @"Interacts"])
	{
		system = [self _interactionSystemForRelationship: relationshipObject];		
		[interactionSystems removeObject: system];
		[interactionDataSources removeObject: [system dataSource]];
		[derivedSystemsDict removeObjectForKey: [system systemName]];
	}
	else
	{
		//Nothing to do for contains relationships at the moment
	}
	
	[[systemRelationshipsDict objectForKey: [relationshipObject objects]]
		removeObject: relationshipObject];
	[[systemRelationshipsDict objectForKey: [relationshipObject subjects]]
		removeObject: relationshipObject];
}

- (void) _addRelationship: (AdRelationship*) relationshipObject
{
	AdInteractionSystem* newSystem;
	NSString* systemOneName, *systemTwoName;
	NSArray* dataSource;
	AdSystem* container, *contained;

	if([[relationshipObject relationship] isEqual: @"Interacts"])
	{
		//create interaction system
		newSystem = [[AdInteractionSystem alloc] 
				initWithEnvironment: environment];
		[newSystem autorelease];
		systemOneName = [relationshipObject objects];
		systemTwoName = [relationshipObject subjects];
		dataSource = [NSArray arrayWithObjects: 
					[systemNames objectForKey: systemOneName],
					[systemNames objectForKey: systemTwoName],
					nil];
		[newSystem setDataSource: dataSource];
		[newSystem reloadData];
		//AdInteractionSystem objects automatically create their own name
		//based on their datasources.
		[interactionDataSources addObject: dataSource];
		[interactionSystems addObject: newSystem];
		[derivedSystemsDict setObject: relationshipObject 
			forKey: [newSystem systemName]];
	}
	else
	{	
		systemOneName = [relationshipObject subjects];
		systemTwoName = [relationshipObject objects];

		if([systemOneName isEqual:systemTwoName])
			[NSException raise: NSInternalInconsistencyException
				format: @"Subject and Object of relationship %@ are the same.",
				[relationshipObject description]];
		
		container = [systemNames objectForKey: systemOneName];
		contained = [systemNames objectForKey: systemTwoName];
		
		//insert one into the other

		[[container dataSource] setExclusionPoints: [[contained coordinates] pointerValue]
			exclusionRadius: 3.0];
		[container reloadData];
	}

	[[systemRelationshipsDict objectForKey: [relationshipObject objects]]
		addObject: relationshipObject];
	[[systemRelationshipsDict objectForKey: [relationshipObject subjects]]
		addObject: relationshipObject];
}

- (BOOL) validateRelationships: (NSArray*) anArray error: (NSError**) error
{
	NSEnumerator* relationshipEnum;
	id relationship;
	
	relationshipEnum = [anArray objectEnumerator];
	while(relationship = [relationshipEnum nextObject])
		if(![self validateRelationship: relationship error: error])
			return NO;

	return YES;		
}

/**
Checks that \e aRelationship is valid with respect to the systems currently in the node
*/
- (BOOL) validateRelationship: (AdRelationship*) relationshipObject error: (NSError**) error
{
	NSString* relationship, *aString;
	id subject, object;
	AdSystem *subjectSystem, *objectSystem;

	//check relationship is of a valid type

	relationship = [relationshipObject relationship];
	if(![allowedRelationships containsObject: relationship])
	{
		//create error	
		aString = [NSString stringWithFormat: 
				@"The relationship defined by the relationship object (%@) is not supported.", 
				[relationshipObject description]];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	if(![[relationshipObject type] isEqual: @"AdOneToOneRelationship"])
	{
		aString = [NSString stringWithFormat: 
				@"AdSystemNode only supports one to one relationships."];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	//check that the subject and object are among the current systems
	
	subject = [relationshipObject subjects];
	object = [relationshipObject objects];
	if((subjectSystem = [systemNames objectForKey: subject]) == nil)
	{
		aString = [NSString stringWithFormat: 
				@"Subject (%@) not among the current systems",
				subject];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	if((objectSystem = [systemNames objectForKey: object]) == nil)
	{
		aString = [NSString stringWithFormat: 
				@"Object (%@) not among the current systems array",
				subject];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	//finally check if that the referenced systems are AdSystems

	if(![objectSystem isKindOfClass: [AdSystem class]])
	{	
		aString = [NSString stringWithFormat: 
				@"System referenced by Object (%@) is not an AdSystem ",
				subject];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	if(![subjectSystem isKindOfClass: [AdSystem class]])
	{
		aString = [NSString stringWithFormat: 
				@"System referenced by Subject (%@) is not an AdSystem ",
				subject];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}
	
	return YES;	
}

/**
Checks that the relationship is valid with respect to the systems supplied
*/
- (BOOL) validateRelationship: (AdRelationship*) relationshipObject
	forSystems: (NSArray*) systemArray 
	error: (NSError**) error
{
	NSString* relationship, *aString;
	id subject, object, system;
	NSMutableArray* names;
	NSEnumerator* systemEnum;

	//check relationship is of a valid type

	relationship = [relationshipObject relationship];
	if(![allowedRelationships containsObject: relationship])
	{
		//create error	
		
		aString = [NSString stringWithFormat: 
				@"The relationship defined by the relationship object (%@) is not supported.", 
				[relationshipObject description]];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	//we only support one to one relationships
	if(![[relationshipObject type] isEqual: @"AdOneToOneRelationship"])
	{
		aString = [NSString stringWithFormat: 
				@"AdSystemNode only supports one to one relationships."];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}
	
	//check all the objects in the systems array are AdSystem instances

	systemEnum = [systemArray objectEnumerator];
	names = [NSMutableArray array];
	while(system = [systemEnum nextObject])
	{
		if(![system isKindOfClass: [AdSystem class]])
		{
			aString = [NSString stringWithFormat: 
					@"Non AdSystem instance present in system array (%@)",
					NSStringFromClass([system class])];
			*error = AdKnownExceptionError(10, aString, nil, nil);
			return NO;
		}
		[names addObject: [system systemName]];
	}	
	
	//check both subject and object are in the systems array

	subject = [relationshipObject subjects];
	object = [relationshipObject objects];
	if(![names containsObject: subject])
	{
		aString = [NSString stringWithFormat: 
				@"Subject (%@) not present in system array",
				subject];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	if(![names containsObject: object])
	{
		aString = [NSString stringWithFormat: 
				@"Object (%@) not present in system array",
				subject];
		*error = AdKnownExceptionError(10, aString, nil, nil);
		return NO;
	}

	return YES;
}

- (NSArray*) _relationshipsForSystemWithName: (NSString*) aName
{
	return [systemRelationshipsDict objectForKey: aName];
}

- (NSArray*) _relationshipsForSystemWithName: (NSString*) aName ofType: (NSString*) relationshipType
{
	NSArray* relationships;
	NSMutableArray* array;
	NSEnumerator* relationshipEnum;
	AdRelationship* relationship;
	
	array = [NSMutableArray array];
	relationships = [self relationshipsForSystemWithName: aName];
	relationshipEnum = [relationships objectEnumerator];
	while(relationship = [relationshipEnum nextObject])
		if([[relationship relationship] isEqual: relationshipType])
			[array addObject: relationship];

	return array;
}

@end
