/*
   Project: Adun

   Copyright (C) 2006 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/AdunController.h"

@implementation AdController

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

- (id) initWithEnvironment: (AdEnvironment*) environment
{
	[self initWithEnvironment: environment observe: YES];
}

- (id) initWithEnvironment: (AdEnvironment*) environment observe: (BOOL) observes
{
	if(self = [super init])
	{
		//This variable is used to communicate an error
		//in controller processing
		controllerError = nil;
		//by default we notify the core when we have finished
		notifyCore = YES;
	}

	return self;
}

- (void) dealloc
{
	[super dealloc];	
}

//Method called when the simulation thread exits
//This should only be called from the simulation thread!

- (void) simulationFinished
{
	NSDictionary* userInfo = nil;

	//If notifyCore is yes the thread has ended either normally,
	//by stopSimulation:, or by an exception in the thread.
	//In any of these cases we notify the Core.
	//Otherwise the controller was stopped via terminateSimulation:.
	//In this case we dont send any notification.

	if(notifyCore)
	{
		if(controllerError != nil)
			userInfo = [NSDictionary dictionaryWithObject: controllerError 
					forKey: @"AdTerminationErrorKey"];

		[[NSNotificationCenter defaultCenter] 
			postNotificationName: @"AdSimulationDidFinishNotification"
			object: self
			userInfo: userInfo];
	}

	[threadConnection invalidate];
	[threadConnection release];
	threadConnection = nil; 
}

//Private method that runs in the simulation thread

- (void) _threadedRunSimulation: (NSArray*) ports
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSConnection* connection;
	NSMutableDictionary* errorInfo;
	
	//Use the ports to connect to the main thread

	connection = [[NSConnection alloc] initWithReceivePort:[ports objectAtIndex:0]
			sendPort:[ports objectAtIndex:1]];
	[ports retain];

	/*
	 * We want to catch all exceptions since they will end the simulation
	 * and we want to notify the user what the cause of the failure was.
	 * (In MD exceptions can occur due to a varity of reasons that
	 * are not under the programmers control e.g. numerical instability
	 * due to user input.)
	 */

	NS_DURING
	{
		//do the controller work
		[self runSimulation];
	}	
	NS_HANDLER
	{
		//Known exceptions contain NSError objects describing what
		//happened in their userInfo dictionaries. If the exception
		//is not known we have to create the error object here.

		if((controllerError = [[localException userInfo] 
				objectForKey: @"AdKnownExceptionError"]) == nil)
		{	
			errorInfo = [NSMutableDictionary dictionary];
			[errorInfo setObject: [localException name] forKey: @"NSUnderlyingErrorKey"];
			[errorInfo setObject: [localException reason] forKey: @"NSLocalizedDescriptionKey"];
			if([localException userInfo] != nil)
				[errorInfo setObject: [localException userInfo] 
					forKey: @"AdDetailedDescriptionKey"];

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

	//we're finished so notify the main thread and exit
	
	[self performSelectorOnMainThread: @selector(simulationFinished)
		withObject: nil
		waitUntilDone: NO];
	[ports release];
	[connection release];
	[pool release];
	[NSThread exit];
}

/***
  Protocol Methods
***/

- (id) coreCreationDelegate
{
	//no creation delegate

	return nil;
}

- (void) coreWillStartSimulation: (AdCore*) core
{
	simulator = [core simulator];
}

- (void) runSimulation
{
	[simulator production];
}

- (void) runThreadedSimulation
{
	NSPort* receive_port, *send_port;
	NSArray *ports;

	//set up the ports that will be used by the NSConnection 
	//for interthread communication

	receive_port = [NSPort port];
	send_port = [NSPort port];
	ports = [NSArray arrayWithObjects: send_port, receive_port, NULL];

	//create the NSConnection

	threadConnection = [[NSConnection alloc] 
				initWithReceivePort:receive_port 
				sendPort:send_port];

	//we set this object i.e. the  main thread controller as root object
	//The simulation thread can then get a reference to it using rootProxy

	[threadConnection setRootObject:self];

	//detach the thread

	controllerError = nil;
	notifyCore = YES;

	[NSThread detachNewThreadSelector: @selector(_threadedRunSimulation:) 
		toTarget: self
		withObject: ports];
}

- (void) stopSimulation: (AdCore*) core
{
	notifyCore = YES;
	[simulator endSimulation];
}

- (void) terminateSimulation: (AdCore*) core
{
	notifyCore = NO;
	[simulator endSimulation];
}	

- (id) simulationResults
{
	return nil;
}

- (void) cleanUp
{
	//nothing to do
}

@end
