// MathConnector.m
// By Charles G. Fleming, Educational Computing Services, Allegheny College.
// Copyright 1993, Allegheny College.
// You may freely copy, distribute and reuse this code. 
// Allegheny College and the author disclaim any warranty of any kind, 
// expressed or implied, as to its fitness for any particular use.

#import "MathConnector.h"

#import <objc/NXBundle.h>

#import <strings.h>
#import <stdlib.h>
#include <stdio.h>

@implementation MathConnector
int   readPacket(MLINK);  
void  readAndPrintExpression(MLINK);
void  error(MLINK);

// Make sure linkPointer is initialized and then start up Mathematica.
- init
{
	id 	returnVal;

	self = [super init];
	linkPointer = NULL;
	
	// If we can't form a MathLink connection to Mathematica, we return 
	// nil.
 	if([self initMathConnection])
		returnVal = self;
	else 
	{
		NXRunAlertPanel("Error", "Unable to form a MathLink connection "
			"with Mathematica.", NULL, NULL, NULL);
		returnVal = nil;
	}
	return returnVal;
}

// Open a MathLink connection to Mathematica.	
- (MLINK)initMathConnection
{
    int numArgs = 2;
    char *args[] = {"-linkname", "math -mathlink", NULL};
		
	// Open a MathLink connection.
    linkPointer = MLOpen(numArgs, args); 

	// Take care of the initial messages sent by Mathematica.
	if(linkPointer != NULL)
	{	
    	// Mathematica sends a text packet containing the string
		//  " -- NeXT graphics initialized -- ".
    	MLNextPacket(linkPointer);
    	MLNewPacket(linkPointer);
    
    	// Mathematica then sends a text packet containing
		// the string "\n"
    	MLNextPacket(linkPointer);
    	MLNewPacket(linkPointer);
    
    	// Mathematica then sends an input name packet containing the
		// string
		// " In[1]:= "
    	MLNextPacket(linkPointer);
    	MLNewPacket(linkPointer);
	}

	if(MLError(linkPointer))
		linkPointer = NULL;

	return linkPointer;	
}

// Return the linkPointer if a connection was formed or NULL.
- (MLINK)link
{
	return linkPointer;
}	

// Send "non-matrix" commands to Mathematica.
- (BOOL)evaluateExpression:(char *)string resultType:(int)type
		result:(void**)result
{
	char *commandString, *resultString, *returnedString;
	BOOL success = YES;
	int returnedInt, *nonVolatileInt, packetTag, packetType;
	double returnedDouble, *nonVolatileDouble;

	commandString = malloc(strlen(string) + 50);
	switch(type)
	{
		case STRING:	
			sprintf(commandString, "ToString[%s, PageWidth->70]", string);
			break;
		case DEFINITION:	
			sprintf(commandString, "ToString[%s, PageWidth->70]", string);
			break;
		case GRAPHICS:	
			sprintf(commandString, "ToString[%s]", string);
			break;
		case INTEGER:
			strcpy(commandString, string);
			break;
		case REAL:
			strcpy(commandString, string);
			break;
		default:
			success = NO;
			break;
	}

	// If an illegal type was specified, we'll return NO.	
	if(!success)
	{
		free(commandString);
		return NO;
	}	
					

	// Ship the command off to Mathematica.
	MLReturnValue = MLPutFunction(linkPointer, "ToExpression", 1);
	MLPutString(linkPointer, commandString);
    MLEndPacket(linkPointer);
	free(commandString);

//readPacket(linkPointer);
//return NO;

	// Get the result of the execution of the command.
	packetType = MLNextPacket(linkPointer);
	switch(packetType)
	{
		case RETURNPKT:
			if(type == DEFINITION)
			{
				// Throw away anything returned.
				MLNewPacket(linkPointer);
				resultString = (char *)malloc(2);
				strcpy(resultString, "");
				*result = resultString;
				return YES;
			}	
			
			packetTag = MLGetNext(linkPointer);
			switch(packetTag)
			{
				case MLTKSTR:
					if(MLGetString(linkPointer, &returnedString))
					{
						resultString = (char *)malloc(strlen(returnedString)
								+1);
						strcpy(resultString, returnedString);
						*result = resultString;
						MLDisownString(linkPointer, returnedString);
					}
					else
						success = NO;
					break;
				case MLTKINT:
					if(MLGetInteger(linkPointer, &returnedInt))
					{
						nonVolatileInt = (int *)malloc(sizeof(int));
						memcpy(nonVolatileInt, &returnedInt, sizeof(int));
						*result = nonVolatileInt;
					}
					else
						success = NO;
					break;
				case MLTKREAL:
					if(MLGetDouble(linkPointer, &returnedDouble))
					{
						nonVolatileDouble = (double *)malloc(sizeof(double));
						memcpy(nonVolatileDouble, &returnedDouble,
								sizeof(double));
						*result = nonVolatileDouble;
					}
					else
						success = NO;
					break;
				case MLTKSYM:
				case MLTKFUNC:	
					// Discard the packet.
					MLNewPacket(linkPointer);
					success = NO;
					break;
				case MLTKERROR:	
				default:
					MLNewPacket(linkPointer);
					if(MLError(linkPointer))
						MLClearError(linkPointer);
					success = NO;	
					break;
			}
			break;
		case MESSAGEPKT:
			// Discard the MLTKSYM and MLTSTR that will be returned.
			MLNewPacket(linkPointer);

			// Discard all packets until we get a RETURNPKT.
			while(MLNextPacket(linkPointer) != RETURNPKT)
				MLNewPacket(linkPointer);
			
			// Discard the return packet.
			MLNewPacket(linkPointer);
			success = NO;
			break;		
		case DISPLAYPKT:
			// We shouldn't ever get one of these.
			MLNewPacket(linkPointer);
			success = NO;
			break;
		default:
			// Discard any mysterious packet.
			MLNewPacket(linkPointer);
			success = NO;
			break;
	}			
	return success;
}	


// Send "matrix" commands to Mathematica.
- (BOOL)evaluateMatrixExpression:(char *)string resultType:(int)type
		result:(void**)result rows:(int *)numRows cols:(int *)numCols
{
	char *commandString, *resultString, *symbol, *returnedString;
	BOOL success = YES;	
	int packetType, packetTag, *returnedIntList, *nonVolatileIntList, row;
	long listCount;
	double *returnedRealList, *nonVolatileRealList, *returnedRow;
	double *returnedMatrix; 

	commandString = malloc(strlen(string) + 50);
	switch(type)
	{
		case STRING:	
			sprintf(commandString, "ToString[%s, PageWidth->70]", string);
			break;
		case DEFINITION:	
			sprintf(commandString, "ToString[%s, PageWidth->70]", string);
			break;
		case INTEGERLIST:
			strcpy(commandString, string);
			break;
		case REALLIST:
			strcpy(commandString, string);
			break;
		case REALMATRIX:
			strcpy(commandString, string);
			break;
		default:
			success = NO;
			break;
	}
	
	// If an illegal type was specified, we'll return NO.	
	if(!success)
	{
		free(commandString);
		return NO;
	}	
					
	// Ship the command off to Mathematica.
	MLReturnValue = MLPutFunction(linkPointer, "ToExpression", 1);
	MLPutString(linkPointer, commandString);
    MLEndPacket(linkPointer);
	free(commandString);

	// Get the result of the execution of the command.
	packetType = MLNextPacket(linkPointer);
	switch(packetType)
	{
		case RETURNPKT:
			if(type == DEFINITION)
			{
				// Throw away anything returned.
				MLNewPacket(linkPointer);
				return NO;
			}	

			packetTag = MLGetNext(linkPointer);
			switch(packetTag)
			{
				case MLTKSTR:
					if(MLGetString(linkPointer, &returnedString))
					{
						resultString = (char *) malloc (strlen(returnedString)
								+1);
						strcpy(resultString, returnedString);
						*result = resultString;
						MLDisownString(linkPointer, returnedString);
					}
					else
						success = NO;
					break;
				case MLTKFUNC:
					switch(type)
					{
						case INTEGERLIST:
							if(MLGetIntegerList(linkPointer, &returnedIntList,
									&listCount))
							{
								nonVolatileIntList = (int *)malloc(sizeof(int)
										* listCount);
								memcpy(nonVolatileIntList, returnedIntList,
										sizeof(int) * listCount);
								*result = nonVolatileIntList;
								*numCols = listCount;
								*numRows = 1;
								MLDisownIntegerList(linkPointer,
										returnedIntList, listCount);
							}	
							break;
						case REALLIST:
							if(MLGetRealList(linkPointer, &returnedRealList,
									&listCount))
							{
								nonVolatileRealList = (double*)
										malloc(sizeof(double) * listCount);
								memcpy(nonVolatileRealList, returnedRealList,
										sizeof(double) * listCount);
								*result = nonVolatileRealList;
								*numCols = listCount;
								*numRows = 1;
								MLDisownRealList(linkPointer, returnedRealList,
										listCount);
							}	
							break;
						case REALMATRIX:
							returnedMatrix = (double *)malloc(*numRows * 
									*numCols * sizeof(double));								
							MLGetNext(linkPointer);
							MLGetSymbol(linkPointer, &symbol);
							MLDisownSymbol(linkPointer, symbol);
						
							for(row = 0; row < *numRows; row++)
							{
								MLGetNext(linkPointer);
								MLGetRealList(linkPointer, &returnedRow, 
										&listCount);
									
								memcpy(returnedMatrix+(row * *numCols),
										returnedRow, *numCols *
										sizeof(double)); 	
								MLDisownRealList(linkPointer, returnedRow,
										listCount);
							}				
						
							*result = returnedMatrix;		
							break;	
						default:
							success = NO;	
							break;
					}
					break;
				default:
					success = NO;
					break;	
			}	
			break;
		case MESSAGEPKT:
			// Discard the MLTKSYM and MLTSTR that will be returned.
			MLNewPacket(linkPointer);

			// Discard all packets until we get a RETURNPKT.
			while(MLNextPacket(linkPointer) != RETURNPKT)
				MLNewPacket(linkPointer);
			
			// Discard the return packet.
			MLNewPacket(linkPointer);
			success = NO;
			break;		
		case DISPLAYPKT:
			// We shouldn't ever get one of these.
			MLNewPacket(linkPointer);
			success = NO;
			break;
		default:
			// Discard any mysterious packet.
			MLNewPacket(linkPointer);
			success = NO;
			break;
	}		
	return success;
}	

void error(MLINK linkPointer)
{
	printf("\nerror: %s\n", MLErrorMessage(linkPointer));
	MLClose(linkPointer);
}  

void  readAndPrintExpression(MLINK linkPointer)
{
	char  *s;
	long    len;
	int n, i;
	double r;
	static int indent;
	
	switch (MLGetNext(linkPointer))
	{
		case MLTKSYM:
	   		printf("MLTSYM\n");
			MLGetSymbol(linkPointer, &s);
			printf("%s\n", s);
		break;
		case MLTKSTR:
	        printf("MLTKSTR\n");
			MLGetString( linkPointer, &s);
			printf("%s\n", s);
			break;
		case MLTKINT:
	        printf("MLTKINT\n");
			MLGetInteger(linkPointer, &n);
			printf("%d\n", n);
			break;
		case MLTKREAL:
	        printf("MLTKREAL\n");
			MLGetReal(linkPointer, &r);
			printf("%f\n", r);
			break;
		case MLTKFUNC:
	        printf("MLTKFUNC\n");
			indent += 3;
			printf("\n %*.*s", indent, indent, "");
			if (MLGetArgCount( linkPointer, &len) == 0)
				error( linkPointer);
			else
			{
				readAndPrintExpression(linkPointer);
				printf("[");
				for (i = 1; i <= len; ++i) 
				{
					readAndPrintExpression(linkPointer);
					if (i != len) printf(", ");
				}
				printf("]");
			}
			indent -= 3;
			break;
		case MLTKERROR:
		default:
	        printf("MLTKERROR\n");		  
		error(linkPointer);
	}
}

int readPacket(MLINK linkPointer)
{	
	int packetTag;
	int length = 1;

	switch (packetTag = MLNextPacket(linkPointer))
	{
		case INPUTPKT:
			printf("<INPUTPKT>\n");
			break;
		case TEXTPKT:
			printf("<TEXTPKT>\n");
			break;
		case RETURNPKT:
			printf("<RETURNPKT>\n");
			break;
		case RETURNTEXTPKT:
			printf("<RETURNTEXTPKT>\n");
			break;
		case MESSAGEPKT:
			length = 2;
			printf("<MESSAGEPKT>\n");
			break;
		case CALLPKT:
			printf("<CALLPKT>\n");
			break;
		case INPUTNAMEPKT:
			printf("<INPUTNAMEPKT>\n");
			break;
		case OUTPUTNAMEPKT:
			printf("<OUTPUTNAMEPKT>\n");
			break;   
		case SYNTAXPKT:
			printf("<SYNTAXERROR>\n");
			break;   
		case MENUPKT:
			printf("<MENUPKT>\n");
			break;   
		case DISPLAYPKT:
			printf("<DISPLAYPKT>\n");
			break;   
		case DISPLAYENDPKT:
			printf("<DISPLAYENDPKT>\n");
			break;   
		default:
			printf("unknown packet \"%d\"\n", packetTag);
			return 0;
	}
	while (length --)
		readAndPrintExpression(linkPointer);

	if ((packetTag != INPUTNAMEPKT) && (packetTag != OUTPUTNAMEPKT))
		printf("\n");
		
	return packetTag;
}  
@end
