/** \file **/
#define _GNU_SOURCE
#include <stdio.h>
#include <float.h>
#include <fenv.h>
#include <mcheck.h>
#include "AdunKernel/AdunKernel.h"

/** \mainpage

At its most basic a Molecular Simulator is a program that takes sets of molecules
(\e systems) and generates their time evolution, via some form of Netwons Second Law, using a numerical integrator
(\e simulator).
The values of the forces acting on the elements of the system, which are necessary for performing the integration are
calculated using a <em>force field</em>.

Adun creates Molecular Simulators using the objects of the Adun framework and where necessary their 
concrete components. The framework objects can be roughly divided into five types, based on their roles. 

<b>Major Components</b>

- AdForceField
- AdSystem
- AdInteractionSystem
- AdSimulator

These classes represent the main parts of a simulator. In the case of AdSimulator & AdForceField 
different subclasses exist representing different simulation/integration techniques and force fields repectively 
e.g. NewtonianSimulator, EnzymixForceField. Hence they are described as "Components" since the different 
subclasses can be interchanged to create different simulators. 
AdSystem (& AdInteractionSystem) on the other hand is customised by using a dataSource - AdSystems with
different dataSources represent different collections of atoms. 

<b>Minor Components</b>

- AdBoundaryImplementor
- AdNonbondedCalculator
- AdThermostat

Like AdForceField & AdSimulator each of these classes has subclasses representing different types of e.g. thermostat
etc.
Again these different types can be interchanged to customise behaviour. The are "Minor" in the
sense that they correspong to customisable parts of higher level objects e.g. AdThermostat represents 
the thermostating algorithms used by AdSimulator classes.

\b Infrastructure

- AdEnvironment
- AdIOManager
- AdMemoryManager
- AdCore

These are singleton (only one instance per running program) objects that provided basic services to the other
Framework objects and the program in general i.e. memory allocation and destruction, input/output and 
information on how each object should initialise itself (values for initialisation arguements). AdCore is special
since it creates and represents the entire simulator.

\b Utility

- AdGrid
- AdTimer
- AdNonbondedListHandler
- AdForceFieldTerm
- AdLinkedList

Instances of utility classes are useful in many circumstances and are used by different objects for 
different purposes. They provide general features helpful when performing simulations (or writing controllers). 
As the need arises other classes may be added to this list.

\b Internal

- AdDynamics
- AdBondedTopology
- AdNonbondedTopology
- AdState

These classes are internal to AdSystem and are not intended to be used or accessed independantly.


Adun can also customise its behaviour by loading plugins (called here
\e controllers). Controller objects are not directly part of the Adun Framework however the framework provides
for their existance. When loaded they can manipulate the simulator structure, create & destroy framework classes,
modify the simulators initialisation procedure aswell as use custom classes. They provide
the most powerful way to customise Adun and can be used to create complex simulation protocols etc. Developers
can write controller objects from scratch by following the AdController protocol or be subclassing the
AdController class which implements some basic controller requirements.

**/

/** 
\defgroup Kernel Kernel
*/

NSMutableDictionary* processArguements(void);

NSMutableDictionary* processArguements(void)
{
	int i, j;
	NSMutableArray* arguments;
	NSEnumerator *enumerator;
	id arg, array, invalidArgs, processedArgs;

	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< (int)[invalidArgs count]; i++)
			GSPrintf(stderr, @"%@\n", [invalidArgs objectAtIndex: i]);
		exit(1);
	}	

	return processedArgs;
}

void uncaughtExceptionHandler(NSException* exception);

void uncaughtExceptionHandler(NSException* exception)
{
	id core;
	NSError* error;
	NSMutableDictionary* errorInfo;
	NSNotification* terminationNotification;

	//post a AdSimulationDidFinishNotification 
	//containing exception information
	//This will cause the core to log the error,
	//post it to the server if necessary, and dump
	//all available information before exiting
	
	if((error = [[exception userInfo] objectForKey: @"AdKnownExceptionError"]) == nil)
	{	
		errorInfo = [NSMutableDictionary dictionary];
		[errorInfo setObject: [exception name] forKey: @"NSUnderlyingErrorKey"];
		[errorInfo setObject: [exception reason] forKey: @"NSLocalizedDescriptionKey"];
		if([exception userInfo] != nil)
			[errorInfo setObject: [exception userInfo] forKey: @"AdDetailedDescriptionKey"];

		NSLog(@"%@", errorInfo);

		error = [NSError errorWithDomain: @"AdKernelErrorDomain"
				code: 1
				userInfo: errorInfo];
	}

	//We post this exception so the core will log the error with AdunServer if necessary.

	[[NSNotificationCenter defaultCenter] postNotificationName: @"AdSimulationDidFinishNotification"
		object: nil
		userInfo: [NSDictionary dictionaryWithObject: error forKey: @"AdTerminationErrorKey"]];
}

//Logs the precision of the double type for the current processor
//Checks which floating point exceptions are enabled
//Clears floating point traps if any are set	

void floatingPointSettings(void);

void floatingPointSettings(void)
{
	GSPrintf(stderr, @"Floating Point Parameters for DOUBLE type.\n\n");
	GSPrintf(stderr, @"\tMantissa precision (base 2)  : %d.\n", DBL_MANT_DIG); 
	GSPrintf(stderr, @"\tMantissa precision (base 10) : %d.\n", DBL_DIG); 
	GSPrintf(stderr, @"\tMinumum exponent: %d -  Corresponds to %d in base 10.\n", DBL_MIN_EXP, DBL_MIN_10_EXP);
	GSPrintf(stderr, @"\tMaximum exponent: %d -  Corresponds to %d in base 10.\n", DBL_MAX_EXP, DBL_MAX_10_EXP);
	GSPrintf(stderr, @"\tMinumum floating point number %E\n", DBL_MIN);
	GSPrintf(stderr, @"\tMaximum floating point number %E\n", DBL_MAX);
	GSPrintf(stderr, @"\tEpsilon: %E.\n", DBL_EPSILON);
	GSPrintf(stderr, @"\tEpsilon is the smallest number such that '1.0 + epsilon != 1.0' is true.\n\n");

	floatingPointExceptionMask = 0;

	#ifdef FE_DIVBYZERO
		floatingPointExceptionMask = floatingPointExceptionMask | FE_DIVBYZERO;
		GSPrintf(stderr, @"FE_DIVBYZERO detection supported and enabled\n");
	#else
		GSPrintf(stderr, @"FE_DIVBYZERO not supported by the processor\n");
	#endif

	#ifdef FE_OVERFLOW
		floatingPointExceptionMask = floatingPointExceptionMask | FE_OVERFLOW;
		GSPrintf(stderr, @"FE_OVERFLOW detection supported and enabled\n");
	#else
		GSPrintf(stderr, @"FE_OVERFLOW not supported by the processor\n");
	#endif

	#ifdef FE_UNDERFLOW
		floatingPointExceptionMask = floatingPointExceptionMask | FE_UNDERFLOW;
		GSPrintf(stderr, @"FE_UNDERFLOW detection supported and enabled\n");
	#else
		GSPrintf(stderr, @"FE_UNDERFLOW not supported by the processor\n");
	#endif

	#ifdef FE_INVALID
		floatingPointExceptionMask = floatingPointExceptionMask | FE_INVALID;
		GSPrintf(stderr, @"FE_INVALID detection supported and enabled\n");
	#else
		GSPrintf(stderr, @"FE_INVALID not supported by the processor\n");
	#endif

	//disable traping of the supported errors
	
	fedisableexcept(floatingPointExceptionMask);
	GSPrintf(stderr, @"\n");
}

int main(int argc, char** argv)
{
	id pool = [[NSAutoreleasePool alloc] init];	
	id core, logFile, error, optionsFile;
	NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
	NSMutableDictionary* inputOptions, *commandLineArgs, *reloadOptions;

	//Register defaults

	[defaults setObject: [NSHomeDirectory() stringByAppendingPathComponent:
		[NSString stringWithFormat: @"adun/AdunCore_%d.log", [[NSProcessInfo processInfo] processIdentifier]]]
		forKey: @"LogFile"];
	[defaults setObject: [NSNumber numberWithBool: NO] forKey:@"OutputMemoryStatistics"];
	[defaults setObject: [NSNumber numberWithBool: NO] forKey:@"TraceMemory"];
	[defaults setObject: [NSNumber numberWithBool: YES]
		forKey: @"RedirectOutput"];
	[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
	[[NSUserDefaults standardUserDefaults] synchronize];

	//setup tracing

	if([[NSUserDefaults standardUserDefaults] boolForKey: @"TraceMemory"] == YES)
	{
		mtrace();
		mcheck(NULL);
	}

	logFile = [[NSUserDefaults standardUserDefaults] stringForKey: @"LogFile"];
	if(![[NSFileManager defaultManager] isWritableFileAtPath:
		 [logFile stringByDeletingLastPathComponent]])
	{
		logFile = [[[NSUserDefaults standardUserDefaults] 
				volatileDomainForName: NSRegistrationDomain]
				valueForKey:@"LogFile"];
		NSWarnLog(@"Invalid value for user default 'LogFile'. The specificed directory is not writable");
		NSWarnLog(@"Switching to registered default %@", logFile);
		if(![[NSFileManager defaultManager] 
			isWritableFileAtPath:
			 [logFile stringByDeletingLastPathComponent]])
		{
			[NSException raise: NSInternalInconsistencyException
				format: [NSString stringWithFormat:
				 @"Fallback logfile (%@) not writable. Cannot start.", logFile]];
		}
	} 
	freopen([logFile cString], "w", stderr);

	GSPrintf(stderr,  @"Checking floating point accuracy and exception detection support\n\n");
	floatingPointSettings();
	GSPrintf(stderr, @"Complete\n\n");

	GSPrintf(stderr, @"Initialising Framework\n");

	commandLineArgs = processArguements();

	core = nil;
	NS_DURING
	{
		NSSetUncaughtExceptionHandler(uncaughtExceptionHandler);
		core = [[AdCore alloc] init];
		if([commandLineArgs count] == 0)
		{
			if(![core connectToServer: &error])
			{
				NSLog(@"Error (%@) - %@", [error domain], 
					[[error userInfo] objectForKey: NSLocalizedDescriptionKey]);
				exit([error code]);
			}

			[core startRunLoop];
		}
		else
		{		
			if((optionsFile = [commandLineArgs objectForKey: @"Reload"]) != nil)
			{
				reloadOptions = [NSMutableDictionary dictionaryWithContentsOfFile: optionsFile];
				[core performSelector: @selector(reload:)
					withObject: reloadOptions];
				if((error = [core errorForCommand: @"reload"]) != nil)
				{
					NSLog(@"(%@) Error reloading trajectory",
					[error domain]);
					NSLog(@"Reason - %@", 
					[[error userInfo] objectForKey: NSLocalizedDescriptionKey]);
					exit([error code]);
				}
			}
			else if([commandLineArgs objectForKey : @"Process"] != nil)
			{
				inputOptions = [NSMutableDictionary dictionary];
				[inputOptions setObject: @"CommandLine" forKey: @"inputSourceName"];
				[inputOptions setObject: [commandLineArgs objectForKey: @"Process"] 
					forKey: @"processFile"];
	 
	 			//FIXME: Read from command line
	 			[core setOutputDirectories: [NSDictionary dictionaryWithObjectsAndKeys:
					@"output", @"simulationOutputDir",
					@"controllerOutput", @"controllerOutputDir", nil]];
				[core loadProcessData: inputOptions];
				[core loadController: nil];
				[core createSimulator: nil];
				[core createSystem: nil];
			}
			else
			{
				NSLog(@"Unknown command line options (%@)", commandLineArgs);
				exit(1);
			}
			
			[core main: nil];
		}
	}
	NS_HANDLER
	{
		//end the controller thread without entering
		//the normal end sequence.

		if([core simulationIsRunning])
			[[core controller] terminateSimulation: core];

		uncaughtExceptionHandler(localException);
	}
	NS_ENDHANDLER

	[core cleanUp];
	[core release];
	GSPrintf(stderr, @"Goodbye!\n");
	[pool release];

	return 0;
}

