/*	MiscMMAProcessor.m

	Copyright 1996 Uwe Hoffmann.

	This notice may not be removed from this source code.
	The use and distribution of this software is governed by the
	terms of the MiscKit license agreement.  Refer to the license
	document included with the MiscKit distribution for the terms.

	Version 2 (August 1996)
*/

#import <AppKit/AppKit.h>           

#import "MiscMMAProcessor.h"

static const char *mmaInitString[3] = {"MMA","-linkname", "math -mathlink"};

@interface MiscMMAProcessor(PrivateMethods)

- (void)_internalMMAError:(NSString *)errorMessage;
- (void)_internalOpenLink;
- (void)_internalCloseLink;

@end

@implementation MiscMMAProcessor (PrivateMethods)

- (void)_internalMMAError:(NSString *)errorMessage
{
	operationState.message = @"Mathlink error";
	operationState.error = YES;
	if(operationState.errorMessage)
		[operationState.errorMessage release];
	if(!errorMessage){
		if(!MLClearError(lp)){
			MLClose(lp);
        		MLDeinitialize(le);
			lp = NULL;
			le = NULL;
			operationState.errorMessage = 
				[[NSString stringWithFormat:@"Unrecoverable Error on Link:%s. Link closed.",
					MLErrorMessage(lp)] retain];
		} else 
			operationState.errorMessage = [[NSString stringWithFormat:@"%s",MLErrorMessage(lp)] retain];
	} else
		operationState.errorMessage = [errorMessage retain];
}  

- (void)_internalOpenLink
{ 
        le = MLInitialize(NULL);
	if(le == NULL){
		[self _internalMMAError:@"Could not initialize MathLink"];
		return;
	}
	lp = MLOpen(3, mmaInitString);
	if(lp == NULL){
		[self _internalMMAError:@"Could not open MathLink"];
		return;
	}
	operationState.message = @"Mathlink open";
	operationState.error = NO;
}

- (void)_internalCloseLink
{
	MLClose(lp);
        MLDeinitialize(le);
	lp = NULL;
	le = NULL;
	operationState.message = @"Mathlink closed";
	operationState.error = NO;
}

@end

@implementation MiscMMAProcessor

- init
{
	[super init];
	lp = le = NULL;
	operationState.message = @"No Mathlink";
	operationState.error = NO;
	operationState.errorMessage = nil;
	return self;
}

- (void)dealloc
{
	if(lp)
		[self closeLink];
	if(operationState.errorMessage)
		[operationState.errorMessage release];
	return [super dealloc];
}
	
- (BOOL)openLink
/*"Receiver opens a link to Mathematica. If a link already exists does nothing.
Returns YES upon success and NO upon error. Find out the error with #errorMessage."*/
{
	if(lp)
		return YES;
	[self _internalOpenLink];
	return !operationState.error;
}

- (void)closeLink
/*"Closes an existing link to Mathematica. If no link exists does nothing."*/
{
	if(!lp)
		return;
	MLPutFunction(lp, "Exit", 0);
        MLEndPacket(lp);
	[self _internalCloseLink];
}


- (MLINK)link
/*"Returns the link managed by the receiver."*/
{
	return lp;
}

- (BOOL)nextPacketWithTag:(int)tag
/*"Reads and discards packets from the link until a packet with tag %tag is encountered."*/
{
	BOOL finished;
	int packetTag;

	finished = NO;
	while(!finished){
            	packetTag = MLNextPacket(lp);
		if(packetTag == tag){
			finished = YES;
			operationState.message = @"Next packet ready";
			operationState.error = NO;
		} else if(packetTag == 0){
			finished = YES;
			[self _internalMMAError:@"Unknown or no packet"];
		} else
			MLNewPacket(lp);
		if(MLError(lp)){
			[self _internalMMAError:nil]; 
			finished = YES;
		}
	}
    	return !operationState.error;
}

- (BOOL)discardRemainingPackets
/*"Discards all remaining packets from the link."*/
{
	if([self nextPacketWithTag:INPUTNAMEPKT])
		MLNewPacket(lp);
	if(MLError(lp))
		[self _internalMMAError:nil];
    	return !operationState.error;
}

- (BOOL)error
/*"Returns whether the receiver has encountered an error in the link."*/
{
	return operationState.error;
}

- (NSString *)errorMessage
/*"Returns the error message or empty string if no error occurred."*/
{
	if(operationState.error)
		return operationState.errorMessage;
	else
		return @"";
}

- (NSString *)stateMessage
/*"Returns the state message for the state in which the receiver is."*/
{
	return operationState.message;
}

- (BOOL)enterExpression:(NSString *)expr
/*"Wraps the expression expr in the Mathematica function Enter[] and sends it over the link.
If succesful returns YES otherwise NO."*/
{
	[self openLink];
	
	MLPutFunction(lp, "Enter", 1);
        MLPutString(lp, (char *)[expr cString]);
        MLEndPacket(lp);

	if(MLError(lp))
		[self _internalMMAError:nil];

	operationState.message = @"Enter expression";

	return !operationState.error;
}

- (NSDictionary *)readReturnWithReader:(id <MiscMMAReturnPacketReading>)reader
/*"Reads result packets from link and returns a NSDictionary with all results.
The dictionary contains an object for the key "Plot" if a 
Mathematica graphic was part of the result.
The dictionary contains an object for the key "Text" if a 
Mathematica text result was part of the result.
The dictionary contains an object for the key "Syntax" if a 
Mathematica syntax error was part of the result.
RETURNPKT are handled by reader and all other packet types are discarded."*/
{
	BOOL finished, readAnything;
	int packetTag;
	NSString *resultText = nil;
	char *mmaStr;
	NSMutableData *plotData = nil;
	NSMutableDictionary *result;
        
	result = [NSMutableDictionary dictionary];

        readAnything = NO;
	finished = NO;
        while(!finished){
            	packetTag = MLNextPacket(lp);
            	if(packetTag == DISPLAYPKT || packetTag == DISPLAYENDPKT){
			if(!plotData)
				plotData = [NSMutableData data];
                    	MLGetNext(lp);
			MLGetString(lp, &mmaStr);
                        [plotData appendBytes:mmaStr length:strlen(mmaStr)];
                        MLDisownString(lp, mmaStr);
                        if(packetTag == DISPLAYENDPKT){
				[result setObject:plotData forKey:@"Plot"];
                       	 	readAnything = YES;
                        }        
                } else if(packetTag == RETURNTEXTPKT){
                    	MLGetNext(lp);
			MLGetString(lp, &mmaStr);
                        resultText = [NSString stringWithCString:mmaStr];
                        MLDisownString(lp, mmaStr);
			[result setObject:resultText forKey:@"Text"];
                        readAnything = YES;
                } else if(packetTag == RETURNPKT){
			if(reader)
				[reader readReturnPacketWithProcessor:self];
			MLNewPacket(lp);
                        readAnything = YES;
                } else if(packetTag == SYNTAXPKT){
                    	MLNewPacket(lp);
			[result setObject:@"Syntax Error" forKey:@"Syntax"];
                        readAnything = YES;
                } else if(packetTag == INPUTNAMEPKT){
			MLNewPacket(lp);
                    	if(readAnything)
				finished = YES;
		} else if(packetTag == 0){
			finished = YES;
			[self _internalMMAError:@"Unknown or no packet"];
		} else {
                    	MLNewPacket(lp);
                    	readAnything = YES;
                }        
		if(MLError(lp)){
			[self _internalMMAError:nil];
			finished = YES;
		}        
        }

	operationState.message = @"Read packet";

	if(operationState.error)
		[result setObject:operationState.errorMessage forKey:@"Error"];
		
    	return result;
}

- (NSDictionary *)readReturn
/*"Reads result packets from link and returns a NSDictionary with all results.
The dictionary contains an object for the key "Plot" if a 
Mathematica graphic was part of the result.
The dictionary contains an object for the key "Text" if a 
Mathematica text result was part of the result.
The dictionary contains an object for the key "Syntax" if a 
Mathematica syntax error was part of the result.
All other packet types are discarded."*/
{
	return [self readReturnWithReader:nil];
}

@end

