//
//  EXPStackMachine.m
//  StackMachine
//
//  Created by Ashley on 23/04/2007.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "EXPStackMachine.h"
#import "EXPTableElement.h"
#define N 2

@implementation EXPStackMachine

- (id) initWithMemory:(unsigned int)newMemorySize stack:(unsigned int)newStackSize
{
	self = [super init];
	
	if (self==nil) {
		return nil;
	}

	memorySize = newMemorySize;
	stackSize = newStackSize;
	memory = (unsigned int *)calloc(memorySize, sizeof(unsigned int));
	stack = (double *)calloc(stackSize, sizeof(double));
	
	int i;
	for(i=0; i<memorySize; i++) {
		memory[i] = 0xffff;
	}
		
	constants = nil;
	auxiliaries = nil;
	variables = nil;
	gradients = nil;
		
	pc = 0;
	sp = stackSize;
	
//	NSDictionary *mnemonics = nil;
	_time = 0.0;
	_startTime = 0.0;
	_stopTime = 10.0;	
	return self;
}

- (unsigned int *) memory
{
	return memory;
}

- (int) nConstants
{
	return nConstants;
}

- (void) setNConstants:(int) newNConstants
{
	nConstants = newNConstants;
}

- (int) nAuxiliaries
{
	return nAuxiliaries;
}

- (void) setNAuxiliaries:(int) newNAuxiliaries
{
	nAuxiliaries = newNAuxiliaries;
}

- (int) nVariables
{	
	return nVariables;
}

- (void) setNVariables:(int) newNVariables
{
	nVariables = newNVariables;
}

- (void) setConstants:(double *)newConstants
{
	if (constants!=nil) {
		free(constants);
	}
	constants = newConstants;
}

- (double *) constants
{
	return constants;
}

- (void) setAuxiliaries:(double *)newAuxiliaries
{
	if (auxiliaries!=nil) {
		free(auxiliaries);
	}
	auxiliaries = newAuxiliaries;
}

- (double *) auxiliaries
{
	return auxiliaries;
}

- (void) setVariables:(double *)newVariables
{
/*	if (variables!=nil) {
		free(variables);
	} */
	variables = newVariables;
}

- (double *) variables
{
	return variables;
}

- (void) setGradients:(double *)newGradients
{
/*	if (gradients!=nil) {
		free(gradients);
	} */
	gradients = newGradients;
}

- (double *) gradients
{
	return gradients;
}

- (void) setTables:(tableStructure **)newTables
{
	tables = newTables;
}

- (tableStructure **)tables
{
	return tables;
}

- (void) setNTables:(int) newNTables
{
	nTables = newNTables;
}

- (int) nTables
{
	return nTables;
}

- (void) setHistory:(id <history, NSObject>)history
{
	[history retain];
	[_history release];
	_history = history;
}

- (id) history
{
	return _history;
}

- (void) setTime:(double)time
{
	_time = time;
}

- (void) setStartTime:(double)startTime
{
	_startTime = startTime;
}

- (double) startTime
{
	return _startTime;
}

- (void) setStopTime:(double)stopTime
{
	_stopTime = stopTime;
}

- (double) stopTime
{
	return _stopTime;
}

- (void) putUInt:(unsigned int)val
{
	printf("%04x: %08x\n", pc, val);
	memory[pc++] = val;
}

- (void) putUInt:(unsigned int)val at:(unsigned int)location
{
//	printf("%04x: %08x\n", pc, val);
	memory[location] = val;
}

- (void) putDouble:(double)val
{
	printf("%04x: %g\n", pc, val);
	double *p = (double *)(&memory[pc]);
	*p = val;
	pc += N;
}

- (void) runFromPc
{
	BOOL running = YES;
	double *p;
	unsigned int addr;
	unsigned int disp;
	double delayTime;
	double initialValue;
	int markValue;
	double value;
	double x1, x2;
	char ch;
	int interpType;
	gsl_interp_accel *accel;
	gsl_spline *spline;
	double *xp;
	double *yp;
	int errorCode;
	int n;
	int i;
	
	id history = [self history];
	double startTime = [self startTime];
	double stopTime = [self stopTime];

	sp = stackSize;
	while (running) {
		switch (memory[pc++]) {
			case ADDP:
				addr = memory[pc++];
				acc += constants[addr];
				break;
			case ADDX:
				addr = memory[pc++];
				acc += auxiliaries[addr];
				break;
			case ADDV:
				addr = memory[pc++];
				acc += variables[addr];
				break;
			case ADDL:
				break;
			case ADDS:
				acc += stack[sp++];
				break;
			case ADDI:
				p = (double *)(&memory[pc]);
				acc += *p;
				pc += N;
				break;

			case SUBP:
				addr = memory[pc++];
				acc -= constants[addr];
				break;
			case SUBX:
				addr = memory[pc++];
				acc -= auxiliaries[addr];
				break;
			case SUBV:
				addr = memory[pc++];
				acc -= variables[addr];
				break;
			case SUBL:
				break;
			case SUBS:
				acc -= stack[sp++];
				break;
			case SUBI:
				p = (double *)(&memory[pc]);
				acc -= *p;
				pc += N;
				break;

			case MULP:
				addr = memory[pc++];
				acc *= constants[addr];
				break;
			case MULX:
				addr = memory[pc++];
				acc *= auxiliaries[addr];
				break;
			case MULV:
				addr = memory[pc++];
				acc *= variables[addr];
				break;
			case MULL:
				break;
				
			case MULS:
				acc *= stack[sp++];
				break;
			case MULI:
				p = (double *)(&memory[pc]);
				acc *= *p;
				pc += N;
				break;

			case PWRI:
				p = (double *)(&memory[pc]);
				acc = pow(acc, *p);
				pc += N;
				break;
			
			case DVDP:
				addr = memory[pc++];
				acc /= constants[addr];
				break;
			case DVDX:
				addr = memory[pc++];
				acc /= auxiliaries[addr];
				break;
			case DVDV:
				addr = memory[pc++];
				acc /= variables[addr];
				break;
			case DVDL:
				break;
			case DVDS:
				acc /= stack[sp++];
				break;
			case DVDI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc /= *p;
				break;

			case PWRP:
				addr = memory[pc++];
				acc = pow(acc, constants[addr]);
				break;

			case PWRX:
				addr = memory[pc++];
				acc = pow(acc, auxiliaries[addr]);
				break;

			case PWRV:
				addr = memory[pc++];
				acc = pow(acc, variables[addr]);
				break;

			case PWRL:
				break;

			case PWRS:
				acc = pow(acc, stack[sp++]);
				break;

			case EQUP:
				addr = memory[pc++];
				acc = (acc == constants[addr]);
				break;

			case EQUX:
				addr = memory[pc++];
				acc = (acc == auxiliaries[addr]);
				break;

			case EQUV:
				addr = memory[pc++];
				acc = (acc == variables[addr]);
				break;

			case EQUL:
				break;
				
			case EQUS:
				acc = (acc == stack[sp++]);
				break;

			case EQUI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc = (acc == *p);
				break;

			case NEQP:
				addr = memory[pc++];
				acc = (acc != constants[addr]);
				break;

			case NEQX:
				addr = memory[pc++];
				acc = (acc != auxiliaries[addr]);
				break;

			case NEQV:
				addr = memory[pc++];
				acc = (acc != variables[addr]);
				break;

			case NEQL:
				break;

			case NEQS:
				acc = (acc != stack[sp++]);
				break;

			case NEQI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc = (acc != *p);
				break;

			case GTHP:
				addr = memory[pc++];
				acc = (acc > constants[addr]);
				break;

			case GTHX:
				addr = memory[pc++];
				acc = (acc > auxiliaries[addr]);
				break;

			case GTHV:
				addr = memory[pc++];
				acc = (acc > variables[addr]);
				break;

			case GTHL:
				break;
				
			case GTHS:
				acc = (acc > stack[sp++]);
				break;

			case GTHI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc = (acc > *p);
				break;

			case GTEP:
				addr = memory[pc++];
				acc = (acc >= constants[addr]);
				break;

			case GTEX:
				addr = memory[pc++];
				acc = (acc >= auxiliaries[addr]);
				break;

			case GTEV:
				addr = memory[pc++];
				acc = (acc >= variables[addr]);
				break;

			case GTEL:
				break;
				
			case GTES:
				acc = (acc >= stack[sp++]);
				break;

			case GTEI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc = (acc >= *p);
				break;

			case LTHP:
				addr = memory[pc++];
				acc = (acc < constants[addr]);
				break;

			case LTHX:
				addr = memory[pc++];
				acc = (acc < auxiliaries[addr]);
				break;

			case LTHV:
				addr = memory[pc++];
				acc = (acc < variables[addr]);
				break;

			case LTHL:
				break;
				
			case LTHS:
				acc = (acc < stack[sp++]);
				break;

			case LTHI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc = (acc < *p);
				break;

			case LTEP:
				addr = memory[pc++];
				acc = (acc <= constants[addr]);
				break;

			case LTEX:
				addr = memory[pc++];
				acc = (acc <= auxiliaries[addr]);
				break;

			case LTEV:
				addr = memory[pc++];
				acc = (acc == variables[addr]);
				break;

			case LTEL:
				break;
				
			case LTES:
				acc = (acc <= stack[sp++]);
				break;

			case LTEI:
				p = (double *)(&memory[pc]);
				pc += N;
				acc = (acc <= *p);
				break;
	
			case STRP:
				addr = memory[pc++];
				constants[addr] = acc;
				break;
			case STRX:
				addr = memory[pc++];
				auxiliaries[addr] = acc;
				break;
			case STRV:
				addr = memory[pc++];
				variables[addr] = acc;
				break;
			case STRG:
				addr = memory[pc++];
				gradients[addr] = acc;
				break;

			case PSHP:
				addr = memory[pc++];
				stack[--sp] = constants[addr];
				break;
			case PSHX:
				addr = memory[pc++];
				stack[--sp] = auxiliaries[addr];
				break;
			case PSHV:
				addr = memory[pc++];
				stack[--sp] = variables[addr];
				break;
			case PSHL:
				stack[--sp] = 0.0;
				break;
			case PSHA:
				stack[--sp] = acc;
				break;
			case PSHI:
				p = (double *)(&memory[pc]);
				pc += N;
				stack[--sp] = *p;
				break;

			case PULP:
				addr = memory[pc++];
				pc += N;
				constants[addr] = stack[sp++];
				break;
			case PULX:
				addr = memory[pc++];
				pc += N;
				auxiliaries[addr] = stack[sp++];
				break;
			case PULV:
				addr = memory[pc++];
				pc += N;
				variables[addr] = stack[sp++];
				break;
			case PULL:
				break;
			case PULG:
				addr = memory[pc++];
				pc += N;
				gradients[addr] = stack[sp++];
				break;
//			case PULA:

			case LDAP:
				addr = memory[pc++];
				acc = constants[addr];
				break;
			case LDAX:
				addr = memory[pc++];
				acc = auxiliaries[addr];
				break;
			case LDAV:
				addr = memory[pc++];
				acc = variables[addr];
				break;
			case LDAL:
				break;
			case LDAS:
				break;
			case LDAI: 
				p = (double *)(&memory[pc]);
				pc += N;
				acc = *p;
				break;

			case PRT:
				printf("%g", acc);
				break;

			case PCHR:
				ch = (char)memory[pc++];
				putchar(ch);
				break;

			case ABSA:
				acc = fabs(acc);
				break;
			case NEGA:
				acc = -acc;
				break;
			case SQRTA:
				if (acc>=0) {
					acc = sqrt(acc);
				} else {
					errorCode = GSL_ERANGE;
					running = NO;
				}
				break;
			case INTA:
				acc = (int)acc;
				break;
			case EXPA:
				acc = exp(acc);
				break;
			case LNA:
				acc = log(acc);
				break;
			case LOG10A:
				acc = log10(acc);
				break;
			case SINA:
				acc = sin(acc);
				break;
			case COSA:
				acc = cos(acc);
				break;
			case TANA:
				acc = tan(acc);
				break;
			case ASINA:
				acc = asin(acc);
				break;
			case ACOSA:
				acc = acos(acc);
				break;
			case ATANA:
				acc = atan(acc);
				break;
			case SINHA:
				acc = asinh(acc);
				break;
			case COSHA:
				acc = cosh(acc);
				break;
			case TANHA:
				acc = tanh(acc);
				break;
			case ASINHA:
				acc = asinh(acc);
				break;
			case ACOSHA:
				acc = acosh(acc);
				break;
			case ATANHA:
				acc = atanh(acc);
				break;
			case ERFA:
				acc = erf(acc);
				break;

			case BRA:
				disp = memory[pc++];
				pc = disp;
				break;
			case BEQ:
				disp = memory[pc++];
				if (acc==0.0) {
					pc = disp;
				}
				break;
			case BNE:
				disp = memory[pc++];
				if (acc!=0.0) {
					pc = disp;
				}
				break;
			case BGT:
				disp = memory[pc++];
				if (acc>0.0) {
					pc = disp;
				}
				break;
			case BGE:
				disp = memory[pc++];
				if (acc>=0.0) {
					pc = disp;
				}
				break;
			case BLT:
				disp = (int)memory[pc++];
				if (acc<0.0) {
					pc = disp;
				}
				break;
			case BLE:
				disp = memory[pc++];
				if (acc<=0.0) {
					pc = disp;
				}
				break;
			case BSR:
				disp = memory[pc++];
				break;
			case RTS:
				break;

			case DELAY:
				addr = memory[pc++];
				markValue = (int)stack[sp++];
				initialValue = stack[sp++];
				delayTime = stack[sp++];
				if (delayTime<0) {
					acc = initialValue;
				} else {
//					[history  pastValue:addr time:delayTime mark:markValue];
					acc = [history  pastValue:addr time:delayTime mark:markValue];
				}
//				printf("%04x %12.5f %12.5f %12.5f %4d\n", addr, acc, initialValue, delayTime, markValue);
				break;

			case TIME:
				acc = _time;
				break;

			case TSTART:
				acc = startTime;
				break;

			case TSTOP:
				acc = stopTime;
				break;

			case MINN:
				n = memory[pc++];
				acc = stack[sp++];
				for(i=1; i<n; i++) {
					value = stack[sp++];
					if (value < acc) {
						acc = value;
					}
				}
				break;

			case MAXN:
				n = memory[pc++];
				acc = stack[sp++];
				for(i=1; i<n; i++) {
					value = stack[sp++];
					if (value > acc) {
						acc = value;
					}
				}
				break;

//	Check reference for stable algorithm for computing mean.
			case MEANN:
				n = memory[pc++];
				acc = 0.0;
				for(i=0; i<n; i++) {
					acc += stack[sp++];
				}
				acc = acc/(double)n;
				break;

			case MOD:
				n = memory[pc++];
				x2 = stack[sp++];
				x1 = stack[sp++];
				acc = floor(x1/x2);
				break;

			case TABLE:
				addr = memory[pc++];
				n = memory[pc++];
				interpType = memory[pc++];
				yp = &stack[sp];
				xp = &stack[sp + n];
				sp += 2*n;
				accel = gsl_interp_accel_alloc();
				switch (interpType) {
					case LINEAR: spline = gsl_spline_alloc(gsl_interp_linear, n); break;
					case POLYNOMIAL: spline = gsl_spline_alloc(gsl_interp_polynomial, n); break;
					case CSPLINE: spline = gsl_spline_alloc(gsl_interp_cspline, n); break;
					case CSPLINEPERIODIC: spline = gsl_spline_alloc(gsl_interp_cspline_periodic, n); break;
					case AKIMA: spline = gsl_spline_alloc(gsl_interp_linear, n); break;
					case AKIMAPERIODIC: spline = gsl_spline_alloc(gsl_interp_akima_periodic, n); break;
				}
				gsl_spline_init(spline, xp, yp, n);
				tables[addr]->acc = accel;
				tables[addr]->spline = spline;
				tables[addr]->xmin = xp[0];
				tables[addr]->xmax = xp[n-1];
				tables[addr]->ymin = yp[0];
				tables[addr]->ymax = yp[n-1];
				break;

			case INTERP:
				addr = memory[pc++];
				accel = tables[addr]->acc;
				spline = tables[addr]->spline;
				if (acc<tables[addr]->xmin) {
					acc = tables[addr]->ymin;
				} else if (acc>tables[addr]->xmax) {
					acc = tables[addr]->ymax;
				} else {
					errorCode = gsl_spline_eval_e(spline, acc, accel, &acc);
					running = (errorCode==0);
				}
				break;

			case HLT: 
				running = NO; 
				break;

			default:
				printf("\nIllegal opcode\n");
				running = NO; 

		}
	}
}

- (unsigned int)pc
{
	return pc;
}

- (void) setPc:(unsigned int)newPc
{
	pc = newPc;
}

- (unsigned int)sp
{
	return sp;
}

- (void) setSp:(unsigned int)newSp
{
	sp = newSp;
}

- (double)acc
{
	return acc;
}

- (void) setAcc:(double)newAcc
{
	acc = newAcc;
}

- (void) dump:(unsigned int)start to:(unsigned int)stop
{
	int i;
	for(i=start; i<stop; i++) {
		printf("%04x %08x\n", i, memory[i]);
	}
}

- (void) dealloc
{
//	printf("deallocating stack machine.\n");
	free(memory);
	free(stack);

	if (constants!=nil) {
		free(constants);
	}

	if (auxiliaries!=nil) {
		free(auxiliaries);
	}

	if (tables!=NULL) {
		int i;
		for(i=0; i<[self nTables]; i++) {
			free(tables[i]);
		}
		free(tables);
	}
/*	if (variables!=nil) {
		free(variables);
	}
	if (gradients!=nil) {
		free(gradients);
	} */
//	[mnemonics release];
	[super dealloc];
}

@end
