//
//  EXPLoader.m
//  dde
//
//  Created by ashley on 20/10/2007.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#ifdef GNUSTEP
#include <math.h>
#endif
#import "EXPLoader.h"
#import "EXPModel.h"
#import "EXPSymbolTable.h"
#import "EXPExpression.h"
#import "EXPConstant.h"
#import "EXPUnaryOp.h"
#import "EXPBinOp.h"
#import "EXPTernOp.h"
#import "EXPParser.h"
#import "EXPAuxiliaryElement.h"
#import "EXPParameterElement.h"
#import "EXPFunctionElement.h"
#import "EXPSymbolReference.h"
#import "EXPError.h"

NSDictionary *unaryOpNames = nil;

@implementation EXPLoader

- (id) initWithString:(NSString *)string withEcho:(BOOL)echo error:(EXPError *)error
{
	self = [super init];
	if (self!=nil) {
		_parser = [[EXPParser alloc] init];
		[_parser setEcho:echo];
		[_parser setString:string];
		[_parser setLoader:self];
		_error = [error retain];
	}
	
	return self;
}

- (void) registerDefaultFunctions:(EXPBlockElement *)model
{
	NSArray *formalArguments0 = [[NSArray alloc] init];
	NSArray *formalArguments1 = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:doubleType], nil];
	NSArray *formalArguments2 = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:doubleType], [NSNumber numberWithInt:doubleType], nil];
	
	EXPSymbolTable *table = [model symbolTable];

	[EXPFunctionElement registerFunction:table name:@"zidz"    variableArgs:NO formalArguments:formalArguments2 returnType:doubleType];
	[EXPFunctionElement registerFunction:table name:@"xidz"    variableArgs:NO formalArguments:formalArguments2 returnType:doubleType];

	[EXPFunctionElement registerFunction:table name:@"min"     variableArgs:YES formalArguments:nil returnType:doubleType];
	[EXPFunctionElement registerFunction:table name:@"max"     variableArgs:YES formalArguments:nil returnType:doubleType];
	[EXPFunctionElement registerFunction:table name:@"mean"    variableArgs:YES formalArguments:nil returnType:doubleType]; 
	[EXPFunctionElement registerFunction:table name:@"mod"     variableArgs:YES formalArguments:formalArguments2 returnType:doubleType]; 

	[EXPFunctionElement registerFunction:table name:@"step"    variableArgs:YES formalArguments:nil returnType:doubleType];
	[EXPFunctionElement registerFunction:table name:@"ramp"    variableArgs:YES formalArguments:nil returnType:doubleType];

	[EXPFunctionElement registerFunction:table name:@"urand"   variableArgs:YES formalArguments:nil returnType:doubleType];
	[EXPFunctionElement registerFunction:table name:@"nrand"   variableArgs:YES formalArguments:nil returnType:doubleType];
	[EXPFunctionElement registerFunction:table name:@"xrand"   variableArgs:YES formalArguments:nil returnType:doubleType];

//	[EXPFunctionElement registerFunction:table name:@"smooth1" opcode:SMOOTH   variableArgs:NO ];
//	[EXPFunctionElement registerFunction:table name:@"smooth3" opcode:SMOOTH3   variableArgs:NO ];
	[formalArguments0 release];
	[formalArguments1 release];
	[formalArguments2 release];

}

- (void) declareBuiltinParameter:(NSString *)name forModel:(EXPBlockElement *)model
{
	EXPParameterElement *element = [[EXPParameterElement alloc] init];
	[element setName:name];
	[element setExpressionType:doubleType];
	[element setModel:self];
	[element setIsLocal:NO];
	EXPSymbolTable *symbolTable = [model symbolTable];
	BOOL success = [symbolTable declareSymbol:element];
	if (success) {
		[model addParameter:element error:nil];
	}
	[element release];
}

- (void) declareBuiltinAuxiliary:(NSString *)name forModel:(EXPBlockElement *)model
{
	EXPAuxiliaryElement *element = [[EXPAuxiliaryElement alloc] init];
	[element setName:name];
	[element setExpressionType:doubleType];
	[element setModel:self];
	[element setIsLocal:NO];
	EXPSymbolTable *symbolTable = [model symbolTable];
	BOOL success = [symbolTable declareSymbol:element];
	if (success) {
		[model addAuxiliary:element error:nil];
	}
	[element release];
}

- (id) error
{
	return _error;
}

- (id) parser
{
	return _parser;
}

- (void) setParser:(id)parser
{
	[parser retain];
	[_parser release];
	_parser= parser;
}

- (BOOL) echo
{
	return _echo;
}

- (void) setEcho:(BOOL)newEcho
{
	[[self parser] setEcho:newEcho];
	_echo = newEcho;
}

- (id) expression8:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	id p;
	BOOL success;

	EXPParser *parser = [self parser];
	
	if ([parser isEndOfFile]) {
		return nil;
	}
		
	if (unaryOpNames==nil) {
		NSDictionary *doubleDouble = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithInt:doubleType], @"argumentType", [NSNumber numberWithInt:doubleType], @"resultType", nil];
		NSDictionary *integerDouble = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithInt:integerType], @"argumentType", [NSNumber numberWithInt:doubleType], @"resultType", nil];
		unaryOpNames = [NSDictionary dictionaryWithObjectsAndKeys:
				doubleDouble, @"abs",
				doubleDouble, @"sqrt",
				integerDouble, @"int",
				doubleDouble, @"exp",
				doubleDouble, @"log",
				doubleDouble, @"log10",
				doubleDouble, @"sin",
				doubleDouble, @"cos",
				doubleDouble, @"tan",
				doubleDouble, @"asin",
				doubleDouble, @"acos",
				doubleDouble, @"atan",
				doubleDouble, @"sinh",
				doubleDouble, @"cosh",
				doubleDouble, @"tanh",
				doubleDouble, @"asinh",
				doubleDouble, @"acosh",
				doubleDouble, @"atanh",
				doubleDouble, @"erf",
				nil
		];
	}

	NSString *symbol = [[NSString alloc] initWithString:[parser symbol]];
	NSDictionary *unaryOpInfo = [unaryOpNames objectForKey:symbol];
	if (unaryOpInfo!=nil) {
		[parser readSymbol];
		[parser checkSymbol:@"("];
		EXPExpression *arg = [self expression:model indices:indices isLeft:isLeft error:error];
		[parser checkSymbol:@")"];
		if ([[arg elementType] isEqualToString:@"constant"]) {
			EXPConstant *constant = (EXPConstant *)arg;
			EXPExpressionType expType;
/*			if ([parser isFloatLiteral]) {
				expType = doubleType;
			} else {
				expType = integerType;
			} */
			expType = doubleType;
			double val = [constant value];
			if ([symbol isEqualToString:@"abs"]) {
				val = fabs(val);
			} else if ([symbol isEqualToString:@"sqrt"]) {
				if (val>=0.0) {
					val = sqrt(val);
				} else {
					[error addError:@"Domain error: sqrt" atLine:[parser lineNumber]];
				}
			} else if ([symbol isEqualToString:@"int"]) {
				val = (int)val;
				expType = integerType;
			} else if ([symbol isEqualToString:@"exp"]) {
				val = exp(val);
			} else if ([symbol isEqualToString:@"log"]) {
				if (val>0.0) {
					val = log(val);
				} else {
					[error addError:@"Domain error: log" atLine:[parser lineNumber]];
				}
			} else if ([symbol isEqualToString:@"log10"]) {
				if (val>0.0) {
					val = log10(val);
				} else {
					[error addError:@"Domain error: log10" atLine:[parser lineNumber]];
				}
			} else if ([symbol isEqualToString:@"sin"]) {
				val = sin(val);
			} else if ([symbol isEqualToString:@"cos"]) {
				val = cos(val);
			} else if ([symbol isEqualToString:@"tan"]) {
				val = tan(val);
			} else if ([symbol isEqualToString:@"asin"]) {
				val = asin(val);
			} else if ([symbol isEqualToString:@"acos"]) {
				val = acos(val);
			} else if ([symbol isEqualToString:@"atan"]) {
				val = atan(val);
			} else if ([symbol isEqualToString:@"sinh"]) {
				val = sinh(val);
			} else if ([symbol isEqualToString:@"cosh"]) {
				val = cosh(val);
			} else if ([symbol isEqualToString:@"tanh"]) {
				val = tanh(val);
			} else if ([symbol isEqualToString:@"asinh"]) {
#ifdef GNUSTEP
				val = log(val + sqrt(1.0 + val*val));
#else
				val = asinh(val);
#endif
			} else if ([symbol isEqualToString:@"acosh"]) {
#ifdef GNUSTEP
				val = log(val + sqrt(val*val - 1.0));
#else
				val = acosh(val);
#endif
			} else if ([symbol isEqualToString:@"atanh"]) {
#ifdef GNUSTEP
				val = 0.5*log((1.0 + val)/(1.0 - val));
#else
				val = atanh(val);
#endif
			} else if ([symbol isEqualToString:@"erf"]) {
#ifdef GNUSTEP
				val = 0.0;
#else
				val = erf(val);
#endif
			} 
			p = constant;
			[p setExpressionType:expType];
			[p setValue:val];
		} else {
			p = [[[EXPUnaryOp alloc] initOp:symbol withLeft:arg] autorelease];
//			EXPExpressionType argumentType = (EXPExpressionType)[[unaryOpInfo objectForKey:@"argumentType"] intValue];
			EXPExpressionType resultType = (EXPExpressionType)[[unaryOpInfo objectForKey:@"resultType"] intValue];
			[p setExpressionType:resultType];
		}

	} else if ([parser isLiteral]) {
		id sym = [parser symbol];
		double val = [sym doubleValue];
		p = [[[EXPConstant alloc] initWithName:nil] autorelease];
		[p setValue:val];
		if ([parser isFloatLiteral]) {
			[p setExpressionType:doubleType];
		} else {
			[p setExpressionType:integerType];
		}
		[parser readSymbol];

	} else if ([[parser symbol] isEqualToString:@"("]) {
		[parser readSymbol];
		p = [self expression:model indices:indices isLeft:isLeft error:error];
		[parser checkSymbol:@")"];

	} else if ([parser isIdentifier]) {
		p = [[[EXPSymbolReference alloc] init] autorelease];
		success = [self setSymbolReference:p forModel:model forAssignment:nil isLeft:isLeft error:error];
		if (!success) {
			p = nil;
		}
		
	} else {
		p = nil;
	}
	
	[symbol release];
	return p;
}

- (id) expression6:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	id p = [self expression8:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}
	
	while ([parser testSymbol:@"^"]) {
		id pp = [self expression8:model indices:indices isLeft:isLeft error:error];
		if (pp==nil) {
			return nil;
		}
		
		EXPExpression *op;
		if ([p isConstant] && [pp isConstant]) {
			op = [[[EXPConstant alloc] init] autorelease];
			double result = pow([p result], [pp result]);
			[op setValue:result];
		} else {
			op = [[[EXPBinOp alloc] initOp:@"^" withLeft:p AndRight:pp] autorelease];
			if (![op isTypeCompatible]) {
				[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
			}
		}
		
		p = op;
	}

	return p;
}

- (id) expression5:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	BOOL unaryMinus = [parser testSymbol:@"-"];
	(void)[parser testSymbol:@"+"];
	
	id p = [self expression6:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}

	BOOL b;
	while ((b = [parser testSymbol:@"*"]) || [parser testSymbol:@"/"]) {
		EXPExpression *op;
		
		id pp = [self expression6:model indices:indices isLeft:isLeft error:error];
		if (pp==nil) {
			return nil;
		}

/*		BOOL constant = [p isConstant] && [pp isConstant];
		if (constant) {
			op = [[[EXPConstant alloc] init] autorelease];
			double result;
			if (b) {
				result = [p result] * [pp result];
			} else {
				double denom = [pp result];
				if (denom==0.0) {
					result = 0.0;
				} else {
					result = [p result] / denom;
				}
			}
			[op setValue:result];
		} else {
			if (b) {
				op = [[[EXPBinOp alloc] initOp:@"*" withLeft:p AndRight:pp] autorelease];
			} else {
				op = [[[EXPBinOp alloc] initOp:@"/" withLeft:p AndRight:pp] autorelease];
			}
			if (![op isTypeCompatible]) {
				[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
			}
		} */
		op = [EXPBinOp multOpWithLeft:p andRight:pp isMultOp:b];
		if (![op isTypeCompatible]) {
			[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
		}
		p = op;
	}
	
	if (unaryMinus) {
		EXPExpression *unop;
		if ([p isConstant]) {
			unop = [[[EXPConstant alloc] init] autorelease];
			[unop setValue:-[p result]];
		} else {
			unop = [[[EXPUnaryOp alloc] initOp:@"-" withLeft:p] autorelease];
			[unop setExpressionType:[p expressionType]];
		}
		p = unop;
	}

	return p;
}

- (id) expression4:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	id p = [self expression5:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}

	BOOL b;
	while ((b = [parser testSymbol:@"+"]) || [parser testSymbol:@"-"]) {
		EXPExpression *op;
		
		id pp = [self expression5:model indices:indices isLeft:isLeft error:error];
		if (pp==nil) {
			return nil;
		}

/*		if ([p isConstant] && [pp isConstant]) {
			op = [[[EXPConstant alloc] init] autorelease];
			double result;
			if (b) {
				result = [p result] + [pp result];
			} else {
				result = [p result] - [pp result];
			}
			[op setValue:result];
		} else {
			if (b) {
				op = [[[EXPBinOp alloc] initOp:@"+" withLeft:p AndRight:pp] autorelease];
			} else {
				op = [[[EXPBinOp alloc] initOp:@"-" withLeft:p AndRight:pp] autorelease];
			}
			if (![op isTypeCompatible]) {
				[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
			}
		} */
		op = [EXPBinOp addOpWithLeft:p andRight:pp isAddOp:b];
		if (![op isTypeCompatible]) {
			[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
		}
		p = op;
	}

	return p;
}

- (id) expression3:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	BOOL unaryNot = [parser testSymbol:@"!"];

	id p = [self expression4:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}
	
	BOOL ltOp = NO, gtOp = NO, gteOp = NO, lteOp = NO, eqOp = NO, neqOp = NO;
	while ((ltOp=[parser testSymbol:@"<"]) || (gtOp=[parser testSymbol:@">"]) || (gteOp=[parser testSymbol:@">="]) || 
		   (lteOp=[parser testSymbol:@"<="]) || (eqOp=[parser testSymbol:@"=="]) || (neqOp=[parser testSymbol:@"!="])) {
		
		id pp = [self expression4:model indices:indices isLeft:isLeft error:error];
		if (pp==nil) {
			return nil;
		}

		EXPExpression *op;
		if ([p isConstant] && [pp isConstant]) {
			op = [[[EXPConstant alloc] init] autorelease];
			double x =[p result];
			double y = [pp result];
			if (ltOp) {
				[op setValue:(x<y)];
			} else if (gtOp) {
				[op setValue:(x>y)];
			} else if (gteOp) {
				[op setValue:(x>=y)];
			} else if (lteOp) {
				[op setValue:(x<=y)];
			} else if (eqOp) {
				[op setValue:(x==y)];
			} else if (neqOp) {
				[op setValue:(x!=y)];
			}
		} else {
			if (ltOp) {
				op = [[EXPBinOp alloc]  initOp:@"<" withLeft:p AndRight:pp];
			} else if (gtOp) {
				op = [[EXPBinOp alloc]  initOp:@">" withLeft:p AndRight:pp];
			} else if (gteOp) {
				op = [[EXPBinOp alloc]  initOp:@">=" withLeft:p AndRight:pp];
			} else if (lteOp) {
				op = [[EXPBinOp alloc]  initOp:@"<=" withLeft:p AndRight:pp];
			} else if (eqOp) {
				op = [[EXPBinOp alloc]  initOp:@"==" withLeft:p AndRight:pp];
			} else if (neqOp) {
				op = [[EXPBinOp alloc]  initOp:@"!=" withLeft:p AndRight:pp];
			}
			if (![op isTypeCompatible]) {
				[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
			}
		}
		[op setExpressionType:integerType];

		p = op;
	}

	if (unaryNot) {
		EXPUnaryOp *unop = [[[EXPUnaryOp alloc]  initOp:@"!" withLeft:p] autorelease];
		p = unop;
	}

	return p;
}

- (id) expression2:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	id p =[self expression3:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}
	
	while ([parser testSymbol:@"and"]) {
		id pp = [self expression4:model indices:indices isLeft:isLeft error:error];
		if (pp==nil) {
			return nil;
		}

		EXPBinOp *op = [[[EXPBinOp alloc]  initOp:@"and" withLeft:p AndRight:pp] autorelease];

		if (![op isTypeCompatible]) {
			[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
		}

		p = op;
	}

	return p;
}

- (id) expression1:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	id p = [self expression2:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}
	
	while ([parser testSymbol:@"or"]) {
		id pp = [self expression2:model indices:indices isLeft:isLeft error:error];
		if (pp==nil) {
			return nil;
		}

		EXPBinOp *op = [[[EXPBinOp alloc]  initOp:@"or" withLeft:p AndRight:pp] autorelease];

		if (![op isTypeCompatible]) {
			[error addError:[NSString stringWithFormat:@"Type incompatibility..."] atLine:[parser lineNumber]];
		}

		p = op;
	}
	
	return p;
}

- (id) expression:(EXPBlockElement *)model indices:(NSArray *)indices isLeft:(BOOL)isLeft error:(EXPError *)error
{
	EXPParser *parser = [self parser];
	
	id p = [self expression1:model indices:indices isLeft:isLeft error:error];
	if (p==nil) {
		return nil;
	}
	
	if ([parser testSymbol:@"?"]) {
		id p1 = [self expression1:model indices:indices isLeft:isLeft error:error];
		if (p1==nil) {
			return nil;
		}
		
		if (![parser testSymbol:@":"]) {
			return nil;
		}
		
		id p2 = [self expression1:model indices:indices isLeft:isLeft error:error];
		if (p2==nil) {
			return nil;
		}
		
		EXPTernOp *op = [[[EXPTernOp alloc]  initOp:@"" withLeft:p1 andRight:p2 andThird:p] autorelease];

		if (![op isTypeCompatible]) {
			[error addError:[NSString stringWithFormat:@"Type incompatibility (TernOp)..."] atLine:[parser lineNumber]];
		}

		p = op;
	}

	return p;
}

- (BOOL) setSymbolReference:(EXPSymbolReference *)element forModel:(EXPBlockElement *)model  forAssignment:(id)assignment 
		isLeft:(BOOL)left error:(EXPError *)error
{
	return NO;
}

- (BOOL) loadModel:(EXPBlockElement *)model
{
	return NO;
}

- (void) dealloc
{
	[_parser release];
	[_error release];
	[super dealloc];
}

@end