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

#import "EXPStackMachine.h"
#import "EXPTableElement.h"

#define M 2
#define N 4
#define MM 1
#define NN 2

#define GLOBALADDR

@implementation EXPStackMachine

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

	memorySize = newMemorySize;
//	storeSize = newStoreSize;
	memory = (unsigned short int *)calloc(memorySize, sizeof(unsigned short int));
//	store = (double *)calloc(storeSize, sizeof(double));
	
	int i;
	for(i=0; i<memorySize; i++) {
		memory[i] = 0xffff;
	}
		
	ptr = 0;
	
	pc = 0;
	gp = 0;
	sp = 0;
	lp = 0;
	
//	NSDictionary *mnemonics = nil;
	_time = 0.0;
	_startTime = 0.0;
	_stopTime = 10.0;	
	return self;
}

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

/*- (double *)store
{
	return store;
} */

- (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) 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;
}

- (BOOL) echo
{
	return echo;
}

- (void) setEcho:(BOOL)newEcho
{
	echo = newEcho;
}

- (void) putOpcode:(unsigned short int)opcode mode:(unsigned short int)mode;
{
	unsigned short int val = opcode + mode;
	if (echo) {
		printf("%04x: %04x\n", ptr, val);
	}
	memory[ptr++] = val;
}

- (void) putUInt:(unsigned int)val
{
	if (echo) {
		printf("%04x: %08x\n", ptr, val);
	}
	*(unsigned int *)(&memory[ptr]) = val;
	ptr += M;
}

- (void) putUShInt:(unsigned short int)val
{
	if (echo) {
		printf("%04x: %04x\n", ptr, val);
	}
	memory[ptr++] = val;
}

- (void) putUInt:(unsigned int)val at:(unsigned int)location
{
	memory[location] = val;
}

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

- (void) runFromPc
{
	BOOL running = YES;
//	double *p;
	unsigned int addr;
	unsigned int disp;
	unsigned short int instruction;
	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;
	unsigned int mode;
	unsigned int type1ins;
	
	id history = [self history];
	double startTime = [self startTime];
	double stopTime = [self stopTime];

	while (running) {
		instruction = memory[pc];
//		printf("%08x: %04x\n", pc, instruction);
		pc += 1;
		
		if (instruction<ABSA) {
			int n, nn;
			if ((instruction & 0x0010)==INTEGER) {
				n = M;
				nn = MM;
			} else {
				n = N;
				nn = NN;
			}
		
			mode = instruction & 0x000f;
			switch (mode) {
				case GLOBAL:
					addr = *(unsigned int *)(&memory[pc]) + gp;
					pc += M;
					break;
				case LOCAL:
					addr = lp + *(unsigned int *)(&memory[pc]);
					pc += M;
					break;
				case STACK:
					addr = sp + *(unsigned int *)(&memory[pc]);
					pc += M;
					break;
				case PCREL:
					addr = pc + *(unsigned int *)(&memory[pc]);
					pc += M;
					break;
				case AUTO:
					addr = sp;
					sp -= n;
					break;
				case AUTODEC:
					sp += n;
					addr = sp;
					break;
				case IMM:
					addr = pc;
					pc += n;
					break;
				case INDXG:
					addr = (acc.i<<nn) + *(unsigned int *)(&memory[pc]) + gp;
					pc += M;
					break;
				case INDXL:
					addr = (acc.i<<nn) + *(unsigned int *)(&memory[pc]) + lp;
					pc += M;
					break;
				case INDXGP:
					addr = (acc.i<<nn) + *(unsigned int *)(&memory[*(unsigned int *)(&memory[pc]) + gp]) + gp;
					pc += M;
					break;
				case INDXLP:
					addr = (acc.i<<nn) + *(unsigned int *)(&memory[*(unsigned int *)(&memory[pc]) + lp]) + gp;
					pc += M;
					break;
				case AUTOIND:
					addr = *(unsigned int *)(&memory[sp]);
					sp += M;
					break;
				case AUTOIDC:
					sp -= M;
					addr = *(unsigned int *)(&memory[sp]);
					break;
			}
			
			type1ins = instruction & 0xfff0;
			switch (type1ins) {
				case PSH + FLTPOINT:
					*(double *)(&memory[sp]) = *(double *)(&memory[addr]);
					sp += N;
					break;
				case PSH + INTEGER:
					*(unsigned int *)(&memory[sp]) = *(unsigned int *)(&memory[addr]);
					sp += M;
					break;
				case PUL + FLTPOINT:
					sp -= N;
					*(double *)(&memory[addr]) = *(double *)(&memory[sp]);
					break;
				case PUL + INTEGER:
					sp -= M;
					*(unsigned int *)(&memory[addr]) = *(unsigned int *)(&memory[sp]);
					break;				
				case LDA + FLTPOINT:
					acc.x = *(double *)(&memory[addr]);
					break;
				case LDA + INTEGER:
					acc.i = *(unsigned int *)(&memory[addr]);
					break;
				case STA + FLTPOINT:
					*(double *)(&memory[addr]) = acc.x;
					break;
				case STA + INTEGER:
					*(unsigned int *)(&memory[addr]) = acc.i;
					break;
				case ADD + FLTPOINT:
					acc.x += *(double *)(&memory[addr]);
					break;
				case ADD + INTEGER:
					acc.i += *(unsigned int *)(&memory[addr]);
					break;
				case SUB + FLTPOINT:
					acc.x -= *(double *)(&memory[addr]);
					break;
				case SUB + INTEGER:
					acc.i -= *(unsigned int *)(&memory[addr]);
					break;
				case MLT + FLTPOINT:
					acc.x *= *(double *)(&memory[addr]);
					break;
				case MLT + INTEGER:
					acc.i *= *(unsigned int *)(&memory[addr]);
					break;
				case DVD + FLTPOINT:
					acc.x /= *(double *)(&memory[addr]);
					break;
				case DVD + INTEGER:
					acc.i /= *(unsigned int *)(&memory[addr]);
					break;
				case PWR + FLTPOINT:
					acc.x = pow(acc.x, *(double *)(&memory[addr]));
					break;
//	To do: Integer power operations.
				case GTH + FLTPOINT:
					acc.i = acc.x > *(double *)(&memory[addr]);
					break;
				case GTH + INTEGER:
					acc.i = acc.i > *(unsigned int *)(&memory[addr]);
					break;
				case GTE + FLTPOINT:
					acc.i = acc.x >= *(double *)(&memory[addr]);
					break;
				case GTE + INTEGER:
					acc.i = acc.i >= *(unsigned int *)(&memory[addr]);
					break;
				case LTH + FLTPOINT:
					acc.i = acc.x < *(double *)(&memory[addr]);
					break;
				case LTH + INTEGER:
					acc.i = acc.i < *(unsigned int *)(&memory[addr]);
					break;
				case LTE + FLTPOINT:
					acc.i = acc.x <= *(double *)(&memory[addr]);
					break;
				case LTE + INTEGER:
					acc.i = acc.i <= *(unsigned int *)(&memory[addr]);
					break;
				case EQL + FLTPOINT:
					acc.i = acc.x == *(double *)(&memory[addr]);
					break;
				case EQL + INTEGER:
					acc.i = acc.i == *(unsigned int *)(&memory[addr]);
					break;
				case NEQ + FLTPOINT:
					acc.i = acc.x != *(double *)(&memory[addr]);
					break;
				case NEQ + INTEGER:
					acc.i = acc.i != *(unsigned int *)(&memory[addr]);
					break;
				case INC + FLTPOINT:
					*(double *)(&memory[addr]) += 1.0;
					break;
				case INC + INTEGER:
					*(unsigned int *)(&memory[addr]) += 1;
					break;
				case DEC + FLTPOINT:
					*(double *)(&memory[addr]) -= 1.0;
					break;
				case DEC + INTEGER:
					*(unsigned int *)(&memory[addr]) -= 1;
					break;
				case NEG + FLTPOINT:
					*(double *)(&memory[addr]) = -*(double *)(&memory[addr]);
					break;
				case NEG + INTEGER:
					*(unsigned int *)(&memory[addr]) = -*(unsigned int *)(&memory[addr]);
					break;
				case CLR + FLTPOINT:
					*(double *)(&memory[addr]) = 0.0;
					break;
				case CLR + INTEGER:
					*(unsigned int *)(&memory[addr]) = 0;
					break;
				case JMP + INTEGER:
					pc = *(unsigned int *)(&memory[addr]);
					break;
				case JSR + INTEGER:
					rp -= M;
					*(unsigned int *)(&memory[rp]) = lp;
					rp -= M;
					*(unsigned int *)(&memory[rp]) = pc;
					lp = sp;
					pc = *(unsigned int *)(&memory[addr]);
					break;
				case LEA + INTEGER:
					acc.i = addr;
					break;
				case LEAS + INTEGER:
					sp = addr;
					break;
				case LEAL + INTEGER:
					lp = addr;
					break;
				case LEAG + INTEGER:
					gp = addr;
					break;
				case PEA + INTEGER:
				case PEA + FLTPOINT:
					*(unsigned int *)(&memory[sp]) = addr;
					sp += M;
					break;
				default:
					printf("\nIllegal opcode: %04x\n", instruction);
					running = NO; 
			}
			continue;
		}
		
		switch (instruction) {

			case PRT + FLTPOINT:
				printf("%g", acc.x);
				break;

			case PRT + INTEGER:
				printf("%d", (int)acc.i);
				break;

			case PCHR + INTEGER + IMM:
				ch = (unichar)memory[pc++];
//				pc += M;
				putchar(ch);
				break;

			case ABSA:
				acc.x = fabs(acc.x);
				break;
			case NEGA:
				acc.x = -acc.x;
				break;
			case SQRTA:
				if (acc.x>=0) {
					acc.x = sqrt(acc.x);
				} else {
					errorCode = GSL_ERANGE;
					running = NO;
				}
				break;
			case INTA:
				acc.i = (int)acc.x;
				break;
			case EXPA:
				acc.x = exp(acc.x);
				break;
			case LNA:
				acc.x = log(acc.x);
				break;
			case LOG10A:
				acc.x = log10(acc.x);
				break;
			case PSHA:
				*(double *)(&memory[sp]) = acc.x;
				sp += N;
				break;
			case PULA:
				sp -= N;
				acc.x = *(double *)(&memory[sp]);
				break;
			case PSHA + INTEGER:
				*(unsigned int *)(&memory[sp]) = acc.i;
				sp += M;
				break;
			case PULA + INTEGER:
				sp -= M;
				acc.i = *(unsigned int *)(&memory[sp]);
				break;

			case SINA:
				acc.x = sin(acc.x);
				break;
			case COSA:
				acc.x = cos(acc.x);
				break;
			case TANA:
				acc.x = tan(acc.x);
				break;
			case ASINA:
				acc.x = asin(acc.x);
				break;
			case ACOSA:
				acc.x = acos(acc.x);
				break;
			case ATANA:
				acc.x = atan(acc.x);
				break;
			case SINHA:
				acc.x = asinh(acc.x);
				break;
			case COSHA:
				acc.x = cosh(acc.x);
				break;
			case TANHA:
				acc.x = tanh(acc.x);
				break;
			case ASINHA:
				acc.x = asinh(acc.x);
				break;
			case ACOSHA:
				acc.x = acosh(acc.x);
				break;
			case ATANHA:
				acc.x = atanh(acc.x);
				break;
			case ERFA:
				acc.x = erf(acc.x);
				break;
			case FLTA:
				acc.x = (double)acc.i;
				break;

			case BRA + LONG:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				pc = disp;
				break;
			case BEQ + LONG:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				if (acc.i==0) {
					pc = disp;
				}
				break;
			case BNE + LONG:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				if (acc.i!=0) {
					pc = disp;
				}
				break;
/*			case BGT:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				if (acc.x>0.0) {
					pc = disp;
				}
				break;
			case BGE:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				if (acc.x>=0.0) {
					pc = disp;
				}
				break;
			case BLT:
				disp = (int)*(unsigned int *)(&memory[pc]);
				pc += M;
				if (acc.x<0.0) {
					pc = disp;
				}
				break;
			case BLE:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				if (acc.x<=0.0) {
					pc = disp;
				}
				break; */
			case BSR + LONG:
				disp = *(unsigned int *)(&memory[pc]);
				pc += M;
				rp -= M;
				*(unsigned int *)(&memory[rp]) = lp;
				rp -= M;
				*(unsigned int *)(&memory[rp]) = pc;
				lp = sp;
				pc = disp;
				break;

			case BRA + SHORT:
				disp = memory[pc++];
//				pc += N;
				pc = disp;
				break;
			case BEQ + SHORT:
				disp = memory[pc++];
//				pc += N;
				if (acc.i==0) {
					pc = disp;
				}
				break;
			case BNE + SHORT:
				disp = memory[pc++];
//				pc += M;
				if (acc.i!=0) {
					pc = disp;
				}
				break;
			case BSR + SHORT:
				disp = memory[pc++];
//				pc += M;
				rp -= M;
				memory[rp] = lp;
				rp -= M;
				memory[rp] = pc;
				lp = sp;
				pc = disp;
				break;

			case DBNC + SHORT:
			case DBNC + LONG:
				switch (instruction & 0x000f) {
				case GLOBAL:
					addr = *(unsigned int *)(&memory[pc]) + gp;
					pc += M;
					break;
				case LOCAL:
					addr = lp + *(unsigned int *)(&memory[pc]);
					pc += M;
					break;
				case STACK:
					addr = sp + *(unsigned int *)(&memory[pc]);
					pc += M;
					break;
				case PCREL:
					addr = pc + *(unsigned int *)(&memory[pc]);
					pc += M;
					break;
				case AUTO:
					addr = sp;
					sp -= N;
					break;
				case IMM:
					addr = pc;
					pc += M;
					break;
				case INDXG:
					addr = (acc.i<<MM) + *(unsigned int *)(&memory[*(unsigned int *)(&memory[pc]) + gp]) + gp;
					pc += M;
					break;
				case INDXL:
					addr = (acc.i<<MM) + *(unsigned int *)(&memory[*(unsigned int *)(&memory[pc]) + lp]) + gp;
					pc += M;
					break;
				}
//				unsigned int *p = (unsigned int *)(&memory[addr]);
//				(*p)--;
				(*(unsigned int *)(&memory[addr]))--;
				if (instruction==(DBNC + SHORT)) {
					disp = memory[pc++];
				} else {
					disp = *(unsigned int *)(&memory[pc]);
					pc += M;
				}
				if ((*(int *)(&memory[addr]))>=0) {
					pc = disp;
				}
				break;

			case RTS:
				break;

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

			case TIME:
				acc.x = _time;
				break;

			case TSTART:
				acc.x = startTime;
				break;

			case TSTOP:
				acc.x = stopTime;
				break;

			case MINN:
				n = *(unsigned int *)(&memory[pc]);
				pc += M;
//				acc.x = store[sp++];
				for(i=1; i<n; i++) {
//					value = store[sp++];
					if (value < acc.x) {
						acc.x = value;
					}
				}
				break;

			case MAXN:
				n = *(unsigned int *)(&memory[pc]);
				pc += M;
//				acc.x = store[sp++];
				for(i=1; i<n; i++) {
//					value = store[sp++];
					if (value > acc.x) {
						acc.x = value;
					}
				}
				break;

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

			case MOD:
				n = *(unsigned int *)(&memory[pc]);
				pc += M;
//				x2 = store[sp++];
//				x1 = store[sp++];
				acc.x = floor(x1/x2);
				break;

			case TABLE:
				addr = *(unsigned int *)(&memory[pc]);
				pc += M;
				n = *(unsigned int *)(&memory[pc]);
				pc += M;
				interpType = memory[pc++];
//				yp = &store[sp];
//				xp = &store[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 = *(unsigned int *)(&memory[pc]);
				pc += M;
				accel = tables[addr]->acc;
				spline = tables[addr]->spline;
				if (acc.x<tables[addr]->xmin) {
					acc.x = tables[addr]->ymin;
				} else if (acc.x>tables[addr]->xmax) {
					acc.x = tables[addr]->ymax;
				} else {
					errorCode = gsl_spline_eval_e(spline, acc.x, accel, (double *)&acc);
					running = (errorCode==0);
				}
				break;

			case ADDGP:
				acc.i += gp;
				break;

			case ADDLP:
				acc.i += lp;
				break;

			case LDGP:
				addr = *(unsigned int *)(&memory[pc]);
				pc += M;
				gp = addr;
				break;

			case LDLP:
				addr = *(unsigned int *)(&memory[pc]);
				pc += M;
				lp = addr;
				break;

			case LDSP:
				addr = *(unsigned int *)(&memory[pc]);
				pc += M;
				sp = addr;
				break;

			case BRK: 
				printf("Break at location %08x\n", pc);
				break;

			case HLT: 
				running = NO; 
				break;

			default:
				printf("\nIllegal opcode: %04x\n", instruction);
				running = NO; 

		}
	}
}

- (unsigned int)ptr
{
	return ptr;
}

- (void) setPtr:(unsigned int)newPtr
{
	ptr = newPtr;
}

- (unsigned int)pc
{
	return pc;
}

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

- (unsigned int)sp
{
	return sp;
}

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

- (unsigned int)gp
{
	return gp;
}

- (void) setGp:(unsigned int)newGp
{
	gp = newGp;
}

- (double)acc_fp
{
	return acc.x;
}

- (void) setAccFP:(double)newAcc
{
	acc.x = newAcc;
}

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

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

	if (tables!=NULL) {
		int i;
		for(i=0; i<[self nTables]; i++) {
			free(tables[i]);
		}
		free(tables);
	}

	[super dealloc];
}

@end
