/*
   Project: UL

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

   Author: Michael Johnston

   Created: 2005-05-23 14:11:40 +0200 by 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 "ULSystem.h"

@implementation ULSystem

- (id) init
{
	self = [super init];

	return self;
}

- (void) setConfiguration: (NSMutableDictionary*) conf
{
	if(configuration != nil)
		[configuration release];

	configuration = conf;
	[configuration retain];
	[metadata setObject: [configuration objectForKey: @"SystemName"]
		forKey: @"Name"];
}

- (void) setTopology: (id) top
{
	if(topology != nil)
		[topology release];

	topology = top;
	[topology retain];
}

- (id) Topology
{
	return topology;
}

- (id) Configuration
{
	return configuration;
}

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

/*******
NSCoding Methods
********/


- (void) _encodeArrayOfDoubles: (NSArray*) array 
		usingCoder: (NSCoder*) encoder 
		forKey: (NSString*) key
{
	int i;
	int bytesLength, numberElements;
	double* bytes;

	numberElements = [array count];
	bytes = (double*)malloc(numberElements*sizeof(double));
	bytesLength = numberElements*sizeof(double);
	for(i=0; i<numberElements; i++)
		bytes[i] = [[array objectAtIndex: i] doubleValue];
	[encoder encodeBytes: (uint8_t*)bytes length: bytesLength forKey: key];
	free(bytes);
}

- (NSMutableArray*) _decodeArrayOfDoublesForKey: key usingCoder: (NSCoder*) decoder
{
	int i;
	int bytesLength, numberElements;
	double* bytes;
	NSMutableArray* array;

	bytes = (double*)[decoder decodeBytesForKey: key returnedLength: &bytesLength];
	array = [NSMutableArray arrayWithCapacity: 1];
	numberElements = bytesLength/sizeof(double);
	for(i=0; i<numberElements; i++)
		[array addObject: [NSNumber numberWithDouble: bytes[i]]];	

	return array;
}

- (void) _encodeArrayOfStrings: (NSArray*) array 
		usingCoder: (NSCoder*) encoder 
		forKey: (NSString*) key
{
	NSString* string;

	string = [array componentsJoinedByString: @"\\"];
	[encoder encodeObject: string forKey: key];
}

- (NSMutableArray*) _decodeArrayOfStringsForKey: key usingCoder: (NSCoder*) decoder
{
	id array;
	id string;

	string = [decoder decodeObjectForKey: key];
	array = [string componentsSeparatedByString: @"\\"];
	return array;
}

//we could store the bonded atoms as an index set....

- (void) _encodeBondedAtomsWithCoder: (NSCoder*) encoder
{
	int* bondedAtomsArray;
	int* numberBondedAtomsArray;
	int counter, length, i, j, numberOfAtoms;
	id bondedAtoms, atom;

	bondedAtoms = [configuration objectForKey: @"BondedAtoms"];
	numberOfAtoms = [bondedAtoms count];
	numberBondedAtomsArray = (int*)malloc(numberOfAtoms*sizeof(int));
	for(length = 0, i=0; i<numberOfAtoms; i++)
	{
		numberBondedAtomsArray[i] = [[bondedAtoms objectAtIndex: i] count];
		length += numberBondedAtomsArray[i];
	}
	bondedAtomsArray = (int*)malloc(length*sizeof(int));
	for(i=0, counter= 0; i<numberOfAtoms; i++)
	{
		atom = [bondedAtoms objectAtIndex: i];
		for(j=0; j< numberBondedAtomsArray[i]; j++)
		{
			bondedAtomsArray[counter] = [[atom objectAtIndex: j] intValue];
			counter ++;
		}
	}	

	[encoder encodeBytes: (uint8_t*)bondedAtomsArray 
		length: length*sizeof(int)		
		forKey: @"BondedAtomsArray"];
	[encoder encodeBytes: (uint8_t*)numberBondedAtomsArray
		length: numberOfAtoms*sizeof(int)		
		forKey: @"NumberBondedAtomsArray"];
}

- (void) _decodeBondedAtomsWithCoder: (NSCoder*) decoder
{
	int i, j, check;
	int length, numberElements, count;
	int* numberBondedAtomsArray;
	int* bondedAtomsArray;
	NSMutableArray* bondedAtoms, *list;
	NSNumber* index;

	numberBondedAtomsArray = (int*)[decoder decodeBytesForKey: @"NumberBondedAtomsArray"
						returnedLength: &length];
	numberElements = length/sizeof(int);
	bondedAtomsArray = (int*)[decoder decodeBytesForKey: @"BondedAtomsArray"
					returnedLength: &length];
	check = length/sizeof(int);	

	bondedAtoms = [NSMutableArray arrayWithCapacity:1];
	for(i=0, count = 0; i<numberElements; i++)
	{
		list = [NSMutableArray arrayWithCapacity:1];
		for(j=0; j<numberBondedAtomsArray[i]; j++)
		{
			index = [NSNumber numberWithInt: bondedAtomsArray[count]];
			[list addObject: index];
			count++;
		}
		[bondedAtoms addObject: list];
	}

	if(check!=count)
		[NSException raise: NSInternalInconsistencyException
			format: [NSString stringWithFormat:
			 @"Bonded atoms decode - Decoded %d indexes. Used %d.", check, count]];

	[configuration setObject: bondedAtoms forKey: @"BondedAtoms"];
}

- (void) _encodeIndexArray: (NSArray*) indexArray
		forKey: (NSString*) key 
		usingCoder: (NSCoder*) encoder 
{
	int i, j,count;
	int totalRanges, totalSets, length;
	int* rangesPerSet;
	NSRange* totalRangeArray, *rangeArray;

	totalSets = [indexArray count];

	for(totalRanges= 0,i=0; i<totalSets; i++)
		totalRanges += [[indexArray objectAtIndex: i] numberOfRanges];
	
	totalRangeArray = (NSRange*)malloc(totalRanges*sizeof(NSRange));
	rangesPerSet = (int*)malloc(totalSets*sizeof(int));
	for(count = 0,i=0; i<totalSets; i++)
	{
		rangeArray = [[indexArray objectAtIndex: i] indexSetToRangeArrayOfLength: &length];
		rangesPerSet[i] = length;
		for(j=0; j<length; j++)
		{
			totalRangeArray[count] = rangeArray[j];
			count++;
		}
		free(rangeArray);
	}

	[encoder encodeBytes: (uint8_t*)totalRangeArray 
		length: totalRanges*sizeof(NSRange) 
		forKey: key];
	[encoder encodeBytes: (uint8_t*)rangesPerSet 
		length: totalSets*sizeof(int) 
		forKey: [NSString stringWithFormat: @"%@.RangesPerSet", key]];
}

- (NSArray*) _decodeIndexArrayForKey: (NSString*) key usingCoder: (NSCoder*) decoder
{
	int i, j, count;
	int totalRanges, totalSets, length;
	int* rangesPerSet;
	NSRange* totalRangeArray, *rangeArray;
	NSIndexSet* set;
	id array;

	totalRangeArray = (NSRange*)[decoder decodeBytesForKey: key returnedLength: &length];
	totalRanges = length/sizeof(NSRange);
	rangesPerSet = (int*)[decoder decodeBytesForKey:
				[NSString stringWithFormat: @"%@.RangesPerSet", key] 
				returnedLength: &length];
	totalSets = length/sizeof(int);

	array = [NSMutableArray arrayWithCapacity: 1];

	for(count = 0, i=0; i<totalSets; i++)
	{
		rangeArray = (NSRange*)malloc(rangesPerSet[i]*sizeof(NSRange));
		for(j=0; j<rangesPerSet[i]; j++)
		{	
			rangeArray[j] = totalRangeArray[count];
			count++;
		}
		set = [NSIndexSet indexSetFromRangeArray: rangeArray 
				ofLength: rangesPerSet[i]];
		[array addObject: set];
		free(rangeArray);
	}

	if(totalRanges != count)
		[NSException raise: NSInternalInconsistencyException
			format: [NSString stringWithFormat: 
			@"Did not decode the same number of ranges encoded. %d %d", count, totalRanges]];
	
	return array;
}

- (id) _decodeInteractionForKey: (NSString*) key usingCoder: (NSCoder*) decoder 
{
	NSMutableDictionary* interaction;
	id object;

	interaction = [NSMutableDictionary dictionaryWithCapacity: 1];
	object = [decoder decodeObjectForKey: 
			[NSString stringWithFormat: @"%@.ElementsPerInteraction", key]];
	[interaction setObject: object forKey: @"ElementsPerInteraction"];
	object = [decoder decodeObjectForKey: 
			[NSString stringWithFormat: @"%@.InteractionsPerResidue", key]];
	[interaction setObject: object forKey: @"InteractionsPerResidue"];
	object = [decoder decodeObjectForKey: 
			[NSString stringWithFormat: @"%@.InteractionType", key]];
	[interaction setObject: object forKey: @"InteractionType"];
	object = [decoder decodeObjectForKey: 
			[NSString stringWithFormat: @"%@.Matrix", key]];
	[interaction setObject: object forKey: @"Matrix"];
	object = [self _decodeIndexArrayForKey:
			[NSString stringWithFormat: @"%@.ResidueInteractions", key]
			usingCoder: decoder];
	[interaction setObject: object forKey: @"ResidueInteractions"];

	return interaction;
}

- (void) _encodeInteraction: (NSDictionary*) interaction
 		forKey: (NSString*) key 
		usingCoder: (NSCoder*) encoder
{
	[encoder encodeObject: [interaction objectForKey: @"ElementsPerInteraction"]
		forKey: [NSString stringWithFormat: @"%@.ElementsPerInteraction", key]];
	[encoder encodeObject: [interaction objectForKey: @"InteractionsPerResidue"]
		forKey: [NSString stringWithFormat: @"%@.InteractionsPerResidue", key]];
	[encoder encodeObject: [interaction objectForKey: @"InteractionType"]
		forKey: [NSString stringWithFormat: @"%@.InteractionType", key]];
	[self _encodeIndexArray: [interaction objectForKey: @"ResidueInteractions"]
		forKey: [NSString stringWithFormat: @"%@.ResidueInteractions", key]
		usingCoder: encoder];
	[encoder encodeObject: [interaction objectForKey: @"Matrix"]
		forKey: [NSString stringWithFormat: @"%@.Matrix", key]];
}

- (id) initWithCoder: (NSCoder*) decoder
{
	id object, interaction;
	id bondedInteractionKeys, bondedInteractions;	
	id nonbondedInteractionKeys, nonbondedInteractions;
	NSEnumerator* interactionsEnum;

	[super initWithCoder: decoder];

	if([decoder allowsKeyedCoding])
	{
		configuration = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
		object = [decoder decodeObjectForKey: @"Coordinates"];
		[configuration setObject: object forKey: @"Coordinates"];
		object = [self _decodeArrayOfStringsForKey: @"AtomNames" usingCoder: decoder]; 
		[configuration setObject: object forKey: @"AtomNames"];
		object = [self _decodeArrayOfStringsForKey: @"LibraryNames" usingCoder: decoder]; 
		[configuration setObject: object forKey: @"LibraryNames"];
		object = [self _decodeArrayOfDoublesForKey: @"PartialCharges" usingCoder: decoder]; 
		[configuration setObject: object forKey: @"PartialCharges"];
		object = [self _decodeArrayOfDoublesForKey: @"Masses" usingCoder: decoder]; 
		[configuration setObject: object forKey: @"Masses"];
		object = [decoder decodeObjectForKey: @"Sequences"];
		[configuration setObject: object forKey: @"Sequences"];
	
		//bonded atoms		

		[self _decodeBondedAtomsWithCoder: decoder];

		topology = [[NSMutableDictionary dictionaryWithCapacity:1] retain];

		bondedInteractionKeys = [decoder decodeObjectForKey:@"BondedInteractions"];
		bondedInteractions = [NSMutableDictionary dictionaryWithCapacity:1];
		interactionsEnum = [bondedInteractionKeys objectEnumerator];
		while(interaction = [interactionsEnum nextObject])
		{
			object = [self _decodeInteractionForKey: interaction usingCoder: decoder];
			[bondedInteractions setObject: object forKey: interaction];
		}
		[topology setObject: bondedInteractions forKey: @"Bonded"];

		nonbondedInteractionKeys = [decoder decodeObjectForKey:@"NonbondedInteractions"];
		nonbondedInteractions = [NSMutableDictionary dictionaryWithCapacity:1];
		interactionsEnum = [nonbondedInteractionKeys objectEnumerator];
		while(interaction = [interactionsEnum nextObject])
		{
			if([interaction isEqual: @"Interactions"])
			{
				object = [self _decodeIndexArrayForKey: @"InteractionsArray"
						usingCoder: decoder];
				[nonbondedInteractions setObject: object forKey: interaction];
			}
			else
			{
				object = [self _decodeInteractionForKey: interaction	
						usingCoder: decoder];
				[nonbondedInteractions setObject: object forKey: interaction];
			}
		}
		[topology setObject: nonbondedInteractions forKey: @"Nonbonded"];
	}
	else
	{
		configuration = [[decoder decodeObject] retain];
		topology = [[decoder decodeObject] retain];
	}

	//backwards compatibility
	if([generaldata objectForKey: @"Class"] == nil)
		[generaldata setObject: @"ULSystem"
			forKey: @"Class"];

	NSDebugLLog(@"ULSystem", @"Configuration %@", configuration);
	NSDebugLLog(@"ULSystem", @"Topology %@", topology);

	return self;
}

- (void) encodeWithCoder: (NSCoder*) encoder
{
	int i;
	id key, interactionsDict, interaction;
	NSEnumerator* interactionsEnum;

	[super encodeWithCoder: encoder];

	if([encoder allowsKeyedCoding])
	{
		NSDebugLLog(@"ULSystem", @"Encoding configuration %@", configuration);
		[encoder encodeObject: [configuration objectForKey: @"Coordinates"]
			forKey: @"Coordinates"];
		[self _encodeArrayOfStrings: [configuration objectForKey: @"AtomNames"]
			usingCoder: encoder
			forKey: @"AtomNames"];
		[self _encodeArrayOfStrings: [configuration objectForKey: @"LibraryNames"]
			usingCoder: encoder
			forKey: @"LibraryNames"];
		[self _encodeArrayOfDoubles: [configuration objectForKey: @"PartialCharges"]
			usingCoder: encoder
			forKey: @"PartialCharges"];
		[self _encodeArrayOfDoubles: [configuration objectForKey: @"Masses"]
			usingCoder: encoder
			forKey: @"Masses"];
		[encoder encodeObject: [configuration objectForKey: @"Sequences"]
			forKey: @"Sequences"];

		[self _encodeBondedAtomsWithCoder: encoder];
	
		NSDebugLLog(@"ULSystem", @"Encoding topology %@", topology);

		interactionsDict = [topology objectForKey: @"Bonded"];
		[encoder encodeObject: [interactionsDict allKeys]
			forKey: @"BondedInteractions"];
		interactionsEnum = [interactionsDict keyEnumerator];
		while(interaction = [interactionsEnum nextObject])
			[self _encodeInteraction: [interactionsDict valueForKey: interaction]
				forKey: interaction
				usingCoder: encoder];
		
		interactionsDict = [topology objectForKey: @"Nonbonded"];
		[encoder encodeObject: [interactionsDict allKeys]
			forKey: @"NonbondedInteractions"];
		interactionsEnum = [interactionsDict keyEnumerator];
		while(interaction = [interactionsEnum nextObject])
			if([interaction isEqual: @"Interactions"])
				[self _encodeIndexArray: [interactionsDict valueForKey: interaction]
					forKey: @"InteractionsArray"
					usingCoder: encoder];
			else
				[self _encodeInteraction: [interactionsDict valueForKey: interaction]
					forKey: interaction
					usingCoder: encoder];
	}
	else
	{
		[encoder encodeObject: configuration];
		[encoder encodeObject: topology];
	}
}

/********
RemoteSystemAccess Methods
********/

- (bycopy NSArray*) transmitMatrixRowsForPath: (NSString*) matrixPath
{
	return [[self valueForKeyPath: matrixPath] matrixRows];	
}

- (bycopy NSArray*) transmitMatrixRowsForConfiguration
{
	return [[self valueForKeyPath: @"Configuration.Coordinates"] matrixRows];	
}

- (bycopy NSMutableArray*) transmitAtomTypes
{
	return [self valueForKeyPath: @"Configuration.AtomNames"];
}

- (bycopy NSMutableArray*) transmitMasses
{
	return [self valueForKeyPath: @"Configuration.Masses"];
}

- (bycopy NSMutableArray*) transmitPartialCharges
{
	return [self valueForKeyPath: @"Configuration.PartialCharges"];
}

/***
Couldnt send the indexset array directly so tried 
using range arrays for index sets. This wouldnt transmit
as a whole object (bycopy) but only worked if transmitting a
range at a time (not bycopy). Now we archive the entire
thing into a data object. Transmit the data object and then 
unarchive this.
*/

- (bycopy id) transmitNonbondedInteractions
{
	return [NSArchiver archivedDataWithRootObject: [self valueForKeyPath: @"Topology.Nonbonded.Interactions"]];
}


@end
