// Driving functions for Sinclair Cambridge Programmable model
//  nib - 2006-03-26 .. 2006-04-07

// Copyright Nigel Bromley 2006. Free use for any non-commercial purpose
//  with attribution.

// Pretty much everything is driven by global state and event-driven
//  keypress routines, which makes it a bit complicated-looking but
//  conceptually simple.

// This is an afternoon's hack, not a properly-structured effort. Although
//  surprisingly I found one odd consequence of the flag logic I used
//  actually reflects what the real thing does!

// The 36-step program, initialised to all "stop" and the program counter
var program = new Array();
for (i=0; i<36; i++) program[i]='0';
var pc=0;

// hash holds the number escape # used in running mode
var hash=0;

// The number input is a bodge, building in the variable x as a value
//  rather than a string in the display. numstate controls the action of
//  the next digit pressed, one of six states
var num=1, deci=2, posex=3, negex=4, gto1=5, gto2=6;
var numstate=num;
// fpos used to control digit position on fractional part, mant and expon
//  build EE format, gto builds arguments for goto in keyboard mode
var fpos, mant, expon, gto;

// Variables controlling the normal calculator mode. x is the displayed
//  register, y the other operand. pend holds one of four pending operators
//  for the 1-level algebraic logic. m is the memory register. f and g
//  flags store the shift state. stale is used to indicate that the previous
//  result is still in x and can be used, but any new numeric input will
//  overwrite. autok is used to decide when the single-operand versions
//  of the arithmetic functions will be used
var add=1, sub=2, mul=3, div=4;
var f_flag=0, g_flag=0, x=0, y=0, m=0, pend=0, stale=0, autok=0;

// Used to control the 1-level brackets. bracket is set while within,
//  bra_pend holds the operator before the open and bra_y the operand
var bracket=0, bra_pend, bra_y;

// The overall state of the calculator
var calc=0, learn=1, running=2;
var calcstate=calc;

// The state of the display, showing register x or the current step
var showx=0, showstep=1;
var dispstate=showx;

// De-bug
var debug, maxsteps;
debug_off();

function debug_off()
{
	debug = 0;
	maxsteps = 5000;
}

function debug_on()
{
	debug = 1;
	maxsteps = 100;
}

// Add one to the program counter rolling back to 0 after 35
function inc_pc()
{
	pc++; if (pc>35) pc = 0;
}

// Produce display contents from current state and display mode and
//  load it into the display window in the form on the document
function display()
{
	if (dispstate == showx)
	{
		if (f_flag)
			document.calculator.display.value = 'F ';
		else if (g_flag)
			document.calculator.display.value = 'G ';
		else
			document.calculator.display.value = '  ';
		document.calculator.display.value += x;
	}
	else
	{
		if (f_flag)
			document.calculator.display.value = 'F ';
		else if (g_flag)
			document.calculator.display.value = 'G ';
		else
			document.calculator.display.value = '  ';
		document.calculator.display.value += program[pc]+"  "+pc;
	}
}

// Complete the pending operation. Called by close bracket, equals or another
//  operator. Does either the complete operation between x and y, or if no 
//  second argument was entered (autok), then on the one operand in x only
function do_pend()
{
	if (autok)
		switch (pend)
		{
		case add:
			x = 2 * x;
			break;
		case sub:
			x = - x;
			break;
		case mul:
			x = x * x;
			break;
		case div:
			x = 1 / x;
			break;
		default:
			break;
		}
	else
		switch (pend)
		{
		case add:
			x = y + x;
			break;
		case sub:
			x = y - x;
			break;
		case mul:
			x = y * x;
			break;
		case div:
			x = y / x;
			break;
		default:
			break;
		}
	autok = 0;
}

// When running, make a goto address out of the two steps following the
//  goto step
function pick_gto()
{
	var j = parseInt(program[pc]);
	inc_pc();
	j = 10*j + parseInt(program[pc]);
	inc_pc();
	if (j>35) j=0;
	return j;
}

// Main function to run a program. Is stopped by running out of steps (safety
//  against loops) or executing a stop instruction, which sets calcstate
//  other than running
function run_program()
{
	var max=maxsteps;

	while (calcstate == running && max>0)
	{
		var next = program[pc];
		if (debug) alert('step='+pc+' action='+next+' x='+x+' y='
							+y+' m='+m);
		inc_pc();
		max--;
		switch (next)
		{
		case '0':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_0()
			break;
		case '1':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_1()
			break;
		case '2':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_2()
			break;
		case '3':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_3()
			break;
		case '4':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_4()
			break;
		case '5':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_5()
			break;
		case '6':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_6()
			break;
		case '7':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_7()
			break;
		case '8':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_8()
			break;
		case '9':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
			}
			else if (!g_flag)
			{
				f_flag=1;
			}
			do_9()
			break;
		case 'A':
			if (hash)
			{
				f_flag=0;
				g_flag=0;
				do_EE();
			}
			else
			{
				f_flag=0;
				g_flag=1;
			}
			break;
		case 'G':
			do_DIV();
			break;
		case '.':
			do_MUL();
			break;
		case '-':
			do_EQU();
			break;
		case 'E':
			do_ADD();
			break;
		case 'F':
			do_MIN();
			break;
		default:
		}
		display();
	}
	calcstate = calc;
}

// Action one of the number keys, taking account of current number input
//  state. Builds either a floating point number in x or a goto address
//  in pc
function number_key(n)
{
	if (stale)
	{
		x = 0;
		stale = 0;
	}
	if (numstate == num)
	{
		x = x*10 + n;
	}
	else if (numstate == deci)
	{
		x = x + n * pos;
		pos /= 10;
	}
	else if (numstate == posex)
	{
		expon = expon*10 + n;
		x = mant * Math.pow(10, expon);
	}
	else if (numstate == negex)
	{
		expon = expon*10 + n;
		x = mant * Math.pow(10, -expon);
	}
	else if (numstate == gto1)
	{
		gto = n;
		numstate = gto2;
	}
	else if (numstate == gto2)
	{
		gto = gto*10 + n;
		numstate = num;
		pc = gto;
		if (pc > 35) pc = 0;
		dispstate=showstep;
	}
	f_flag = 0;
	g_flag = 0;
	autok = 0;

	display();
}

// Put one instruction into the next program step location
function add_step(w)
{
	program[pc] = w;
	inc_pc();
}

// All of the 20 functions from here are driven by pressing one of the 19
//  keys or the power switch

// Base - ./EE/- changes state of number entry. With f - set g-shift
function do_EE()
{
	if (calcstate == learn)
	{
		add_step('A');
		display();
	}
	else if (f_flag)
	{
		f_flag=0;
		g_flag=1;
		display();
	}
	else if (g_flag)
	{
		g_flag=0;
		display();
	}
	else
	{
		switch (numstate)
		{
		case num:
			numstate = deci;
			pos = 1/10;
			break;
		case deci:
			numstate = posex;
			mant = x;
			expon = 0;
			break;
		case posex:
			numstate = negex;
			x = mant * Math.pow(10, -expon);
			display();
			break;
		default:
			break;
		}
	}
}

// Shift key - once for f-shift, twice for g-shift
function do_FG()
{
	if (f_flag)
	{
		f_flag=0;
		g_flag=1;
	}
	else if (g_flag)
	{
		f_flag=0;
		g_flag=0;
	}
	else
	{
		f_flag=1;
		g_flag=0;
	}
	display();
}

// C/CE - normally clears just about everything except program and pc (C).
//  If there is a pending operator, clear that operator and any operand
//  after it, and bring the 1st argument back into x (CE)
function do_CCE()
{
	if (f_flag)
	{
		inc_pc();
		f_flag=0;
		dispstate=showstep;
		display();
	}
	else if (g_flag)
	{
		g_flag=0;
		display();
	}
	else
	{
		if (pend)
		{
			pend = 0;
			autok = 0;
			x = y;
			y = 0;
		}
		else
		{
			x = 0;
			y = 0;
			f_flag = 0;
			g_flag = 0;
			pend = 0;
			autok = 0;
			stale = 1;
			bracket=0;
		}
		dispstate=showx;
		numstate=num;
		calcstate=calc;
		display();
	}
}

// Power switch used as a complete reset, but unlike the calc does not
//  lose the program
function do_power()
{
	x = 0;
	y = 0;
	m = 0;
	f_flag = 0;
	g_flag = 0;
	numstate=num;
	dispstate=showx;
	calcstate=calc;
	bracket=0;
	pc=0;
	pend=0;
	stale=1;
	autok=0;
	hash=0;
	display();
}

// 7 key. Base state enters a 7. With f does sin. With g does arcsin
function do_7()
{
	if (calcstate == learn)
	{
		add_step('7');
		display();
	}
	else if (f_flag)
	{
		x = Math.sin(x);
		f_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		x = Math.asin(x);
		g_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else
		number_key(7);
}

// 8 key. Base state enters a 8. With f does cos. With g does arccos
function do_8()
{
	if (calcstate == learn)
	{
		add_step('8');
		display();
	}
	else if (f_flag)
	{
		x = Math.cos(x);
		f_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		x = Math.acos(x);
		g_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else
		number_key(8);
}

// 9 key. Base state enters a 9. With f does tan. With g does arctan
function do_9()
{
	if (calcstate == learn)
	{
		add_step('9');
		display();
	}
	else if (f_flag)
	{
		x = Math.tan(x);
		f_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		x = Math.atan(x);
		g_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else
		number_key(9);
}

// RUN key. Base calls the program execution function. With f switches to
//  learn mode. Don't want recursion, so error if somehow called while still
//  running
function do_RUN()
{
	if (calcstate == running)
	{
		alert('run in run!');
		calcstate = calc;
	}
	else if (f_flag)
	{
		calcstate=learn;
		f_flag=0;
		numstate=num;
		dispstate=showstep;
		display();
	}
	else if (g_flag)
	{
		g_flag=0;
		display();
	}
	else
	{
		calcstate = running;
		run_program();
		display();
	}
}

// 4 key. Base state enters a 4. With f does natural log. With g does e^x
function do_4()
{
	if (calcstate == learn)
	{
		add_step('4');
		display();
	}
	else if (f_flag)
	{
		x = Math.log(x);
		f_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		x = Math.exp(x);
		g_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else
		number_key(4);
}

// 5 key. Base state enters a 5. With f does recall from store m. With g
//  does exchange with m. See 2 key for oddities of recall function
function do_5()
{
	if (calcstate == learn)
	{
		add_step('5');
		display();
	}
	else if (f_flag)
	{
		x = m;
		f_flag=0;
		stale = 1;
		autok = 0;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		z = x; x = m; m = z;
		g_flag=0;
		stale = 1;
		autok = 0;
		numstate=num;
		display();
	}
	else
		number_key(5);
}

// 6 key. Base state enters a 6. With f does open and close 1-level
//  brackets. With g converts radians to degrees
function do_6()
{
	if (calcstate == learn)
	{
		add_step('6');
		display();
	}
	else if (f_flag)
	{
		if (!bracket)
		{
			bra_y = y;
		 	bra_pend = pend;
			pend = 0;
			bracket=1;
			stale = 1;
		}
		else
		{
			do_pend();
			y = bra_y;
			pend = bra_pend;
			bracket=0;
			stale = 1;
		}
		f_flag=0;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		x = x*180/Math.PI;
		g_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else
		number_key(6);
}

// Division key. Not affected by shifts. Completes any pending binary
//  operator, saves divide as pending operator. Keeps x reg but marks it
//  for overwriting if any number entered following. Sets autok so that
//  if no following number the pending divide will get done as 1-operand
//  version (1/x). When running, clears numeric mode.
function do_DIV()
{
	if (calcstate == learn)
	{
		add_step('G');
	}
	else
	{
		do_pend();
		y = x;
		pend = div;
		stale = 1;
		autok = 1;
		f_flag = 0;
		g_flag = 0;
		numstate=num;
		hash=0;
	}
	display();
}

// 1 key. Base state enters a 1. With f does square root. With g does go
//  if neg, which is available only when running. Goifneg picks jump
//  address from program if x<0, else continues with next step.
function do_1()
{
	if (calcstate == learn)
	{
		add_step('1');
		display();
	}
	else if (f_flag)
	{
		x = Math.sqrt(x);
		f_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		if (calcstate == running)
		{
			if (x<0)
			{
				pc = pick_gto();
			}
			else
			{
				inc_pc();
				inc_pc();
			}
		}
		else
		{
			stale = 1;
			numstate=num;
		}
		g_flag=0;
		display();
	}
	else
		number_key(1);
}

// 2 key. Base state enters a 2. With f does store to m. With g does
//  goto. When running, goto picks its argument from the program, else
//  it changes number state to pick up from keyboard. The operations
//  store, recall and mem exchange clear autok, which affects sequences
//  such as  x  * ( / sto ) which will always return x, as the
//  closing bracket will do 2-operand division x/x between two copies of x
//  instead of the 1-operand version in x * ( / ) which will return 1, as
//  the ( / ) sequence will calculate 1/x.
function do_2()
{
	if (calcstate == learn)
	{
		add_step('2');
		display();
	}
	else if (f_flag)
	{
		m = x;
		f_flag=0;
		stale = 1;
		autok = 0;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		if (calcstate == running)
		{
			pc = pick_gto();
		}
		else
		{
			x = 'goto';
			stale = 1;
			numstate=gto1;
		}
		g_flag=0;
		display();
	}
	else
		number_key(2);
}

// 3 key. Base state enters a 3. With f when running sets the numeric
//  escape to allow numerics in program. With f otherwise converts disp
//  from 5+2 to 8; not needed here as we have a full display. With g
//  converts degrees to radians
function do_3()
{
	if (calcstate == learn)
	{
		add_step('3');
		display();
	}
	else if (f_flag)
	{
		if (calcstate == running)
		{
			hash=1;
		}
		else
		{
			numstate=num;
		}
		f_flag=0;
		display();
	}
	else if (g_flag)
	{
		x = x*Math.PI/180;
		g_flag=0;
		stale = 1;
		numstate=num;
		display();
	}
	else
		number_key(3);
}

// Multiplication key. Not affected by shifts. Completes any pending binary
//  operator, saves multiply as pending operator. Keeps x reg but marks it
//  for overwriting if any number entered following. Sets autok so that
//  if no following number the pending multiplication will get done as
//  1-operand version (x^2). When running, clears numeric mode.
function do_MUL()
{
	if (calcstate == learn)
	{
		add_step('.');
	}
	else
	{
		do_pend();
		y = x;
		pend = mul;
		stale = 1;
		autok = 1;
		f_flag = 0;
		g_flag = 0;
		numstate=num;
		hash=0;
	}
	display();
}

// 0 key. Base state enters a 0. With f when running stops running. With
//  g changes the sign of x
function do_0()
{
	if (calcstate == learn)
	{
		add_step('0');
		display();
	}
	else if (f_flag)
	{
		f_flag=0;
		calcstate=calc;
		dispstate=showx;
		numstate=num;
		display();
	}
	else if (g_flag)
	{
		x = -x;
		g_flag=0;
		numstate=num;
		display();
	}
	else
		number_key(0);
}

// Equals key. Not affected by shifts. Completes any pending binary
//  operator. When running, clears numeric mode.
function do_EQU()
{
	if (calcstate == learn)
	{
		add_step('-');
	}
	else
	{
		do_pend();
		stale = 1;
		f_flag = 0;
		g_flag = 0;
		pend = 0;
		numstate=num;
		hash=0;
	}
	display();
}

// Addition key. Not affected by shifts. Completes any pending binary
//  operator, saves add as pending operator. Keeps x reg but marks it
//  for overwriting if any number entered following. Sets autok so that
//  if no following number the pending addition will get done as 1-operand
//  version (2x). When running, clears numeric mode.
function do_ADD()
{
	if (calcstate == learn)
	{
		add_step('E');
	}
	else
	{
		do_pend();
		y = x;
		pend = add;
		stale = 1;
		autok = 1;
		f_flag = 0;
		g_flag = 0;
		numstate=num;
		hash=0;
	}
	display();
}

// Subtraction key. Not affected by shifts. Completes any pending binary
//  operator, saves subtract as pending operator. Keeps x reg but marks it
//  for overwriting if any number entered following. Sets autok so that
//  if no following number the pending subtraction will get done as 1-operand
//  version (-x). When running, clears numeric mode.
function do_MIN()
{
	if (calcstate == learn)
	{
		add_step('F');
	}
	else
	{
		do_pend();
		y = x;
		pend = sub;
		stale = 1;
		autok = 1;
		f_flag = 0;
		g_flag = 0;
		numstate=num;
		hash=0;
	}
	display();
}
