/*
   Project: ResultsConverter

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

   Author: Michael Johnston

   Created: 2005-11-15 12:12:31 +0100 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 "ResultsConverter.h"

@implementation ResultsConverter

- (void) _printInfo: (ULSimulation*) resultsObject
{
	[resultsObject printAvailableInfo];
}

- (void) _printHelp
{
	GSPrintf(stderr, @"\nUsage: ResultsConverter [mode=modevalue] [options]\n");
	GSPrintf(stderr, @"All Options must be specified as option=value pairs\n\n");
	GSPrintf(stderr, @"Options common to all modes.\n");
	GSPrintf(stderr, @"  Required:\n");
	GSPrintf(stderr, @"\tTrajectory            A valid adun results output directory.\n"); 
	GSPrintf(stderr, @"  Optional:\n");
	GSPrintf(stderr, @"\tSubsystems            A comma seperated list of subsystems. Defaults to all\n\n");

	GSPrintf(stderr, @"Info specific options (Mode=Info):\n");
	GSPrintf(stderr, @"  Required:\n");
	GSPrintf(stderr, @"\tNone\n");	
	GSPrintf(stderr, @"  Optional:\n");
	GSPrintf(stderr, @"\tNone\n\n");	
	
	GSPrintf(stderr, @"Energy options (Mode=Energy):\n");
	GSPrintf(stderr, @"  Required:\n");
	GSPrintf(stderr, @"\tOutput                Output file name\n"); 
	GSPrintf(stderr, @"  Optional:\n");
	GSPrintf(stderr, @"\tTerms                 A comma seperated list of energy terms. Defaults to all\n");
	GSPrintf(stderr, @"\tUnit                  KCalMol|JouleMol|Simulation. Defaults to KCalMol\n");
	GSPrintf(stderr, @"\tStart                 The initial frame\n");
	GSPrintf(stderr, @"\tLength                The number of frames\n");
	GSPrintf(stderr, @"\tStepsize              The stepsize. Defaults to 1\n\n");

	GSPrintf(stderr, @"Configuration options (Mode=Configuration):\n");
	GSPrintf(stderr, @"  Required:\n");
	GSPrintf(stderr, @"\tNone\n");
	GSPrintf(stderr, @"  Optional:\n");
	GSPrintf(stderr, @"\tStart                 The initial frame\n");
	GSPrintf(stderr, @"\tLength                The number of frames\n");
	GSPrintf(stderr, @"\tStepsize              The stepsize. Defaults to 1\n\n");
}	

- (void) _processArguements
{
	int i, j;
	NSMutableArray* arguments;
	NSDictionary* defaults;
	NSEnumerator *enumerator;
	id arg, array, invalidArgs, optionalArgs, commandLineArgs;	
	id mode, value;

	processedArgs = [NSMutableDictionary dictionaryWithCapacity: 1];
	arguments = [[[NSProcessInfo processInfo] arguments] mutableCopy];
	invalidArgs = [NSMutableArray arrayWithCapacity: 1];

	//remove the first object - program name
	
	[arguments removeObjectAtIndex: 0];

	//check if the arguments all have valid format argname=value(s)

	NSDebugLLog(@"Arguments", @"Checking arguments format");

	enumerator = [arguments objectEnumerator];		
	while(arg = [enumerator nextObject])
	{
		array = [arg componentsSeparatedByString: @"="];
		if([array count] == 2)
			[processedArgs setObject: [array objectAtIndex: 1]
				forKey: [array objectAtIndex: 0]];
		else
			[invalidArgs addObject: array];
	}

	if([invalidArgs count] != 0)
	{
		GSPrintf(stderr, 
		@"\nError - The format of some options is invalid. Options must be in Option=Value pairs:\n");

		for(i=0; i< [invalidArgs count]; i++)
			GSPrintf(stderr, @"%@\n", [invalidArgs objectAtIndex: i]);
		[self _printHelp];
		exit(1);
	}	

	commandLineArgs = [[processedArgs allKeys] mutableCopy];

	//check Mode is present and valid
	
	NSDebugLLog(@"Arguments", @"Checking for mode presence");

	if((mode = [processedArgs objectForKey: @"Mode"]) == nil)
	{
		GSPrintf(stderr, @"\nMode must be specified.\n");
		[self _printHelp];
		exit(1);
	}
	
	if((validOptions = [validOptions objectForKey: mode]) == nil)	
	{
		GSPrintf(stderr, @"\nInvalid value for mode.\n");
		[self _printHelp];
		exit(1);
	}

	[commandLineArgs removeObject: @"Mode"];

	//check required args are present
	NSDebugLLog(@"Arguments", @"Checking for required args");
	
	enumerator = [[validOptions objectForKey: @"RequiredArgs"] objectEnumerator];
	while(arg = [enumerator nextObject])
		if([processedArgs objectForKey: arg] == nil)
		{
			GSPrintf(stderr, @"\nRequired arguement - %@ - not present\n", arg);
			[self _printHelp];
			exit(1);
		}

	[commandLineArgs removeObjectsInArray: [validOptions objectForKey: @"RequiredArgs"]];
	
	//check each of the remaining options are in the optionalArgs list
	
	NSDebugLLog(@"Arguments", @"Checking for optional args");
	
	[invalidArgs removeAllObjects];
	enumerator = [commandLineArgs objectEnumerator];
	optionalArgs = [validOptions valueForKey:@"OptionalArgs"];
	while(arg = [enumerator nextObject])
		if(![optionalArgs containsObject: arg])
			[invalidArgs addObject: arg];

	if([invalidArgs count] != 0)
	{
		GSPrintf(stderr, @"Detected %d unknown argument(s)\n", [invalidArgs count]);
		for(i=0; i< [invalidArgs count]; i++)
			GSPrintf(stderr, @"%@\n", [invalidArgs objectAtIndex: i]);
		[self _printHelp];
		exit(1);
	}

	//check which options were not given - insert these and give them default values
	
	NSDebugLLog(@"Arguments", @"Applying default values for missing args");

	enumerator = [[validOptions valueForKey:@"OptionalArgs"] objectEnumerator];
	defaults = [NSDictionary dictionaryWithObjects: [validOptions objectForKey: @"OptionDefaults"]
			forKeys: [validOptions objectForKey:@"OptionalArgs"]];
	while(arg = [enumerator nextObject])
		if([processedArgs objectForKey: arg] == nil)
			[processedArgs setObject: [defaults valueForKey: arg]
				forKey: arg];
	
	NSDebugLLog(@"Arguments", @"Complete.");
}

- (void) _validateArgs
{	
	int i, j;
	NSMutableArray* arguments;
	NSEnumerator *enumerator;
	NSError* error;
	id arg , value;	
	id subsystems, array;

	enumerator = [processedArgs keyEnumerator];
	while(arg = [enumerator nextObject])
	{
		//catch "All" values
		value = [processedArgs valueForKey: arg];
		
		if([value isEqual: @"All"])
		{
			if([arg isEqual: @"Subsystems"])
				value = [results availableSubsystems];
			else if([arg isEqual: @"Terms"])
			{
				subsystems = [results availableSubsystems];
				value = [NSMutableArray arrayWithCapacity: 1];
				for(i=0; i<[subsystems count]; i++)
				{
					array = [results availableTermsForSubsystem: 
							[subsystems objectAtIndex: i]]; 
					[value removeObjectsInArray: array];
					[value addObjectsFromArray: array];
				}
			}
			else
				[NSException raise: NSInvalidArgumentException
					format: @"Option %@ cannot be set to All", arg];
		}
		else
			if(![self validateValue: &value forKey: arg error:  &error])
				[NSException raise: NSInvalidArgumentException
					format: @"Invalid format for arguement %@", arg];

		[processedArgs setObject: value forKey: arg];
	}
}

- (Class) _loadBundle: (NSString*) pluginName fromDir: (NSString*) pluginDir 
{
	NSBundle *pluginBundle;
	NSString *pluginPath;
	NSString *temp;
	Class pluginClass;

	NSDebugLLog(@"ResultsConverter", 
		@"Plugin dir is %@. Plugin Name is %@", 
		pluginDir, pluginName);

	//add check to see if bundle actually exists

	pluginBundle = [NSBundle bundleWithPath: 
				[pluginDir stringByAppendingPathComponent: 
				pluginName]];
	if(pluginBundle == nil)
		[NSException raise: NSInvalidArgumentException 
			format: @"Specified plugin does not exist"];	

	NSDebugLLog(@"ResultsConverter", @"Plugin Bundle is %@", pluginBundle);
	NSDebugLLog(@"ResultsConverter", 
		@"Dynamicaly Loading Plugin from Directory: %@.\n\n", 
		[pluginBundle bundlePath]);

	if(pluginClass = [pluginBundle principalClass])
	{
		NSDebugLLog(@"ResultsConverter", 
			@"Found plugin (plugin=%@).\n", 
			[pluginClass description]);
	}
	else
		[NSException raise: NSInternalInconsistencyException
			 format: @"Specified plugin has no principal class"];

	NSDebugLLog(@"ResultsConverter", @"Loaded plugin\n");

	return pluginClass;
}

- (void) _loadConverterBundle
{
	NSString* pluginDir;
	Class pluginClass;

	pluginDir = [NSHomeDirectory() stringByAppendingPathComponent: @"adun/Plugins/Analysis"];
	
	if([[processedArgs valueForKey:@"Mode"] isEqual:@"Energy"])
		pluginClass = [self _loadBundle: @"EnergyConverter" fromDir: pluginDir];		
	else if([[processedArgs valueForKey:@"Mode"] isEqual:@"Configuration"])
		pluginClass = [self _loadBundle: @"ConformationConverter" fromDir: pluginDir];		
	else
		[NSException raise: @"NSInvalidArgumentException"
			format: @"Unknown mode %@", [processedArgs valueForKey:@"Mode"]];

	plugin = [pluginClass new];

	if(![plugin conformsToProtocol:@protocol(ULAnalysisPlugin)])
	{
		[NSException raise: NSInternalInconsistencyException 
			format: @"Specified plugins (%@) principal class does not conform to\
 ULAnalysisPlugin protocol", [pluginClass description]];
	}
}

- (void) _outputDataSets: (NSArray*) dataSets
{
	int i;
	NSString *outputDir;
	NSEnumerator* tableEnum, *dataSetEnum;
	NSString* format, *holder;
	id table, dataSet;

	outputDir = [processedArgs objectForKey: @"Trajectory"]; 
	dataSetEnum = [dataSets objectEnumerator];
	if([dataSets count] > 0)
	{
		i = 0;
		while(dataSet = [dataSetEnum nextObject])
		{
			tableEnum = [[dataSet dataMatrices] objectEnumerator];
			while(table = [tableEnum nextObject])
			{	
				holder = [NSString stringWithFormat: @"%@.%@", 
						[table name],
						[processedArgs objectForKey: @"Output"]];
				holder = [outputDir stringByAppendingPathComponent: holder];		
				[table writeMatrixToFile: holder];
				i++;	
			}	
		}
	}
}

- (id) init
{
	NSString* path;

	path = 	[[[NSBundle mainBundle] bundlePath] 
			stringByAppendingPathComponent: @"converterOptions.plist"];
	validOptions = [NSMutableDictionary dictionaryWithContentsOfFile: path]; 
	NSDebugLLog(@"ResultsConverter", @"Valid Options are %@", validOptions);
	
	return self;
}

- (void) main
{
	int i;
	FILE* file_p;
	NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
	NSEnumerator *subsystemEnum;
	NSArray *resultsTables;
	id subsystems, subsystem, dict, options;
	id output, storage;
	NSError* error;

	GSPrintf(stderr, @"Processing Arguments\n");

	[self _processArguements];

	//create the results object and set the options

	GSPrintf(stderr, @"Accessing Results\n");
	
	results = [[ULSimulation alloc] initWithName: @"Results"];
	storage = [[ULFileSystemSimulationStorage alloc] initForReadingSimulationDataAtPath: 
			 [processedArgs valueForKey: @"Trajectory"]];
	if(![storage isAccessible])
	{		
		error = [storage accessError];
		NSLog(@"Error - %@", [[error userInfo] objectForKey: NSLocalizedDescriptionKey]);
		exit(1);
	}
	[results setDataStorage: storage];
	[results loadData];
	
	options = [results optionsDict];

	NSDebugLLog(@"ResultsConverter", 
		@"Results class %@. Options %@", 
		NSStringFromClass([results class]), options);

	GSPrintf(stderr, @"Validating Arguments against Results\n");
	
	[self _validateArgs];

	NSDebugLLog(@"ResultsConverter",
		 @"Processed and Validated Args\n%@",
		processedArgs); 

	//convert 

	if(![[processedArgs valueForKey:@"Mode"] isEqual: @"Info"])
	{
		[self _loadConverterBundle];
		options = [plugin pluginOptions: [NSArray arrayWithObject: results]];

		[options setValue: [processedArgs valueForKey: @"Subsystems"]
			 forKeyPath: @"Subsystems.Selection"];
		[options setValue: [processedArgs valueForKey: @"Start"]
			 forKeyPath: @"Frames.Start"];
		[options setValue: [processedArgs valueForKey: @"Length"] 
			forKeyPath: @"Frames.Length"];
		[options setValue: [processedArgs valueForKey: @"Stepsize"] 
			forKeyPath: @"Stepsize"];

		if([processedArgs objectForKey: @"Unit"] != nil)
			[options setValue: [processedArgs valueForKey: @"Unit"] 
				forKey: @"EnergyUnit"]; 

		if([[processedArgs valueForKey:@"Mode"] isEqual: @"Energy"])
		{
			subsystemEnum = [[options valueForKey: @"Subsystems"] keyEnumerator];
			while(subsystem = [subsystemEnum nextObject])
				if(![subsystem isEqual: @"Selection"])
				{
					dict = [options valueForKeyPath: 
							[NSString stringWithFormat: 
								@"Subsystems.%@", subsystem]];
					[dict setObject: [processedArgs valueForKey: @"Terms"] 
						forKey: @"Selection"];
				}
		}

		GSPrintf(stderr, @"Performing Conversion\n");

		output = [plugin processInputs: [NSArray arrayWithObject: results]
				 userOptions: options]; 

		if(output != nil)
		{
			resultsTables = [output objectForKey: @"ULAnalysisPluginDataSets"];
			if(resultsTables != nil)
				[self _outputDataSets: resultsTables];
		}
	}
	else
		[self _printInfo: results];

	GSPrintf(stderr, @"Complete\n");
}

/*******
Arg Validation
*****/

- (BOOL) validateTerms: (id*) terms error: (NSError**) error
{
	if([*terms isKindOfClass: [NSArray class]])
		return YES;
	else if([*terms isKindOfClass: [NSString class]])
	{
		*terms = [*terms componentsSeparatedByString: @","];
		return YES;
	}
	else
		return NO;
}

- (BOOL) validateSubsystems: (id*) subsystems error: (NSError**) error
{
	if([*subsystems isKindOfClass: [NSArray class]])
		return YES;
	else if([*subsystems isKindOfClass: [NSString class]])
	{
		*subsystems = [*subsystems componentsSeparatedByString: @","];
		return YES;
	}
	else
		return NO;
}


@end
