/*
   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/AdunInteractionSystem.h"

@implementation AdInteractionSystem

- (AdMatrix*) _combineMatrix: (AdMatrix*) matrixOne
	 	withMatrix: (AdMatrix*) matrixTwo
{
	int i;
	AdMatrix* dest;
	
	dest = (AdMatrix*)malloc(sizeof(AdMatrix));
	dest->no_rows = matrixOne->no_rows + matrixTwo->no_rows;
	dest->no_columns = matrixOne->no_columns;
	dest->matrix = (double**)malloc(dest->no_rows*sizeof(double*));

	for(i=0; i < matrixOne->no_rows; i++)
		dest->matrix[i] = matrixOne->matrix[i];
	
	for(i=matrixOne->no_rows; i < dest->no_rows; i++)
		dest->matrix[i] = matrixTwo->matrix[i-matrixOne->no_rows];

	return dest;
}

- (InterTable*) _combineTable: (InterTable*) matrixOne
	 	withTable: (InterTable*) matrixTwo
{
	int i;
	InterTable* dest;
	
	dest = (InterTable*)malloc(sizeof(InterTable));
	dest->no_interactions = matrixOne->no_interactions + matrixTwo->no_interactions;
	dest->no_columns = matrixOne->no_columns;
	dest->table = (double**)malloc(dest->no_interactions*sizeof(double*));

	for(i=0; i < matrixOne->no_interactions; i++)
		dest->table[i] = matrixOne->table[i];
	
	for(i=matrixOne->no_interactions; i < dest->no_interactions; i++)
		dest->table[i] = matrixTwo->table[i-matrixOne->no_interactions];

	return dest;
}

//combine the necessary matrices

- (void) _createCombinedSystem
{	
	InterTable* combinedVDW;
	id subOneParams, subTwoParams;

	//create a combination coordinate velocity and acceleration matrix 
	
	combinedCoordinates = [self _combineMatrix: 
					[[subsystemOne valueForKeyPath:@"dynamics.coordinates"] pointerValue]
				 withMatrix: [[subsystemTwo valueForKeyPath:@"dynamics.coordinates"] pointerValue]];
	combinedVelocities = [self _combineMatrix:
					 [[subsystemOne valueForKeyPath:@"dynamics.velocities"] pointerValue]
				 withMatrix: [[subsystemTwo valueForKeyPath:@"dynamics.velocities"] pointerValue]];
	combinedAccelerations = [self _combineMatrix:
					 [[subsystemOne valueForKeyPath:@"dynamics.accelerations"] pointerValue]
				 withMatrix: [[subsystemTwo valueForKeyPath:@"dynamics.accelerations"] pointerValue]];
	
	//combine the nonbondedParameters - just VDW for the moment

	subOneParams = [subsystemOne valueForKeyPath:
				@"shortRangeNonbondedTopology.nonbondedInteractionTypes.TypeOneVDWInteraction"];
	subTwoParams = [subsystemTwo valueForKeyPath:
				@"shortRangeNonbondedTopology.nonbondedInteractionTypes.TypeOneVDWInteraction"];
	combinedVDW = [self _combineTable: [subOneParams pointerValue] withTable: [subTwoParams pointerValue]];
	
	[nonbondedInteractionTypes setValue: [NSValue valueWithPointer: combinedVDW] forKey: TypeOneVDWInteraction];
	[nonbondedInteractionTypes setValue: [NSNull null] forKey: CoulombElectrostatic];
}

//create the interaction list

- (void) _createSubsystemsInteractions
{
	int i, subsystemTwoAtoms, subsystemOneAtoms;
	NSMutableIndexSet* indexes;
	NSRange indexRange;

	subsystemOneAtoms = [[subsystemOne valueForKeyPath:@"dynamics.numberOfAtoms"] intValue];
	subsystemTwoAtoms = [[subsystemTwo valueForKeyPath:@"dynamics.numberOfAtoms"] intValue];
	numberOfAtoms = subsystemOneAtoms + subsystemTwoAtoms;	

	for(i=0; i<subsystemOneAtoms; i++)
	{
		indexRange.location = subsystemOneAtoms;
		indexRange.length = numberOfAtoms - indexRange.location;
		indexes = [NSMutableIndexSet indexSetWithIndexesInRange: indexRange];
		[nonbondedInteractions addObject: indexes];
	}
}

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

Object Creation

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

- (void) _useEnvironmentDefaults
{
	//no defaults
}

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(self = [super initWithEnvironment: object observe: value])
	{
		systemKeywords = [NSMutableArray arrayWithObjects: 
					@"Interaction",
					nil];
		[systemKeywords retain];
		//this ivar is temporary until AdInteractionSystem can handle bonded interactions
		bondedTopology = [NSMutableDictionary new];
		shortRangeNonbondedTopology = [[AdNonBondedTopology alloc] 
						initWithEnvironment: environment];
		nonbondedInteractions = [NSMutableArray new];
		nonbondedInteractionTypes = [NSMutableDictionary new];
			allowedStates = [[NSArray alloc] initWithObjects:
					@"Active", 
					@"Inactive", 
					nil];

		status = @"Active";
		[status retain];

		if(environment != nil)
		{
			[self synchroniseWithEnvironment];
			[self registerWithEnvironment];
		}
		else
			[self _useEnvironmentDefaults];

		dataLoaded = NO;	
	}
	
	return self;
}

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

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

- (void) _cleanUp
{
	InterTable* vdwInteraction;
	
	if(!dataLoaded)
		return;
	
	vdwInteraction = [[nonbondedInteractionTypes valueForKey: TypeOneVDWInteraction] pointerValue];
	free(combinedCoordinates->matrix);	
	free(combinedCoordinates);
	free(combinedVelocities->matrix);	
	free(combinedVelocities);
	free(combinedAccelerations->matrix);	
	free(combinedAccelerations);
	free(vdwInteraction->table);
	free(vdwInteraction);
}

- (void) dealloc
{
	[self _cleanUp];
	[state clearTimer];
	[state release];
	[bondedTopology release];
	[shortRangeNonbondedTopology release];
	[nonbondedInteractions release];
	[nonbondedInteractionTypes release];
	[systemKeywords release];
	[allowedStates release];
	[status release];
	[super dealloc];
}

- (void) handleDataSourceContentsChange: (NSNotification*) aNotification
{
	//if the contents of our data sources changed then rebuild all 
	//interactions

	[self _cleanUp];
	[self _createCombinedSystem];
	[self _createSubsystemsInteractions];
	[shortRangeNonbondedTopology reloadData];
	[state updateSystemData];
}

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

Updating

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

- (void) frameUpdate
{
	[state frameUpdate];
}

- (void) update
{
	NSDebugLLog(@"AdInteractionSystem", @"(%@) This method does nothing %@", 
		[self class], 
		NSStringFromSelector(_cmd));
}

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

DataSource delegation

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

//FIXME: Need to be more rigourous
- (void) setDataSource: (id) object;
{
	//remove ourselves from observing all current 
	//AdSystemContentsDidChangeNotification's 
	[notificationCenter removeObserver: self
		name: @"AdSystemContentsDidChangeNotification"
		object: nil];

	dataSource = object;
	[notificationCenter addObserver: self
		selector: @selector(handleDataSourceContentsChange:)
		name: @"AdSystemContentsDidChangeNotification"
		object: [dataSource objectAtIndex: 0]];
	[notificationCenter addObserver: self
		selector: @selector(handleDataSourceContentsChange:)
		name: @"AdSystemContentsDidChangeNotification"
		object: [dataSource objectAtIndex: 1]];
}
	
- (id) dataSource
{
	return dataSource;
}

- (void) reloadData
{
	//clean up variables created by any previous reloadData calls

	[self _cleanUp];

	subsystemOne = [dataSource objectAtIndex: 0];
	subsystemTwo = [dataSource objectAtIndex: 1];
	[self _createCombinedSystem];
	[self _createSubsystemsInteractions];
	
	[shortRangeNonbondedTopology setDataSource: self];
	[shortRangeNonbondedTopology reloadData];
	
	if(state == nil)
		state = [[AdState alloc] initWithEnvironment: environment system: self];
	else
		[state updateSystemData];
	
	[state setKineticUpdate: NO];

	dataLoaded = YES;
}

/*
 * 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];
}


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

NonbondedTopologyDataSource Methods

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

- (NSValue*) objectValueForCoordinates: (id) object
{
	return [NSValue valueWithPointer: combinedCoordinates];
}

- (NSArray*) objectValueForNonbondedInteractions: (id) object
{
	return nonbondedInteractions;
}

- (NSDictionary*) objectValueForNonbondedInteractionTypes: (id) object
{
	return [nonbondedInteractionTypes copy];
}

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

Coding

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

- (id) initWithCoder: (NSCoder*) decoder
{
	self = [super initWithCoder: decoder];
	if([decoder allowsKeyedCoding])
	{
		dataSource = [decoder decodeObjectForKey: @"DataSource"];
		subsystemOne = [dataSource objectAtIndex: 0];
		subsystemTwo = [dataSource objectAtIndex: 1];
		nonbondedInteractions = [NSMutableArray new];
		nonbondedInteractionTypes = [NSMutableDictionary new];
		[self _createCombinedSystem];
		[self _createSubsystemsInteractions];
		systemKeywords = [decoder decodeObjectForKey: @"SystemKeywords"];
		state = [decoder decodeObjectForKey: @"State"]; 
		shortRangeNonbondedTopology = [decoder decodeObjectForKey: @"ShortRangeNonbondedTopology"];
		[shortRangeNonbondedTopology retain];
		[state retain];
		[systemKeywords retain];	
		bondedTopology = [NSMutableDictionary new];
		status = [decoder decodeObjectForKey: @"Status"];
		[status retain];
		allowedStates = [[NSArray alloc] initWithObjects:
					@"Active", 
					@"Inactive", 
					nil];

		environment = [AdEnvironment globalEnvironment];
		if(environment != nil)
		{
			[self synchroniseWithEnvironment];
			[self registerWithEnvironment];
		}
		else
			[self _useEnvironmentDefaults];

	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];
		

	return self;
}

- (void) encodeWithCoder: (NSCoder*) encoder
{
	[super encodeWithCoder: encoder];
	if([encoder allowsKeyedCoding])
	{
		[encoder encodeObject: shortRangeNonbondedTopology 
			forKey: @"ShortRangeNonbondedTopology"];
		[encoder encodeObject: state forKey: @"State"];
		[encoder encodeConditionalObject:  dataSource
			forKey: @"DataSource"];
		[encoder encodeObject: systemKeywords forKey: @"SystemKeywords"]; 
		[encoder encodeObject: status forKey: @"Status"];
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];
}

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

Accessors

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

- (NSValue*) coordinates
{
	return [NSValue valueWithPointer: combinedCoordinates];	
}

- (NSValue*) velocities
{
	return [NSValue valueWithPointer: combinedVelocities];	
}

- (NSValue*) accelerations
{
	return [NSValue valueWithPointer: combinedAccelerations];	
}

/****

bondedTopology is an empty dict for now

*****/

- (id) dynamics
{
	return nil;
}

- (id) bondedTopology
{
	return nil;
}

- (id) shortRangeNonbondedTopology
{
	return shortRangeNonbondedTopology;
}

- (id) longRangeNonbondedTopology
{
	return nil;
}

- (id) state
{
	return state;
}

- (NSDictionary*) bondedInteractions
{
	return bondedTopology;
}

- (NSDictionary*) nonbondedInteractionTypes
{
	return [shortRangeNonbondedTopology valueForKey: @"nonbondedInteractionTypes"];
}

- (NSValue*) shortRangeNonbondedInteractions
{
	return [shortRangeNonbondedTopology valueForKey:@"nonbondedInteractions"];
}

- (NSValue*) longRangeNonbondedInteractions
{
	return [longRangeNonbondedTopology valueForKey:@"nonbondedInteractions"];
}

- (id) systemKeywords
{
	return systemKeywords;
}

- (NSString*) systemName
{
	return [NSString stringWithFormat: @"%@%@Interaction", 
			[[dataSource objectAtIndex: 0] systemName],
			[[dataSource objectAtIndex: 1] systemName]];
}

- (void) setCurrentForceFieldState: (id) forceFieldState
{
	[state setCurrentForceFieldState: forceFieldState];
}

- (NSString*) status
{
	return status;
}

- (void) setStatus: (NSString*) aString
{
	NSMutableDictionary* userInfo;

	if(![allowedStates containsObject: aString])
		[NSException raise: NSInvalidArgumentException
			format: @"Requested status %@ not valid", aString];

	//If the requested status is the same as the current status
	//do nothing
	if([aString isEqual: status])
		return;

	//If we are being activated but either of the dataSources are inactive
	//raise an NSInternalInconsistencyException

	if([aString isEqual: @"Active"])
	{
		if([[dataSource objectAtIndex: 0] isEqual: @"Inactive"]
			|| [[dataSource objectAtIndex: 1] isEqual: @"Inactive"])
		{
			[NSException raise: NSInternalInconsistencyException
				format: @"Cannot activate an interaction system when either of its consituent systems\
is inactive."];
		}
	}

	userInfo = [NSMutableDictionary dictionary];
	[userInfo setObject: status forKey: @"PreviousStatus"]; 
	[status release];
	
	status = [aString retain];
	[userInfo setObject: status forKey: @"CurrentStatus"]; 
	
	[notificationCenter postNotificationName: @"AdSystemStatusDidChangeNotification"
		object: self
		userInfo: userInfo];
}

@end
