#import <Foundation/Foundation.h>
#include <math.h>
#include <getopt.h>

#import "DDERungeKutta23.h"
#import "EXPElement.h"
#import "EXPSymbolReference.h"
#import "EXPVirtualMachine.h"
#import "EXPParser.h"
#import "EXPError.h"
#import "EXPModel.h"
#import "EXPDDEModelLoader.h"
#import "EXPXModelLoader.h"
#import "EXPOpenSimLoader.h"
#import "EXPBlockElement.h"
#import "EXPMachineGenerator.h"
#import "EXPCCodeGenerator.h"
#import "EXPDDESolveGenerator.h"
#import "EXPSolv95Generator.h"
#import "PLTMatrix.h"

//	int main (int argc, const char * argv[]) {
int main (int argc, char **argv) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

//	NSProcessInfo *processInfo = [NSProcessInfo processInfo];
//	NSArray *arguments = [processInfo arguments];
//	path = @"/Users/ashley/Projects/Objective-C/DDESolver/MathsProgs/dde/test.dde";
	BOOL echo = NO;
	BOOL convert = NO;
	BOOL debugMode = NO;
	BOOL cgiMode = NO;
	int nMemoryBlocks = 64;
	NSMutableDictionary *definitions = [[NSMutableDictionary alloc] init];
	NSString *outFileName = @"";
//	NSString *outFileType = @"hex";
	
/*	int narg;
	for (narg = 1; narg<[arguments count]; narg++) {
		NSString *arg = [arguments objectAtIndex:narg];
		int arglen = [arg length];
		if ([arg characterAtIndex:0]=='-') {
			if ([arg isEqualToString:@"-"]) {
				break;
			}
			NSRange range = NSMakeRange(2, arglen-2);
			NSString *def = [arg substringWithRange:range];
			char optChar = [arg characterAtIndex:1];
			if (optChar=='v') {
				echo = YES;
			} else if (optChar=='g' || optChar=='d') {
				debugMode = YES;
			} else if (optChar=='F') {
				outFileType = def;
			} else if (optChar=='D') {
				NSArray *comps = [def componentsSeparatedByString:@"="];
				if ([comps count]==2) {
					[definitions setValue:[comps objectAtIndex:1] forKey:[comps objectAtIndex:0]];
				}
			} else if (optChar=='c') {
				if ([def length]>0) {
					outFileName = def;
					convert = YES;
				}
			} else {
			}
		} else {
			path = arg;
		}
	} */
	int index;
	int c;
	
	NSString *outputFormat = [[NSString alloc] initWithString:@"lst"];
	BOOL printHeadings = NO;
	BOOL printAll = NO;
	BOOL debug = NO;
//	NSString *inputType = [[NSString alloc] initWithString:@"dde"];
	NSString *inputType = [[NSString alloc] initWithString:@""];
	NSString *outputType = [[NSString alloc] initWithString:@"ddesolve"];
	NSString *outputName = [[NSString alloc] initWithString:@"out.c"];
	NSString *listfileName = nil;
    opterr = 0;
     
    while ((c = getopt (argc, argv, "cgvf:o:s:D:O:PHF:")) != -1) {
		switch (c) {
			case 'c':
				convert = YES;
				break;
			case 'g':
				debug = YES;
				break;
			case 'v':
				echo = YES;
				break;
			case 'f':
				if (optarg!=NULL) {
					[outputType release];
					outputType = [[NSString alloc] initWithUTF8String:optarg];
				}
				break;
			case 'o':
				if (optarg!=NULL) {
					[outputName release];
					outputName = [[NSString alloc] initWithUTF8String:optarg];
				}
				break;
			case 's':
				if (optarg!=NULL) {
					[inputType release];
					inputType = [[NSString alloc] initWithUTF8String:optarg];
				}
				break;
			case 'D':
				{
					NSString *optionString = [[NSString alloc] initWithUTF8String:optarg];
					NSArray *comps = [optionString componentsSeparatedByString:@"="];
					if ([comps count]==2) {
						[definitions setValue:[comps objectAtIndex:1] forKey:[comps objectAtIndex:0]];
					}
					[optionString release];
				}
				break;
			case 'O':
				if (optarg!=NULL) {
					[listfileName release];
					listfileName = [[NSString alloc] initWithUTF8String:optarg];
				}
				break;
			case 'P':
				printAll = YES;
				break;
			case 'H':
				printHeadings = YES;
				break;
			case 'F':
				if (optarg!=NULL) {
					[outputFormat release];
					outputFormat = [[NSString alloc] initWithUTF8String:optarg];
				}
				break;
			case '?':
				break;
			default:
				printf("Invalid option: %c\n", c);
		}
	}		
	
//	NSLog(@"inputType = %@", inputType);
//	NSLog(@"outputType = %@", outputType);
//	NSLog(@"outputName = %@", outputName);

	if (cgiMode) {
		NSProcessInfo *processInfo = [NSProcessInfo processInfo];
		NSDictionary *environment = [processInfo environment];
		NSString *queryString = [environment objectForKey:@"QUERY_STRING"];
		if (queryString!=nil) {
		}
	}

	EXPError *error = [[EXPError alloc] init];
	EXPModel *model = [[EXPModel alloc] initWithMemory:8096*nMemoryBlocks];
	[model setEcho:echo];
	[model setListFilename:[listfileName stringByExpandingTildeInPath]];
	EXPBlockElement *block = [[EXPBlockElement alloc] initWithEnclosingBlock:nil];
	[block setModel:model];
	[block  setName:@"MAIN"];

	NSDictionary *loaders = [[NSDictionary alloc] initWithObjectsAndKeys:
		[EXPDDEModelLoader class], @"dde",
		[EXPXModelLoader class], @"xmodel",
		[EXPOpenSimLoader class], @"osm",
		nil];

	BOOL success;

    for (index = optind; index < argc; index++) {
		NSString *path = [[NSString stringWithUTF8String:argv[index]] stringByExpandingTildeInPath];
#ifdef GNUSTEP
		NSString *sourceFileContents = [[NSString alloc] initWithContentsOfFile:path];
#else
		NSString *sourceFileContents = [[NSString alloc] initWithContentsOfFile:path encoding:NSASCIIStringEncoding error:nil];
#endif	
		
		if (sourceFileContents==nil) {
			printf("Could not open input file %s.\n", [path UTF8String]);
			exit(-1);
		}
		
		NSString *sourceType = nil;
		if ([inputType length]==0) {
			sourceType = [path pathExtension];
		} else {
			sourceType = inputType;
		}
	
		Class loaderClass = [loaders objectForKey:sourceType];
		if (loaderClass==nil) {
			printf("Could not found loader for model type %s.\n", [sourceType UTF8String]);
			
		} else {
			EXPDDEModelLoader *loader = [[loaderClass alloc] initWithString:sourceFileContents withEcho:echo error:error];
			[sourceFileContents release];

			success = [loader loadModel:block];
			if (!success || ([error numErrors]!=0)) {
				printf("Syntax error in model\n");
				exit(-1);
			}
			
			if (debugMode) {
				[block list];
			} 
			[loader release];
		}
//		[path release];
	}
	[loaders release];
	
	[model setBlock:block];
	
	if (echo) printf("\n");
	
//	EXPVirtualMachine *machine = [[EXPVirtualMachine alloc] initWithMemory:8096*nMemoryBlocks];
	EXPMachineGenerator *generator = [[EXPMachineGenerator alloc] init];
	[generator setEcho:echo];
	success = [generator generate:block machine:model error:error];
	[generator release];

	if(!success || ([error numErrors]!=0)) {
		printf("Model failed to compile.\n");
//		[error reportErrors];
		exit(-1);
	}
	
	NSDictionary *options = [block options];
	NSEnumerator *enumerator = [options keyEnumerator];
	id key;
 
	while ((key = [enumerator nextObject])) {
		id option = [block option:key];
		[model setOption:option forName:key];
	}	

	if (convert) {
/*		if ([outFileType isEqualToString:@"hex"]) {
			[model writeToFile:outFileName];
		} else if ([outFileType isEqualToString:@"ddesolve"]) {
			EXPCCodeGenerator *generator = [[EXPCCodeGenerator alloc] init];
			[generator generate:block toFile:outFileName solv95:NO error:error];
			[generator release];
		} else if ([outFileType isEqualToString:@"solv95"]) {
			EXPCCodeGenerator *generator = [[EXPCCodeGenerator alloc] init];
			[generator generate:block toFile:outFileName solv95:YES error:error];
			[generator release];
		} else {
			printf("Invalid output file type.\n");
		} */
		NSDictionary *generators = [[NSDictionary alloc] initWithObjectsAndKeys:
			[EXPDDESolveGenerator class], @"ddesolve",
			[EXPSolv95Generator class], @"solv95",
			nil];
		Class generatorClass = [generators objectForKey:outputType];
		if (generatorClass!=nil) {
			EXPCCodeGenerator *generator = [[generatorClass alloc] init];
			[generator generate:block toFile:[outFileName stringByExpandingTildeInPath] error:error];
			[generator release];
		} else {
			printf("Could not convert model to file type %s.\n", [outputType UTF8String]);
		}
			
	} else {
		int top = [model ptr];

		[model setGp:top];
		[model setSp:[model dataTop] + top];
		[model setRp:[model memorySize]];
		[model setPrintHeadings:printHeadings];
		[model setPrintAll:printAll];
		[model setOutputFormat:outputFormat];

		[model initConstants];
		[model setConstants:definitions];
	
		int nVariables = [model nVariables];
		double *state = (double *)calloc(nVariables, sizeof(double));
		double *gradients = (double *)calloc(nVariables, sizeof(double));
		[definitions release];
	
		DDERungeKutta23 *solver = [[DDERungeKutta23 alloc] initWithRhs:model];
	
		[model setHistory:solver];
//	int address = 

		int exitCode = [model exitCode];
		if (exitCode==0) {
			success = [solver solve];
			if (!success) {
				printf("Solver returned with errors...\n");
			}
	
			if (debugMode) {
				printf("Steps accepted = %d\n", [solver accepted]);
				printf("Steps rejected = %d\n", [solver rejected]);
			}

			if (success) {
				[model print];
			}
		}

		if (success) {
			[model setPc:[model washupExecutionAddress]];
			[model runFromPc];
		}
		[model setHistory:nil];
		[solver release]; 
		free(state);
		free(gradients);
	}
	
	[outputType release];
	[outputName release];
	[inputType release];
	[listfileName release];
	[outputFormat release];
	
	[block release];
	[error release];
	[model release];
	[pool release];

	if (debugMode) {
		[EXPElement reportElementCount];
		[EXPExpression reportElementCount];
	}
	
    return 0;
}