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

@implementation AdSystem

- (void) _removeTranslationalDOFFrom: (AdMatrix*) coordinates withVelocities: (AdMatrix*) velocities
{
	int i, j;
	double total_mass;
	double centre[3];
	
	//remove the translational degrees of freedom
	//by subtracting the centre of mass velocity
	//(sets the inital momentum to zero)

	for(i=0; i<3; i++)
		centre[i] = 0;

	for(total_mass = 0, i=0; i<numberOfAtoms; i++)
	{
		total_mass += coordinates->matrix[i][3];		

		for(j=0; j<3; j++)
			centre[j] += velocities->matrix[i][j]*coordinates->matrix[i][3];
	}

	for(i=0; i<3; i++)
		centre[i] = centre[i]/total_mass;	

	for(i=0; i<numberOfAtoms; i++)
		for(j=0; j<3; j++)
			velocities->matrix[i][j] -= centre[j];
}

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

Object Creation and Maintainence

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

- (void) _useEnvironmentDefaults
{
	//no defaults
}

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(self = [super initWithEnvironment: object observe: value])
	{
		//FIXME: Get the system keywords from data source
		systemKeywords = [NSMutableArray arrayWithObjects: 
				@"Unknown", nil];
		
		[systemKeywords retain];
		dynamics = [[AdDynamics alloc] initWithEnvironment: environment];
		bondedTopology = [[AdBondedTopology alloc] initWithEnvironment: environment];
		shortRangeNonbondedTopology = [[AdNonBondedTopology alloc] initWithEnvironment: environment];
		allowedStates = [[NSArray alloc] initWithObjects:
					@"Active", 
					@"Passive",
					@"Inactive", 
					nil];
		status = @"Active";
		[status retain];

		//we have no long range interactions at the moment
		longRangeNonbondedTopology = nil;
		//state = [[AdState alloc] initWithEnvironment: environment system: self];
		if(environment != nil)
		{
			[self synchroniseWithEnvironment];
			[self registerWithEnvironment];
		}
		else
			[self _useEnvironmentDefaults];
	}

	return self;
}

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

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

- (void) dealloc
{
	[allowedStates release];
	[status release];
	[state clearTimer];
	[state release];
	[dynamics release];
	[bondedTopology release];
	[shortRangeNonbondedTopology release];
	[longRangeNonbondedTopology release];
	[systemKeywords release];
}

- (void) setDataSource: (id) anObject
{
	if(![anObject conformsToProtocol: @protocol(AdSystemDataSource)])
		[NSException raise: NSInvalidArgumentException
			format: @"The data source does not conform to AdSystemDataSource"];

	dataSource = anObject;
	systemName = [dataSource valueForKey: @"systemName"];
	[dynamics setDataSource: dataSource];
	[bondedTopology setDataSource: dataSource];
	[shortRangeNonbondedTopology setDataSource: dataSource];
}

- (id) dataSource
{
	return dataSource;
}

- (void) reloadData
{
	systemName = [dataSource valueForKey: @"systemName"];
	[dynamics reloadData];
	[bondedTopology reloadData];
	[shortRangeNonbondedTopology reloadData];
	numberOfAtoms = [[dynamics valueForKey:@"numberOfAtoms"] intValue];
	
	//FIXME: This must be done here since state cannot deal with a system that has not
	//loaded its dataSource. See AdState class todo list

	if(state == nil)
		state = [[AdState alloc] initWithEnvironment: environment system: self];
	else
		[state updateSystemData];

	NSDebugLLog(@"AdSystem", @"Removing translational degrees of freedom (%@).", 
			NSStringFromClass([self class]));
	[self _removeTranslationalDOFFrom: [[dynamics valueForKey: @"coordinates"] pointerValue] 
				withVelocities: [[dynamics valueForKey: @"velocities"] pointerValue]];
	NSDebugLLog(@"AdSystem", @"Complete (%@).", NSStringFromClass([self class]));

	[notificationCenter postNotificationName: @"AdSystemContentsDidChangeNotification"
		object: self];
}	

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

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

Coding 

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

- (void) encodeWithCoder: (NSCoder*) encoder
{
	[super encodeWithCoder: encoder];
	if([encoder allowsKeyedCoding])
	{
		NSDebugLLog(@"Encode", @"Encoding %@", [self description]);
		[encoder encodeObject: dynamics forKey: @"Dynamics"];
		[encoder encodeObject: bondedTopology forKey: @"BondedTopology"]; 
		[encoder encodeObject: shortRangeNonbondedTopology forKey: @"ShortRangeNonbondedTopology"]; 
		[encoder encodeObject: longRangeNonbondedTopology forKey: @"LongRangeNonbondedTopology"]; 
		[encoder encodeObject: state forKey: @"State"];
		[encoder encodeObject: systemName forKey: @"SystemName"];
		[encoder encodeObject: systemKeywords forKey: @"SystemKeywords"];
		[encoder encodeObject: status forKey: @"Status"];
		[encoder encodeConditionalObject: dataSource forKey: @"DataSource"];
		NSDebugLLog(@"Encode", @"Complete %@", [self description]);
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];
}

- (id) initWithCoder: (NSCoder*) decoder
{
	self = [super initWithCoder: decoder];
	if([decoder allowsKeyedCoding])
	{
		dynamics = [decoder decodeObjectForKey: @"Dynamics"];
		bondedTopology = [decoder decodeObjectForKey: @"BondedTopology"];
		shortRangeNonbondedTopology = [decoder decodeObjectForKey: @"ShortRangeNonbondedTopology"];
		longRangeNonbondedTopology = [decoder decodeObjectForKey: @"LongRangeNonbondedTopology"];
		systemName = [decoder decodeObjectForKey: @"SystemName"];
		systemKeywords = [decoder decodeObjectForKey: @"SystemKeywords"];
		dataSource = [decoder decodeObjectForKey: @"DataSource"];
		state = [decoder decodeObjectForKey: @"State"];
		status = [decoder decodeObjectForKey: @"Status"];

		[dynamics retain];
		[bondedTopology retain];
		[shortRangeNonbondedTopology retain];
		[longRangeNonbondedTopology retain];
		[systemName retain];
		[systemKeywords retain];
		[state retain];
		[status retain];

		allowedStates = [[NSArray alloc] initWithObjects:
					@"Active", 
					@"Passive",
					@"Inactive", 
					nil];

		numberOfAtoms = [[dynamics valueForKey:@"numberOfAtoms"] intValue];

		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;
}

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

Accessors

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

- (id) dynamics
{
	return dynamics;
}

- (id) bondedTopology
{
	return bondedTopology;
}

- (id) shortRangeNonbondedTopology
{
	return shortRangeNonbondedTopology;
}

- (id) longRangeNonbondedTopology
{
	return longRangeNonbondedTopology;
}

- (id) state
{
	return state;
}

- (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;

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

- (id) systemKeywords
{
	return systemKeywords;
}

- (id) systemName
{
	return systemName;
}

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

 Facade Accessors 
We could just use paths for most of these though

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

- (double) kineticEnergy
{
	return [state kineticEnergy];
}

- (double) temperature
{
	return [state temperature];
}

- (double) time
{
	return [state time];
}

- (double) potentialEnergy
{
	return [state potentialEnergy];
}

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

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

- (NSDictionary*) bondedInteractions
{
	return [bondedTopology valueForKey: @"interactions"];
}

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

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

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

- (NSValue*) coordinates
{
	return [dynamics valueForKey: @"coordinates"];
}

- (NSValue*) accelerations
{
	return [dynamics valueForKey: @"accelerations"];
}

- (NSValue*) velocities
{
	return [dynamics valueForKey: @"velocities"];
}

- (void) zeroAccelerations
{
	[dynamics zeroAccelerations];
}

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

- (int) numberOfAtoms
{
	return [dynamics numberOfAtoms];
}

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

Methods delegated to system components

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

- (void) moveCentreOfMassToOrigin
{
	[dynamics moveCentreOfMassToOrigin];
}

@end

