//
//  EXPCCodeGenerator.m
//  dde
//
//  Created by ashley on 18/06/2008.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#include <stdio.h>

#import "EXPExpression.h"
#import "EXPUnaryOp.h"
#import "EXPBinOp.h"
#import "EXPTernOp.h"
#import "EXPConstant.h"
#import "EXPParameterElement.h"
#import "EXPAuxiliaryElement.h"
#import "EXPStateElement.h"
#import "EXPFunctionElement.h"
#import "EXPAssignmentOp.h"
#import "EXPSymbolReference.h"
#import "EXPTableElement.h"
#import "EXPBlockElement.h"
#import "EXPSwitchElement.h"
#import "EXPCCodeGenerator.h"

NSDictionary *functionNameTable;
NSDictionary *unaryOpNameTable;

@implementation EXPCCodeGenerator

+ (NSDictionary *) functionNameTable
{
	if (functionNameTable==nil) {
		functionNameTable = [NSDictionary dictionaryWithObjectsAndKeys:
				@"zidz", @"zidz",
				@"xidz", @"xidz",
				
				@"min", @"min",
				@"max", @"max",
				@"mean", @"mean",
				@"mod", @"mod",
				
				@"step", @"step",
				@"ramp", @"ramp",
				
				@"urand", @"urand",
				@"nrand", @"nrand",
				@"xrand", @"xrand",
				
				nil];
	}
	
	return functionNameTable;
}

+ (NSDictionary *) unaryOpNameTable
{
	if (unaryOpNameTable==nil) {
		unaryOpNameTable = [NSDictionary dictionaryWithObjectsAndKeys:@"-", @"-",  
				@"", @"+",  
				@"abs", @"abs",
				@"sqrt", @"sqrt",
				@"(int)", @"int",
				@"(double)", @"float", 
				@"exp", @"exp",
				@"log", @"log",
				@"log10", @"log10",
				@"sin", @"sin",
				@"cos", @"cos",
				@"tan", @"tan",
				@"asin", @"asin",
				@"acos", @"acos",
				@"atan", @"atan",
				@"sinh", @"sinh",
				@"cosh", @"cosh",
				@"tanh", @"tanh",
				@"asinh", @"asinh",
				@"acosh", @"acosh",
				@"atanh", @"atanh",
				@"erf", @"erf",

				nil];
	}
	
	return unaryOpNameTable;
}

- (id) init
{
	if ((self = [super init])!=nil) {
		_constantOutputMode = @"normal";
		_outputString = [[NSMutableString alloc] init];
	}
	return self;
}

- (id) outputString
{
	return _outputString;
}

- (void) outString:(NSString *)string
{
	NSMutableString *s = [self outputString];
	[s appendString:string];
}

- (void) indent:(int)level
{
	int i;
	for(i=0; i<level; i++) {
		[self outString:@"    "];
	}
}

- (void) outLine:(NSString *)line /*toFile:(FILE *)f*/ indentationLevel:(int)level
{
/*	int i;
	for(i=0; i<level; i++) {
		fprintf(f, "    ");
	}
	fprintf(f, "%s\n", [line UTF8String]); */
	[self indent:level];
	[self outString:line];
	[self outString:@"\n"];
}

- (void) setConstantOutputMode:(id)constantOutputMode
{
	[constantOutputMode retain];
	[_constantOutputMode release];
	_constantOutputMode =  constantOutputMode;
}

- (id) constantOutputMode
{
	return _constantOutputMode;
}

- (BOOL) generateConstant:(EXPConstant *)constant error:(EXPError *)error
{
	double value = [constant value];
	NSString *string = [[NSString alloc] initWithFormat:@"%g", value];
	[self  outString:string];
	[string release];
	
	return YES;
}

- (BOOL) generateParameter:(EXPParameterElement *)parameter error:(EXPError *)error
{
	int address = [parameter address];
	NSString *mode = [self constantOutputMode];
	if ([mode isEqualToString:@"normal"]) {
		NSString *str = [[NSString alloc] initWithFormat:@"c[%d]", address];
		[self outString:str];
		[str release];
	} else if ([mode isEqualToString:@"out"]) {
		NSString *str = [[NSString alloc] initWithFormat:@"out->c[%d]", address];
		[self outString:str];
		[str release];
	}
	return YES;
}

- (BOOL) generateAuxiliary:(EXPAuxiliaryElement *)auxiliary error:(EXPError *)error
{
	int address = [auxiliary address];
	NSString *string = [[NSString alloc] initWithFormat:@"_auxiliary%d", address];
//	NSLog(@"generateAuxiliary: %@", string);
	[self outString:string];
	return YES;
}

- (BOOL) generateVariable:(EXPStateElement *)variable withArguments:(NSArray *)arguments dimensions:(NSArray *)dimensions withPrimes:(int)primes 
		 error:(EXPError *)error
{
/*	int i = 0;
	for(i=0; i<[assignment primes]; i++) {
		[self outString:@"'"];
	} */
	
	int address = [variable address];
	NSString *string = nil;
	
	int nArguments = [arguments count];
	if (nArguments>0) {
		[self outString:@"pastvaluewithinitvalue("];
		[self outString:[NSString stringWithFormat:@"%d, ", address]];
		EXPExpression *delayTime = [arguments objectAtIndex:0];
		[self generateExpression:delayTime error:error];
		[self outString:@", "];
		EXPExpression *markValue = nil;
		if (nArguments>1) {
			[self generateExpression:markValue error:error];
		} else {
			[self outString:@"0"];
		}
		[self outString:@", "];
		EXPExpression *initValue = nil;
		if (nArguments>2) {
			[self generateExpression:initValue error:error];
		} else {
			[self outString:@"0.0"];
		}
		[self outString:@", pastvalue)"];
	} else {
		NSString *arrayName;
		if (primes==0) {
			arrayName = @"s";
		} else {
			arrayName = @"g";
		}
		string = [[NSString alloc] initWithFormat:@"%@[%d]", arrayName, address];
		[self outString:string];
		[string release];
	}
	
	return YES;
}

- (NSString *) tableVariableForName:(NSString *)name
{
	return [NSString stringWithFormat:@"_table_%@", name];
}

- (BOOL) generateTableFunctionCall:(EXPTableElement *)function withArguments:(NSArray *)arguments dimensions:(NSArray *)dimensions error:(EXPError *)error
{
	NSString *name = [function name];
	
	NSString *str = [[NSString alloc] initWithFormat:@"evaluate(%@, ", [self tableVariableForName:name]];
	[self outString:str];
	[str release];
	if ([arguments count]==1) {
		EXPExpression *exp = [arguments objectAtIndex:0];
		[self generateExpression:exp error:error];
	} else {
		return NO;
	}
	[self outString:@")"];
	
	return YES;
}

- (BOOL) generateFunctionCall:(EXPFunctionElement *)function withArguments:(NSArray *)arguments dimensions:(NSArray *)dimensions error:(EXPError *)error
{
	NSString *name = [function name];
	if ([name isEqualToString:@"t"]) {
		[self outString:@"t"];
	} else if ([name isEqualToString:@"tstart"]) {
	} else if ([name isEqualToString:@"tstop"]) {
	} else {
		NSDictionary *functionNameTable = [[self class] functionNameTable];
		NSString *functionName = [functionNameTable objectForKey:name];
		[self outString:functionName];
		[self outString:@"("];
		int i;
		for(i=0; i<[arguments count]; i++) {
			EXPExpression *exp = [arguments objectAtIndex:0];
			[self generateExpression:exp error:error];
			if (i<[arguments count]-1) {
				[self outString:@", "];
			}
		}
		[self outString:@")"];
	}
	
	return YES;
}

- (BOOL) generateReference:(EXPSymbolReference *)reference error:(EXPError *)error
{
//	NSLog(@"%@ %@", [reference elementType], [reference class]);
	EXPElement *element = [reference element];
	NSString *elementType = [reference elementType];
	if ([elementType isEqualToString:@"parameter"]) {
		[self generateParameter:(EXPParameterElement *)element error:error];
	} else if ([elementType isEqualToString:@"auxiliary"]) {
		[self generateAuxiliary:(EXPAuxiliaryElement *)element error:error];
	} else if ([elementType isEqualToString:@"variable"]) {
		int primes = [reference primes];
		NSArray *arguments = [reference arguments];
		NSArray *dimensions = [reference dimensions];
		[self generateVariable:(EXPStateElement *)element withArguments:arguments dimensions:dimensions withPrimes:primes error:error];
	} else if ([elementType isEqualToString:@"function"]) {
		NSArray *arguments = [reference arguments];
		NSArray *dimensions = [reference dimensions];
		[self generateFunctionCall:(EXPFunctionElement *)element withArguments:arguments dimensions:dimensions error:error];
	} else if ([elementType isEqualToString:@"table"]) {
//		printf("Table...\n");
		NSArray *arguments = [reference arguments];
		NSArray *dimensions = [reference dimensions];
		[self generateTableFunctionCall:(EXPTableElement *)element withArguments:arguments dimensions:dimensions error:error];
	} else {
		return NO;
	}
	return YES;
}

- (BOOL) generateUnaryOp:(EXPUnaryOp *)op error:(EXPError *)error
{
	NSDictionary *unaryOpNameTable = [[self class] unaryOpNameTable];
	NSString *name = [op name];
	NSString *unaryOpName = [unaryOpNameTable objectForKey:name];
	[self outString:unaryOpName];
	[self outString:@"("];
	EXPExpression *exp = [op left];
	[self generateExpression:exp error:error];
	[self outString:@")"];
	return YES;
}

- (BOOL) generateBinaryOp:(EXPBinOp *)op error:(EXPError *)error
{
	NSString *name = [op name];

	[self outString:@"("];
	EXPExpression *exp = [op left];
	[self generateExpression:exp error:error];
	[self outString:@")"];
	
	[self outString:name];

	[self outString:@"("];
	exp = [op right];
	[self generateExpression:exp error:error];
	[self outString:@")"];
	
	return YES;
}

- (BOOL) generateTernaryOp:(EXPTernOp *)op error:(EXPError *)error
{
	EXPExpression *third = [op third];
	[self generateExpression:third error:error];
	[self outString:@"?"];
	
	EXPExpression *left = [op left];
	[self generateExpression:left error:error];
	[self outString:@":"];
	
	EXPExpression *right = [op right];
	[self generateExpression:right error:error];
	
	return YES;
}

- (BOOL) generateTablePoints:(NSArray *)points forVariable:(NSString *)name points:(int)n error:(EXPError *)error
{
//	Check if all elements are constants
	int i;
	BOOL allConstant = YES;
	for(i=0; i<n; i++) {
		EXPExpression *exp = [points objectAtIndex:i];
		NSString *elementType = [exp elementType];
		if (![elementType isEqualToString:@"constant"]) {
			allConstant = NO;
			break;
		}
	}
	
	if (allConstant) {
		[self indent:1];
		[self outString:[NSString stringWithFormat:@"double %@[] = {", name]];
		for(i=0; i<n; i++) {
			EXPExpression *exp = [points objectAtIndex:i];
			[self generateExpression:exp error:error];
			if (i!=(n-1)) {
				[self outString:@", "];
			}
		}
		[self outString:@"};\n"];
	} else {
		[self outLine:[NSString stringWithFormat:@"double %@[%d];", name, n] indentationLevel:1];
		for(i=0; i<n; i++) {
			EXPExpression *exp = [points objectAtIndex:i];
			[self indent:1];
			[self outString:name];
			[self outString:[NSString stringWithFormat:@"[%d] = ", i]];
			[self generateExpression:exp error:error];
			[self outString:@";\n"];
		}
	}
		
	return YES;
}

- (BOOL) generateTableDefinition:(EXPTableElement *)table error:(EXPError *)error
{
	NSArray *dimensions = [table dimensions];
	if ([dimensions count]>0) {
		return NO;
	}
	
//	int i;
	NSString *name = [self tableVariableForName:[table name]];
	NSArray *xPoints = [table xPoints];
	NSArray *yPoints = [table yPoints];
	int nPoints = [yPoints count];
	BOOL isRegular = [table isRegular];
	
	int interpType = [table interpType];
/*	switch ([table interpType]) {
		case STEPWISE:
			interpType = @"stepwise";
			break;
		case LINEAR
			interpType = @"";
			break;
		case SPLINE
			interpType = @"";
			break;
	} */
	[self outLine:[NSString stringWithFormat:@"%@ = makeInterp(%d);", name, interpType] indentationLevel:1];
	
	NSString *yPointsVariable = [NSString stringWithFormat:@"%@_tableYPoints", name];
	if (isRegular) {
		NSString *xMinVariable = [NSString stringWithFormat:@"%@_tableXMin", name];
		[self indent:1];
		[self outString:[NSString stringWithFormat:@"double %@ = ", xMinVariable]];
		[self generateExpression:[table xMin] error:error];
		[self outString:@";\n"];
		
		NSString *xMaxVariable = [NSString stringWithFormat:@"%@_tableXMax", name];
		[self indent:1];
		[self outString:[NSString stringWithFormat:@"double %@ = ", xMaxVariable]];
		[self generateExpression:[table xMax] error:error];
		[self outString:@";\n"];
		
		[self generateTablePoints:yPoints forVariable:yPointsVariable points:nPoints error:error];
		
		NSString *str = [[NSString alloc] initWithFormat:@"(void)setInterpRegular(%@, %@, %@, %@, %d);", name, xMinVariable, xMaxVariable, yPointsVariable,
			nPoints];
		[self outLine:str indentationLevel:1];
	} else {
		NSString *xPointsVariable = [NSString stringWithFormat:@"%@_tableXPoints", name];
		[self generateTablePoints:xPoints forVariable:xPointsVariable points:nPoints error:error];
		[self generateTablePoints:yPoints forVariable:yPointsVariable points:nPoints error:error];
		
		[self outLine:[NSString stringWithFormat:@"(void)setInterpIrregular(%@, %@, %@, %d);", name, xPointsVariable, yPointsVariable, nPoints] 
			indentationLevel:1];
	}
	
	[self outLine:@"" indentationLevel:1];
	
	return YES;
}

- (BOOL) generateExpression:(EXPExpression *)expression error:(EXPError *)error
{
	NSString *elementType = [expression elementType];
//	if ([elementType isEqualToString:@"parameter"]) {
	if ([expression isMemberOfClass:[EXPSymbolReference class]]) {
		EXPSymbolReference *ref = (EXPSymbolReference *)expression;
//		[self generateParameter:[ref element] error:error];
		[self generateReference:ref error:error];
	} else if ([elementType isEqualToString:@"constant"]) {
		EXPConstant *constant = (EXPConstant *)expression;
		[self generateConstant:constant error:error];
	} else if ([elementType isEqualToString:@"unaryop"]) {
		[self generateUnaryOp:(EXPUnaryOp *)expression error:error];
	} else if ([elementType isEqualToString:@"binaryop"]) {
		[self generateBinaryOp:(EXPBinOp *)expression error:error];
	} else if ([elementType isEqualToString:@"ternaryop"]) {
		[self generateTernaryOp:(EXPTernOp *)expression error:error];
//	} else if ([elementType isEqualToString:@"table"]) {
//		printf("Table...\n");
	}
	
	return YES;
}

- (BOOL) generateAssignment:(EXPAssignmentOp *)assignment error:(EXPError *)error
{
	EXPSymbolReference *left = [assignment left];
//	EXPElement *element = [left element];
	if ([left isMemberOfClass:[EXPSymbolReference class]]) {
		[self generateReference:left error:error];
	} else {
		NSLog(@"Whoopsie!");
	}
	
	[self outString:@" = "];
	
	NSArray *rhs = [assignment right];
	EXPExpression *exp = [rhs objectAtIndex:0];
	[self generateExpression:exp error:error];
	
	return YES;
}

- (BOOL) generateStatement:(EXPExpression *)exp error:(EXPError *)error
{
	[self indent:1];
	if ([[exp elementType] isEqualToString:@"assignment"]) {
		EXPAssignmentOp *assignment = (EXPAssignmentOp *)exp;
		[self generateAssignment:assignment error:error];
	}
	[self outString:@";\n"];

	return YES;
}

- (BOOL) generateBlock:(EXPBlockElement *)block error:(EXPError *)error
{
	NSArray *statements = [block statements];

	int i;
	for(i=0; i<[statements count]; i++) {
		EXPExpression *statement = [statements objectAtIndex:i];
		[self generateStatement:statement error:error];
	}

	return YES;
}

- (BOOL) generate:(EXPBlockElement *)block /*solv95:(BOOL)solv95*/ error:(EXPError *)error
{
	return NO;
}

- (BOOL) generate:(EXPBlockElement *)block toFile:(NSString *)filename /*solv95:(BOOL)solv95*/ error:(EXPError *)error
{
	BOOL success = YES;
	
	success = [self generate:block /*solv95:solv95*/ error:error];
	if (success) {
		printf("%s\n", [[self description] UTF8String]);
		[[self outputString] writeToFile:[filename stringByExpandingTildeInPath] atomically:YES encoding:NSUTF8StringEncoding error:nil];
	} else {
	}
	
	return success;
}

- (NSString *) description
{
	return _outputString;
}

- (void) dealloc
{
	[_constantOutputMode release]; 
	[_outputString release];
	[super dealloc];
}

@end