/*
   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.

   fThis 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/AdunForceFieldManager.h"

@implementation AdForceFieldManager

- (void) _removeForceFieldForSystem: (id) subsystem
{
	NSEnumerator* forceFieldEnum;
	AdForceField* obj, *forceField;

	forceFieldEnum = [forceFields objectEnumerator];
	forceField = nil;
	while(obj = [forceFieldEnum nextObject])
		if([[obj system] isEqual: subsystem])
			forceField = obj;

	//their may be no force field present for system
	if(forceField == nil)
		return;
	
	[forceFields removeObject: forceField];	
	numberOfForceFields--;
}

/**
Add or remove AdForceField objects from the forceFields array
as necessary.
*/
- (void) handleStatusChange: (NSNotification*) aNotification
{
	id subsystem, interaction, relationship;
	AdForceField* forceField;
	NSString* previousStatus, *currentStatus;
	NSEnumerator* relationshipEnum;

	subsystem = [[aNotification userInfo] objectForKey: @"System"];	
	previousStatus = [[aNotification userInfo] objectForKey: @"PreviousStatus"];
	currentStatus = [[aNotification userInfo] objectForKey: @"CurrentStatus"];
	
	if([currentStatus isEqual: @"Active"])
	{
		forceField = [AdForceField objectForEnvironment: environment];
		[forceField setSystem: subsystem];
		[forceFields addObject: forceField];
		numberOfForceFields++;
	}
	else
	{
		//if its not active then it must be inactive or passive
		//so remove it. Also if an AdSystem becomes inactive
		//we must remove the AdInteractionSystems its involved
		//in (if they are present)
		
		[self _removeForceFieldForSystem: subsystem];
		if([subsystem isKindOfClass: [AdSystem class]])
		{
			relationshipEnum = [[system relationshipsForSystemWithName: [subsystem name]
							ofType: @"Interacts"]
						objectEnumerator];
			while(relationship = [relationshipEnum nextObject])
			{
				interaction = [system interactionSystemForRelationship: relationship];
				[self _removeForceFieldForSystem: interaction];
			}	
		}
	}

	[subsystems release];
	subsystems = [system activeSystems];
	[subsystems retain];
}

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

Creation/Destruction

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

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

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

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(self = [super initWithEnvironment: object observe: value])
	{
		forceFields = [[NSMutableArray arrayWithCapacity:1] retain];
	
		//check if we need to implement boundary conditions

		if(environment != nil)
		{
			if([[environment valueForKey: @"ExplicitSolvent"] boolValue] == YES)
			{
				boundaryImplementor = [AdBoundaryImplementor
							 objectForEnvironment: environment];
				[boundaryImplementor retain];
			}
		}
		else
			boundaryImplementor = nil;
	}

	return self;
}	

- (void) dealloc
{
	[subsystems release];
	[forceFields release];
	[boundaryImplementor release];	
	[super dealloc];
}

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

Public Methods

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

- (void) calculateAccelerations
{
	int i, j, k;
	id subsystem;
	AdMatrix* accelerations, *coordinates;
	NSEnumerator* subsystemEnum;

	subsystemEnum = [subsystems objectEnumerator];
	while(subsystem = [subsystemEnum nextObject])
		if([subsystem isKindOfClass: [AdSystem class]])
			[subsystem zeroAccelerations];

	for(i=0; i<numberOfForceFields; i++)
		[[forceFields objectAtIndex: i] calculateForces];
	
	//\note boundary implementation may have to be moved back to each force field object
	//for the case in which there may be more than two solvents
	//this case will only currently work for one solute and one solvent - for release 1.0 it
	//should at least work for any number of solutes
	//addendum: The interface to AdBoundaryImplementor will be changed so it is passed the
	//solvent system and an array of all the solute systems it contains.

	[boundaryImplementor applyBoundaryConditions];

	subsystemEnum = [subsystems objectEnumerator];
	while(subsystem = [subsystemEnum nextObject])
		if([subsystem isKindOfClass: [AdSystem class]])
		{
			coordinates = [[subsystem coordinates] pointerValue];
			accelerations = [[subsystem accelerations] pointerValue];
			for(i=0; i<accelerations->no_rows; i++)
				for(j=0; j<3; j++)
					accelerations->matrix[i][j] *= coordinates->matrix[i][4];
		}
}

//object acessors
		
- (void) setSystem: (id) object
{
	int i, numberOfSubsystems;
	id subsystem, forceField;	

	if(system != nil)
	{
		[notificationCenter removeObserver:self 
			name:@"AdSystemStatusDidChangeNotification"
			object: system];
		[forceFields removeAllObjects];
	}

	system = object;

	//register for all notifications from System

	[notificationCenter addObserver: self 
		selector: @selector(handleStatusChange:)
		name: @"AdSystemStatusDidChangeNotification"
		object: system];

	if(subsystems != nil)
		[subsystems release];
	subsystems = [system activeSystems];
	[subsystems retain];
	numberOfSubsystems = [subsystems count];
	for(i=0; i<numberOfSubsystems; i++)
	{
		forceField = [AdForceField objectForEnvironment: environment];
		[forceField setSystem: [subsystems objectAtIndex: i]];
		[forceFields addObject: forceField];
	}
	
	numberOfForceFields = [forceFields count];
	
	if(boundaryImplementor != nil)
		[boundaryImplementor setValue: system forKey: @"system"];
}

@end
