// ** COPYRIGHT (c) 2004 STEFAN WANER **
// ****************** ALL RIGHTS RESERVED *******************

// *** ERROR HANDLING
window.onerror = myErrorTrap;

// var exit = false; // get out of here
var okToRoll = true;		// preliminary testing results
var doNotReduce = true;	// automatic reduction in integer mode is off by default
var stepName = "";		// for error trap
var tab = unescape( "%09" );	// these are now the appropriate strings;
var cr = unescape( "%0D" );	
var activeX = 1;		// activated cell coords
var activeY = 1;
var prevX = 0;			// previously active cell
varPrevY = 0;
var comma = ",";
var singular = false;
var maxRows = 15;
var maxCols = 15;
var numRows =1;
var numCols = 1;
var cellPosition = 0;		// active cell in the spreadsheet
var PrevCellPosition = 0;	// previously active cell position
var lineSkip = false;		// skip line on tab
var backSteps = 0;		// how many steps you can back up at any given point
var backupPosition = 1;	// cycles 1-5
var maxBackSteps = 5;	// fixed at 5;
var reDoFlag = false;		// cannot redo unless you back up
var firstBackUp = true;	// first backup must do an extra save
var stackSize = 0;		// the size of the row operations stack not yet implemented
var unDoSteps = 0;		// number of successive undos

var operationsStack = new Array();		// the row operations stack not yet implemented
				// stack location = stackSize
				// format of each cell = string  | y , R# , mult ,  +/- ,  R# ,  mutl |
				// or 				Int to Frac Switch
				// y = 1: a single row op  y = 0, part of a chain of row ops
				// as in a pivot op or a complete reduction
				// this cylces back to top after 100 operations
var stackPtr = 0;
var theMatrix = new makeArray2(maxRows,maxCols); 
				// we will only work with submatrix numRows x numCols
var theSavedMatrix = new makeArray2(maxRows,maxCols); 	// to revert to saved matrix
var theFistBackupMatrix = new makeArray2(maxRows,maxCols); 	// saves latest operation
var theBackMatrix = new makeArray3(maxRows,maxCols, maxBackSteps);
var theBackStatus = new Array();
var saveThis = true;


var numSigDigs = 6;					// default accuracy
var theSmallestNumber = 0.000000000001	// everything smaller is set to zero

// old globals below...

var maxDenom = 10000;  // for fraction approximation
var tol = .000000001; // for 10 digit accuracy guaranteed cutoff for fractin approx not yet implemented
var tooBigString = "Too many matrices in your expression," + cr + "or your expression is too complicated." + cr +"Please keep it simple!"

var fractionMode = false;
var integerMode = false;
var okToRoll = true;
var browserName = navigator.appName;
var browserVersion = navigator.appVersion;
if ( (browserName == "Netscape") && (parseInt(browserVersion) >= 3)) browserName = "N";
else if ( (browserName == "Microsoft Internet Explorer") && (parseInt(browserVersion) >= 3) ) browserName = "M";

// ****************** ERROR HANDLER *************
function myErrorTrap(message,url,linenumber) {
alert("I'm sorry, Dave. I can't do that.");
return (true);
} // end of on error

// ******************** MATH UTILITIES ******************
function hcf (a,b) {

if ( (a == 0) && (b == 0) ) return(0); // actually should be infinity
var bigger = Math.abs(a);
var smaller = Math.abs(b);
var x = 0;
var theResult = 1; 
if (a == b) return(bigger);

if (smaller > bigger) {x = bigger; bigger = smaller;  smaller = x}
if (smaller == 0) return(bigger);

var testRatio = roundSigDig(bigger/smaller, 11);
var testRatio2 = 0;
if (testRatio == Math.floor(testRatio) ) return (smaller)
else
	{
	// look for a factor of the smaller, deplete it by that factor and multiply bigger by it
	var found = false;
	var upperlimit = smaller;
	for (var i = upperlimit; i >= 2; i--)
		{
		testRatio = roundSigDig(smaller/i, 10);
		testRatio2 = roundSigDig(bigger/i, 10);

		if  ( (testRatio == Math.floor(testRatio) ) && (testRatio2 == Math.floor(testRatio2) ) )
			{

			smaller = Math.round(smaller/i);
			bigger = Math.round(bigger/i);
			theResult = i* hcf(bigger, smaller)
			return(theResult);
			}
		}
		return(theResult);
		}
alert("error!");
return(-1); // should never get here
} // hcf


function lcm(a,b) {
// lowest common multiple
var bigger = Math.abs(a);
var smaller = Math.abs(b);
var x = 0;
if ( (a == 0) || (b == 0) ) return(1);
if (smaller > bigger) {x = bigger; bigger = smaller;  smaller = x}

var testRatio = roundSigDig(bigger/smaller, 11)
if (testRatio == Math.floor(testRatio) ) return (bigger)
else
	{
	// look for a factor of the smaller, deplete it by that factor and multiply bigger by it
	var found = false;
	for (var i = 2; i <= smaller; i++)
		{
		if (i*i >= smaller) break;
		testRatio = roundSigDig(smaller/i, 11);
		if (testRatio == Math.floor(testRatio) )
			{
			smaller = testRatio;
			bigger = bigger*i;
			return( lcm(bigger, smaller) );
			}
		}
		return(bigger*smaller);
		}
alert("error!");
return(-1); // should never get here
} // lcm

// *** reducing a fraction ***
function reduce(fraction){
with (Math)
	{
	var HCF = hcf(fraction[1], fraction[2]);
	fraction[1] = Math.round(fraction[1]/HCF);
	fraction[2] = Math.round(fraction[2]/HCF);
	} // with math
return(fraction);
} // reduce fraction


function toFracArr(x, maxDenom, tol) {
// identical to toFrac, except this returns an array [1] = numerator;  [2] = denom 
// rather than a string
// tolerance is the largest errror you will tolerate before resorting to 
// expressing the result as the input decimal in fraction form
// suggest no less than 10^-10, since we round all to 15 decimal places.
	var theFrac = new Array();
	theFrac[1] = 0;
	theFrac[2] = 0;
	var p1 = 1;
	var p2 = 0;
	var q1 = 0;	
	var q2 = 1;	
	var u =0;
	var t = 0;
	var flag = true;
	var negflag = false;
	var a = 0;
	var xIn = x; // variable for later
	var n = 0;
	var d = 0;
	var p = 0;
	var q = 0;

	if (x >10000000000) return(theFrac);
while (flag)
	{
	if (x<0) {x = -x; negflag = true; p1 = -p1}
	var intPart = Math.floor(x);
	var decimalPart = roundSigDig((x - intPart),15);

	x = decimalPart;
	a = intPart;
	
	t = a*p1 + p2;
	u = a*q1 + q2;
	if  ( (Math.abs(t) > 10000000000 ) || (u > maxDenom ) ) 
		{
			n = p1;
			d = q1;
			break;
		}

		p = t;
		q = u;
			
//		cout << "cf coeff: " << a << endl; // for debugging
//		cout << p << "/" << q << endl;	// for debugging
		
	if ( x == 0 )
		{
		n = p;
		d = q;
		break;
		}

		p2 = p1;
		p1 = p;
		q2 = q1;
		q1 = q;
		x = 1/x;
	
	} // while ( true );
	
	theFrac[1] = n;
	theFrac[2] = d;
	return(theFrac);

} // toFracArr

function toFrac(x, maxDenom, tol) {
// tolerance is the largest errror you will tolerate before resorting to 
// expressing the result as the input decimal in fraction form
// suggest no less than 10^-10, since we round all to 15 decimal places.
	var theFrac = new Array();
	theFrac[1] = 0;
	theFrac[2] = 0;
	var p1 = 1;
	var p2 = 0;
	var q1 = 0;	
	var q2 = 1;	
	var u =0;
	var t = 0;
	var flag = true;
	var negflag = false;
	var a = 0;
	var xIn = x; // variable for later
	var n = 0;
	var d = 0;
	var p = 0;
	var q = 0;

	if (x >10000000000) return(theFrac);
while (flag)
	{
	if (x<0) {x = -x; negflag = true; p1 = -p1}
	var intPart = Math.floor(x);
	var decimalPart = roundSigDig((x - intPart),15);

	x = decimalPart;
	a = intPart;
	
	t = a*p1 + p2;
	u = a*q1 + q2;
	if  ( (Math.abs(t) > 10000000000 ) || (u > maxDenom ) ) 
		{
			n = p1;
			d = q1;
			break;
		}

		p = t;
		q = u;
			
//		cout << "cf coeff: " << a << endl; // for debugging
//		cout << p << "/" << q << endl;	// for debugging
		
	if ( x == 0 )
		{
		n = p;
		d = q;
		break;
		}

		p2 = p1;
		p1 = p;
		q2 = q1;
		q1 = q;
		x = 1/x;
	
	} // while ( true );
	
	theFrac[1] = n;
	theFrac[2] = d;

	if (theFrac[2] == 1) return (theFrac[1].toString());
	else return (theFrac[1] + "/" + theFrac[2]);

} // toFrac


function lastChar(theString) {
if (theString == "") return(theString);
var len = theString.length;
return theString.charAt(len-1); 
}

function looksLikeANumber(theString) {
// returns true if theString looks like it can be evaluated
var result = true;
var length = theString.length;
if (length == 0) return (false);
var x = ""
var y = "1234567890-+*. /"
var yLength = y.length;
for (var i = 0; i <= length; i++)
	{ 
	x = theString.charAt(i);
		result = false;
		for (var j = 0; j <= yLength; j++) 
			{
			if (x == y.charAt(j)) {result = true; break}
			} // j
	if (result == false) return(false);
	} // i
return(result);
} // looks like a number

function roundSix(theNumber) {
var x = (Math.round(1000000*theNumber))/1000000;
return(x);
}

function shiftRight(theNumber, k) {
	if (k == 0) return (theNumber)
	else
		{
		var k2 = 1;
		var num = k;
		if (num < 0) num = -num;
		for (var i = 1; i <= num; i++)
			{
			k2 = k2*10
			}
		}
	if (k>0) 
		{return(k2*theNumber)}
	else 
		{return(theNumber/k2)}
	}

function roundSigDig(theNumber, numDigits) {
		numDigits = numDigits -1		// too accurate as written
	with (Math)
		{
		if (theNumber == 0) return(0);
		else if(abs(theNumber) < theSmallestNumber) return(0);
// WARNING: ignores numbers less than 10^(-12)
		else
			{
			var k = floor(log(abs(theNumber))/log(10))-numDigits
			var k2 = shiftRight(round(shiftRight(abs(theNumber),-k)),k)
			if (theNumber > 0) return(k2);
			else return(-k2)
			} // end else
		}
	}


function looksLikeANumber(theString) {
// returns true if theString looks like it can be evaluated
var result = true;
var length = theString.length;
var x = ""
var y = "1234567890-+^*./ "
var yLength = y.length;
for (var i = 0; i <= length; i++)
	{ 
	x = theString.charAt(i);
		result = false;
		for (var j = 0; j <= yLength; j++) 
			{
			if (x == y.charAt(j)) {result = true; break}
			} // j
	if (result == false) return(false);
	} // i
return(result);
} // looks like a number

// ************ MAKE INTEGER
// Makes a matrix integer by least common multiples of rows
// returms a matrix of STRINGS if Strings = true else gives integers
// input = a matrix of real floats


function makeInteger(theMatrix, RowNum, ColNum,Strings) {
var rowArray = new makeArray2(ColNum,2);
var outArray = new makeArray2(maxRows,maxCols);
for (var i = 1; i <= RowNum; i++)
	{
	// set up fraction row array
	for (var j = 1; j <= ColNum; j++) 
		{
		for (var k = 1; k <= 2; k++) rowArray[j][k] = toFracArr(theMatrix[i][j],maxDenom, tol)[k];
		} // j
	
	// get the lcm of all the row denominators
	var rowLcm = 1; 
	
	for (j = 1; j <= ColNum; j++) rowLcm = lcm(rowLcm,rowArray[j][2]);
	// now multiply the row by the lcm
	for  (j = 1; j <= ColNum; j++) 
		{
		rowArray[j][1] = rowLcm*rowArray[j][1]/rowArray[j][2];
		// now ignore [2] entry from here on
		}
	// undo information follows
	stackPtr++;
	if(stackPtr == 101) stackPtr = 1;
	operationsStack[stackPtr] = "0,"+ i + "," + (1/rowLcm).toString();
	// starts with 1 to mark first step in a sequence
	
// now get the hcf of the integers in the row
if(!doNotReduce)
	{
	var rowHcf = 0;

	for (j = 1; j <= ColNum; j++) rowHcf = hcf(rowHcf,rowArray[j][1]);
	if (rowHcf != 0)
		{
		// now divide the row by the hcf
		for  (j = 1; j <= ColNum; j++) 
			{
			rowArray[j][1] = rowArray[j][1]/rowHcf;
			}
		} // if row Hcf
	} // if reducing

	// prepare output array
	var x = 0;
	for  (j = 1; j <= ColNum; j++) 
		{ 
		x = rowArray[j][1]
		if (!Strings) outArray[i][j] = Math.round(x);
		else outArray[i][j] = Math.round(x).toString();
	//	else outArray[i][j] = x.toString();
		} // j

	} // i
return(outArray);

} // makeInteger
// *************END MAKE INTEGER ***************

// ***********ROW REDUCE *********************
function rowReduce() {

var theNum;
for (var i = 1; i <= numRows; i++)
	{ 
	var theCol = 0;
	// search for the leading entry in the ith row
	for (var j = 1; j <= numCols; j++)
		{
		theNum = theMatrix[i][j];
		if (Math.abs(theNum) < theSmallestNumber) theMatrix[i][j] = 0;
		else {theCol = j; break}
		} // j
	if (theCol != 0) pivot(theMatrix,numRows,numCols,i,theCol);
	} // i
// Now arrange the pivoted rows by leading entries
// search the columns from left
var theRow = 1;
for (j = 1; j <= numCols; j++)
	{
	for (i = theRow; i <= numRows; i++)
		{
		if( theMatrix[i][j] != 0) 
			{
			if (i == theRow) {theRow++; break;}
			else { swapRows(theMatrix,theRow,i); theRow++; break}
			} // found non-zero
		} // i
	} // j
} // rowReduce

// ***********END ROW REDUCE ***************

// *******************PIVOT **********************
function pivot(InMatrix,rows,cols,theRow,theCol) {
// alert("theRow = " + theRow + "theCol" + theCol);
var thePivot = InMatrix[theRow][theCol];
doNotReduce = false;		// turns on automatic reduction
for (var i = 1; i <= cols; i++)
	{
	InMatrix[theRow][i] = InMatrix[theRow][i]/thePivot;
	} // i
	// undo information follows
	stackPtr++;
	if(stackPtr == 101) stackPtr = 1;
	operationsStack[stackPtr] = "1,"+theRow.toString() + ","+thePivot.toString() 	
	// starts with 1 to mark the first step in a sequence
// now pivot
var theNum = 1;
for (var i = 1; i <= rows; i++)
	{
	if ( (i != theRow) && (InMatrix[i][theCol] != 0) )
		{
		var factr = InMatrix[i][theCol];

		for (var j = 1; j <= cols; j++)
			{ 

			InMatrix[i][j] = InMatrix[i][j] - factr*InMatrix[theRow][j];

			} // j
			// undo information follows
			stackPtr++;
			if(stackPtr == 101) stackPtr = 1;

			operationsStack[stackPtr] = "0," + i + ",1,+," + theRow + "," + factr.toString();
			// starts with 0 to mark subsequent steps in a sequence
		}
	} // i
 

return(InMatrix);
}

// ***************** END PIVOT *********************

// *************DIVIDE ROW BY SELECTION ************
function divideRowbySelection(InMatrix,rows,cols,theRow,theCol) {
// if it is in integer mode and division results in fractions, switches to fraction mode

var theNumber = theMatrix[activeX][activeY];
var integerFlag = true;
var num = 1;
// it should not be zero at this point...
for (var i = 1; i <= cols; i++)
	{
	num= InMatrix[theRow][i]/theNumber;
	InMatrix[theRow][i] = num;
	if ( (integerFlag)&&(Math.round(num) != num) ) integerFlag = false;
	} // i
	// undo information follows
	stackPtr++;
	if(stackPtr == 101) stackPtr = 1;
	operationsStack[stackPtr] = "1," + theRow + "," +theNumber.toString();
	// starts with 1 to mark first step in a sequence

if ( (integerMode) && (!integerFlag)) 
	{
	integerMode = false;
	fractionMode = true;
	document.theSpreadsheet.Mode.options[1].selected = true;
	// undo Information
	stackPtr++;
	if(stackPtr == 101) stackPtr = 1;
	operationsStack[stackPtr] = "Int to Frac Switch"
	// end undo information
	} // if integer mode
return(InMatrix);
} // divide row by selection
// **********END DIVIDE ROW BY SELECTION **********

// ************ ROW OPERATION ********************
function rowOp(theMatrix, theRow, theOtherRow, a, b) {
// replaces theRow by a*theRow + b*theOtherRow & puts it in resultRow
// alert("here a = "+a);
var integerFlag = true;
var num = 1;
for (var j = 1; j <= numCols; j++)
	{
	if (theOtherRow != 0) num = a*theMatrix[theRow][j] + b*theMatrix[theOtherRow][j];
	else num = a*theMatrix[theRow][j];
	theMatrix[theRow][j] = num;
	if ( (integerFlag)&&(Math.round(num) != num) ) integerFlag = false;
	} // j
// undo information follows
stackPtr++;
if(stackPtr == 101) stackPtr = 1;
operationsStack[stackPtr] = "1," + theRow + "," +a.toString() + ",-," + theOtherRow + "," + b.toString();
// starts with 1 to mark first step in a sequence
if ( (integerMode) && (!integerFlag)) 
	{
	integerMode = false;
	fractionMode = true;
	document.theSpreadsheet.Mode.options[1].selected = true;
	// undo Information
	stackPtr++;
	if(stackPtr == 101) stackPtr = 1;
	operationsStack[stackPtr] = "Int to Frac Switch"
	// end undo information
	} // if integer mode
return(theMatrix);
} // rowOp
// ********END ROW OPERATIION ********************

// ***********SWAP ROWS *************************
function swapRows(InMatrix,p,q) {
var rowHold =0;
for(var j = 1; j <= numCols; j++)
	{
	rowHold = InMatrix[p][j];
	InMatrix[p][j] = InMatrix[q][j];
	InMatrix[q][j] = rowHold;
	} // j
// undo Information
stackPtr++;
if(stackPtr == 101) stackPtr = 1;
operationsStack[stackPtr] = "Swap,"+ p + "," + q;
// end undo information
return(InMatrix);
} // end swap rows
// ********END SWAP ROWS ************************



// *************** FORM UTILITIES ******************

// **** sesame *****************
function sesame(url,hsize,vsize){ 
// Default size is 550 x 400
        var tb="toolbar=0,directories=0,status=0,menubar=0"
        tb+=",scrollbars=1,resizable=1,"
    var tbend="width="+hsize+",height="+vsize;
    if(tbend.indexOf("<undefined>")!=-1){tbend="width=550,height=400"}
      tb+=tbend
     	Win_1 = window.open("","win1",tb);
      Win_1 = window.open(url,"win1",tb);
	Win_1.focus();
    }
// ***************************

// ********ACTIVE CELL CHECK*********
function checkIn() {
lineSkip = false
prevX = activeX;
prevY = activeY; 
prevCellPosition = cellPosition;
activeX = checkIn.arguments[0];
activeY = checkIn.arguments[1];
if ( ( browserName == "N") || ( browserName == "M") )
{
cellPosition = (activeX-1)*maxCols + activeY-1;
if (prevCellPosition + 1 == cellPosition) 
	{
	// looks like you tabbed there
	lineSkip = true;
	}
if ( (lineSkip) && (document.theSpreadsheet1[prevCellPosition].value == "" ) && (activeX < maxRows))
	{
//	document.theSpreadsheet1[cellPosition].blur();
	var posn = cellPosition + maxCols - activeY+1; 
// alert(posn);
	document.theSpreadsheet1[posn].focus();
	document.theSpreadsheet1[posn].select();
// alert(posn);

	}

} // if Netscape
return(true);
} // checkIn
// ******END ACTIVE CELL CHeCK ******

// ********TAB-ING HANDLE BLUR *****
function handleBlur() {

return(true);
} // handleBlur
// ********END HANDLE BLUR *********

// *************READ THE MATRIX********************
function readMatrix() {
// reads the current Matrix

if ((!fractionMode) && (!integerMode)) doIt(2);		// rounding information

unDoSteps = 0;		// no steps left to undo
firstBackUp = true;		// have broken a possible chain of backups
var count = 0;
// first detect the size of the active Matrix
var theRowSize = 0;
var theColSize = 0;
	
for (var i = 1; i <= maxRows; i++)
	{
	for (var j = 1; j <= maxCols; j++)
		{
		var theString = stripSpaces(document.theSpreadsheet1[count].value);
		if (theString == "")
			{
			}
		else 
			{
			if (theRowSize<i) theRowSize = i;
			if (theColSize < j) theColSize = j;
			}
		count++;
		} // j
	} // i

numRows = theRowSize; 			// reset globals here
numCols = theColSize; 			// reset globals here
count = 0;
for (i = 1; i <= maxRows; i++)
	{
	for (j = 1; j <= maxCols; j++)
		{
		theString = stripSpaces(document.theSpreadsheet1[count].value);
		if (theString == "")
			{
			theMatrix[i][j] = 0;
			if ( (theRowSize>=i) && (theColSize >= j) ) document.theSpreadsheet1[count].value = "0";
			}
		else
			{
			theMatrix[i][j] = eval(theString);
			}
		// starts numbering at 0
		count++; 
		} // j
	} // i

// save this if they want it
if (saveThis)
	{
	saveThis = false;
	for (i = 1; i <= maxRows; i++)
	{
	for (j = 1; j <= maxCols; j++)
		{
		theSavedMatrix[i][j] = theMatrix[i][j];
		} // j
	} // i
	
	} // if save this


// save the settings
backSteps++;
if (backSteps > maxBackSteps) backSteps = maxBackSteps;
backupPosition++;
if (backupPosition > maxBackSteps) backupPosition = 1;
if (fractionMode) theBackStatus[backupPosition] = "F";
else if (integerMode) theBackStatus[backupPosition] = "I";
else theBackStatus[backupPosition] = "D"
// alert (theBackStatus[backupPosition])
for (i = 1; i <= maxRows; i++)
	{
	for (j = 1; j <= maxCols; j++)
		{
		theBackMatrix[i][j][backupPosition] = theMatrix[i][j];
		} // j
	} // i
// lastly, reset the mode
fractionMode = false;
integerMode = false;
var theMode = document.theSpreadsheet.Mode.selectedIndex;
// alert(document.theSpreadsheet.Mode.selectedIndex);
if (document.theSpreadsheet.Mode.options[theMode].text == "Fraction") fractionMode = true;
else if (document.theSpreadsheet.Mode.options[theMode].text == "Integer") integerMode = true;
// alert("fractionMode = " + fractionMode + "integerMode = " + integerMode);

// *** testing *******
// var str = "x selected = "+activeX + "  y selected = " + activeY + cr;
// document.theSpreadsheet.output.value = "";
// for (var i = 1; i <= numRows; i++)
//	{
//	for (var j = 1; j <= numCols; j++)
//		{
//		 str += theMatrix[i][j] + tab;
//		}
//	str += cr;
//	}
// document.theSpreadsheet.output.value = str;
//  alert ("here");
// *** testing *******


} // readMatrix

 // ******* END READ MATRIX *********

// *******BACKUP ******************
function backUp() {

if(firstBackUp)
	{
	for (var i = 1; i <= numRows; i++)
		{
		for (var j = 1; j <= numCols; j++)
			{
			theFistBackupMatrix[i][j] = theMatrix[i][j];
			} // j
		} // i

// alert("here " + theFistBackupMatrix[2][1]);
	firstBackUp = false;
	}

document.theSpreadsheet.expr.value = ""
if (backSteps == 0) {document.theSpreadsheet.expr.value = "Sorry. You have backed up as far as you can go..."; return(false)}
if (theBackStatus[backupPosition] == "F") {fractionMode = true; integerMode = false; document.theSpreadsheet.Mode.options[1].selected = true;}
else if (theBackStatus[backupPosition] == "I") {fractionMode = false; integerMode = true; document.theSpreadsheet.Mode.options[2].selected = true;}
else {fractionMode = false; integerMode = false; document.theSpreadsheet.Mode.options[0].selected = true;}
for (var i = 1; i <= maxRows; i++)
	{
	for (var j = 1; j <= maxCols; j++)
		{
		theMatrix[i][j] = theBackMatrix[i][j][backupPosition];
		} // j
	} // i
displayMatrix();
backSteps--;
if (backSteps < 0) backSteps = 0;
backupPosition--;
if (backupPosition == 0) backupPosition = maxBackSteps;
unDoSteps++;
reDoFlag = true;
return(false);
} // backup
// ********END BACKUP ************

// ********REDO ******************
function reDo() {
// alert(unDoSteps);
document.theSpreadsheet.expr.value = ""
if (unDoSteps == 0) {document.theSpreadsheet.expr.value = "Sorry. You cannot redo now"; return(true)}
else if (unDoSteps == 1) 
	{
	readMatrix();
	for (var i = 1; i <= numRows; i++)
		{
		for (var j = 1; j <= numCols; j++)
			{
			theMatrix[i][j] = eval(theFistBackupMatrix [i][j]);
			} // j
		} // i
	displayMatrix(); undoSteps = 0; return(true)
	}

unDoSteps--;

backSteps++;
if (backSteps > maxBackSteps) backSteps = maxBackSteps;
backupPosition++;
if (backupPosition > maxBackSteps) backupPosition = 1;
if(reDoFlag)
	{
	backSteps++;
	if (backSteps > maxBackSteps) backSteps = maxBackSteps;
	backupPosition++;
	if (backupPosition > maxBackSteps) backupPosition = 1;
	reDoFlag = false;
	}

if (theBackStatus[backupPosition] == "F") {fractionMode = true; integerMode = false; document.theSpreadsheet.Mode.options[1].selected = true;}
else if (theBackStatus[backupPosition] == "I") {fractionMode = false; integerMode = true; document.theSpreadsheet.Mode.options[2].selected = true;}
else {fractionMode = false; integerMode = false; document.theSpreadsheet.Mode.options[0].selected = true;}
for (var i = 1; i <= maxRows; i++)
	{
	for (var j = 1; j <= maxCols; j++)
		{
		theMatrix[i][j] = theBackMatrix[i][j][backupPosition];
		} // j
	} // i
displayMatrix();


return(false);
} // backup
// ********END REDO ************

// *******REVERT TO SAVED *********
function Revert() {
for (var i = 1; i <= maxRows; i++)
	{
	for (var j = 1; j <= maxCols; j++)
		{
		theMatrix[i][j] = theSavedMatrix[i][j];
		} // j
	} // i
for (var i = 1; i <= 100; i++) operationsStack[i] = "";
stackPtr = 0;
displayMatrix();
return(true);
	
} // end revert
// ******END OF REVERT ***********

// ****** DISPLAY CURRENT MATRIX ****
function displayMatrix() {

var RowNum = numRows;
var ColNum = numCols;
var x = "";  // a string
// alert("about to display a "+ RowNum+ " x " + ColNum + "matrix");

if (integerMode) 
	{
	theMatrix = makeInteger(theMatrix, numRows, numCols, true)
	var count = 0;
	for (var i = 1; i <= maxRows; i++)
		{
		for (var j = 1; j <= maxCols; j++)
			{
			if ( (i <= numRows) &&  (j <= numCols)) document.theSpreadsheet1[count].value = theMatrix[i][j];
			count++; 
			} // j
		} // i

	} // if integer mode
 // else, handle fractions & decimals
else {
	var count = 0;
	for (var i = 1; i <= maxRows; i++)
		{
		for (var j = 1; j <= maxCols; j++) 		
			{
			if ( (i <= numRows) &&  (j <= numCols))
	 			{
// alert("i = "+i + " j = " + j + "table entry = " + theMatrix[i][j]);
			if (fractionMode) x = toFrac (roundSigDig(theMatrix[i][j],15) , maxDenom, tol);  
			else {x = roundSigDig(theMatrix[i][j], numSigDigs).toString()};

// alert("x = "+x);	
 
			document.theSpreadsheet1[count].value = x;
			} // if ok
			count++;
		} // j 
		
	} // i
	} // end else (if not integer mode)

doNotReduce = true;		// turns off automatic reduction
return(0);
}

// ******** END OF DISPLAY ROUTINE ***************



function makeArray3 (X,Y,Z)
	{
	var count;
	this.length = X+1;
	for (var count = 1; count <= X+1; count++)
		// to allow starting at 1
		this[count] = new makeArray2(Y,Z);
	} // makeArray3


function makeArray2 (X,Y)
	{
	var count;
	this.length = X+1;
	for (var count = 0; count <= X+1; count++)
		// to allow starting at 1
		this[count] = new makeArray(Y);
	} // makeArray2

function makeArray (Y)
	{
	var count;
	this.length = Y+1;
	for (var count = 1; count <= Y+1; count++)
		this[count] = 0;
	} // makeArray


function stripSpaces (InString)  {
	OutString="";
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=" ")
			OutString=OutString+TempChar;
		}
	return (OutString);
	}

function stripChar (InString,symbol)  {
	OutString="";
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=symbol)
			OutString=OutString+TempChar;
	}
	return (OutString);
}

function rightString(InString, num)  {
	var OutString=InString.substring (InString.length-num, InString.length);
	return (OutString);
}

function leftString(InString, num)  {
	var OutString=InString.substring (0, num);
	return (OutString);
}

function stripChar(InString,symbol)  {
	var OutString="";
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=symbol)
			OutString=OutString+TempChar;
	}
	return (OutString);
}

function replaceSubstring(InString,oldSubstring,newSubstring)  {
	OutString="";
	var sublength = oldSubstring.length;
	for (Count=0; Count < InString.length; Count++)  {
		TempStr=InString.substring (Count, Count+sublength);
		TempChar=InString.substring (Count, Count+1);
		if (TempStr!= oldSubstring)
			OutString=OutString+TempChar
		else 
			{
			OutString=OutString+ newSubstring;
			Count +=sublength-1
			}

	}
	return (OutString);
}

function doRowOps() {
// <form name = "rowops"> names of cells are "r"
okToRoll = true;
var skipThisRow = false;
readMatrix();
var scratchString = "";
var scratchArr = new Array();
for (var ijk = 1; ijk <= numRows; ijk++)
	{
	skipThisRow = false;
	scratchString = stripSpaces(document.rowops[ijk-1].value); 
	if (scratchString == "") skipThisRow = true;
	if (!skipThisRow){
		scratchArr = parseRowOp(scratchString);
		// the values of this array are: 
		// 0: number of rows involved
		// 1: row to change
		// 2: other row used
		// 3: coeff of row to change
		// 4: coeff of other row
			var ast = scratchArr[3];
			var ist = scratchArr[1];
			var bst = scratchArr[4];
			var jst = scratchArr[2];
			if (!looksLikeANumber(ast) ||  !looksLikeANumber(bst) || !looksLikeANumber(ist) || !looksLikeANumber(jst) ) 
				{
				okToRoll = false;
				document.theSpreadsheet.expr.value = "You must first enter numbers in all four fields."
				}

			if (ijk != ist) {
// alert("stopped");
			document.theSpreadsheet.expr.value = "The first-mentioned row must be the changing row. (See the textbook.) I will do nothing.";
			okToRoll = false;  
			}
			var a = eval(ast);
			var b = eval(bst);
			var i = eval(ist);
			if (jst != "") var j = eval(jst);
			else var j= 0;		// for a single multiple
			if (okToRoll) 
				{
				if  (a == 0)
					{
					okToRoll = false;
					document.theSpreadsheet.expr.value = "You cannot multliply the changing row by zero."
					}
				else if ( (i < 0) || (i > numRows) || (Math.round(i) != i))
					{
					okToRoll = false;
				document.theSpreadsheet.expr.value = "Row "+i+" is not a valid row number."
					}
				else if ( (j < 0) || (j > numRows) || (Math.round(j) != j) || (j == i))
					{
					okToRoll = false;
					document.theSpreadsheet.expr.value = "Row "+j+" is not a valid row number."
					}
				} // end of second batch of tests

			if (okToRoll)
				{
				rowOp(theMatrix,i, j, a, b);
				doNotReduce = true;
				displayMatrix();
				doNotReduce = false;

				} // of okToRoll
			okToRoll = true;	// reset
	} // if not skip This Row
		} // ijk
} // end of doRowOps

function parseRowOp(inString) {
	document.theSpreadsheet.expr.value = "";
	var theResult = new Array();
// alert(inString);
// some preprocessing if necessary here:
	inString = replaceSubstring(inString,"--","+");
	inString = replaceSubstring(inString,"-R","-1R");	
	inString = replaceSubstring(inString,"-r","-1r");	
	inString = stripChar(inString,"*");
	var L = inString.length;
	var outString = "";
	var rowNum1 = 0;	// will be in position 1 of theResult
	var rowNum2 = 0;	// will be in position 2 of theResult
	var coeff1 = 0;		// will be in position 3 of theResult
	var coeff2 = 0;		// will be in position 4 of theResult
	var pieces = 1;		// will be in position 0 of theResult
	var minusFlag = false;
	// need to break the string into two pieces at plus-minus
	var leftPiece = inString;
	var rightPiece = ""; 
	var permissionToBreak = false; // remains so until "r" encountered
	for (var i = 0; i <= L-1; i++) {
		
		var dig = inString.charAt(i);
		if (dig.toLowerCase() == "r") permissionToBreak = true;
		dig = inString.charAt(i);
		if (((dig == "+") || (dig == "-")) && (permissionToBreak)) { 
 			pieces = 2;
			leftPiece = inString.substring(0, i);
			rightPiece = inString.substring(i+1, L);
			if (dig == "-")  minusFlag = true;
			i = L; // exit loop
       	 } // end if
		} // i
	// Now deal with each piece
// alert("HERE; leftPiece is ***" + leftPiece + "***");
// alert("HERE; rightPiece is ***" + rightPiece + "***");


	for (var k = 1; k <= pieces; k++) { 
// alert("k = " + k);
		if (k == 1) inString = leftPiece;
		else inString = rightPiece;
		L = inString.length;
		for (var i = 0; i <= L-1; i++) 
			{
			if (((inString.charAt(i) == "R") || (inString.charAt(i) == "r")) && (k == 1)) 
				{
// alert(leftString(inString,i-1));
				if (i == 0) coeff1 = 1;
				else coeff1 = eval(leftString(inString,i));

//  debug;
// alert("coeff1 =  "+ coeff1);
// end debug
				rowNum1 = eval(rightString(inString, L-i-1));
				
				} // end if

			if (((inString.charAt(i) == "R") || (inString.charAt(i) == "r")) && (k == 2)) 
				{
				if (i == 0) coeff2 = 1;
				else coeff2 = eval(leftString(inString,i));
				rowNum2 = eval(rightString(inString,L-i-1));
				if (minusFlag) coeff2 = -coeff2;
				}
			} // next i
		} // next k


theResult[0] = pieces;
theResult[1] = rowNum1;
theResult[2] = rowNum2;
theResult[3] = coeff1;
theResult[4] = coeff2;
//  debug;
// alert( "***"+ theResult[3] + "***"+ theResult[1] + "***"+ minusFlag +  "***"+ theResult[4] + "***"+ theResult[2] + "***; "+ theResult[0] + " pieces" );
// end debug
return(theResult);


	} // parseRowOp

function doIt(){

	fractionMode = false;
	integerMode = false;
	var theMode = document.theSpreadsheet.Mode.selectedIndex;
	if (document.theSpreadsheet.Mode.options[theMode].text == "Fraction") fractionMode = true;
	else if (document.theSpreadsheet.Mode.options[theMode].text == "Integer") integerMode = true;

	var num = doIt.arguments[0];

	//**********

	// Option 1 Pivot
	if (num == 1)
		{
		
		readMatrix();
	
		if (theMatrix[activeX][activeY] == 0) 
			{
			okToRoll = false;
			document.theSpreadsheet.expr.value = "You cannot pivot on a zero."
			}
		if (okToRoll)
			{

			pivot(theMatrix,numRows,numCols,activeX,activeY);

			displayMatrix();
			document.theSpreadsheet.expr.value = "Done."
			} // of okToRoll
		okToRoll = true;	// reset 
		// readMatrix(); 	// an extra one for TESTING -- not needed
		} // end of this option

	// Option 2 // preliminary checks
	else  if (num == 2)
		{
		okToRoll = true;
		stepName = "Rounding information"
		var accuracydig = document.theSpreadsheet.acc.value;
		
		if ( (accuracydig == "") || (!looksLikeANumber(accuracydig)) ) { document.theSpreadsheet.expr.value = "Enter a value for the accuracy (Rounding) in the range 1-13."; okToRoll = false}
		
		if (okToRoll)
			{ 
			var thenum = eval(accuracydig); 
			if ((thenum < 1) || (thenum > 14)) {document.theSpreadsheet.expr.value = "Accuracy (Rounding) must be in the range 1-13."; okToRoll = false}
			
			else numSigDigs =thenum;

			} // if okToRoll
		} // end of this option
	
	// Option 3 (Erase)
	else  if (num == 3)
		{
		var count = 0;
		document.theSpreadsheet.expr.value = "";
		for (var i = 1; i <= maxRows; i++)
			{
			for (var j = 1; j <= maxCols; j++)
				{
				document.theSpreadsheet1[count].value = "";
				count++;
				}
			}
		document.rowops.reset();
		} // end of this option
	
	// Option 4 Divide by this
	else  if (num == 4)
		{
		readMatrix();
		if (theMatrix[activeX][activeY] == 0) 
			{
			okToRoll = false;
			document.theSpreadsheet.expr.value = "You cannot divide by zero."
			}
		if (okToRoll)
			{
			divideRowbySelection(theMatrix,numRows,numCols,activeX,activeY);
			displayMatrix();
			} // of okToRoll
		okToRoll = true;	// reset 
		
		} // of this option

	//  Option 5 General Row Operation
	else  if (num == 5)
		{
		readMatrix();
		var ast = stripSpaces(document.theSpreadsheet.a.value);
		var ist = stripSpaces(document.theSpreadsheet.i.value);
		var bst = stripSpaces(document.theSpreadsheet.b.value);
		var jst = stripSpaces(document.theSpreadsheet.j.value);
		if (ast == "") ast = "1";
		if (bst == "") bst = "1";
		if (!looksLikeANumber(ast) ||  !looksLikeANumber(bst) || !looksLikeANumber(ist) || !looksLikeANumber(jst) ) 
			{
			okToRoll = false;
			document.theSpreadsheet.expr.value = "You must first enter numbers in all four fields."
			}
		var a = eval(ast);
		var b = eval(bst);
		var i = eval(ist);
		if (jst != "") var j = eval(jst);
		else var j= 0;		// for a single multiple
		if (okToRoll) 
			{
			if  (a == 0)
				{
				okToRoll = false;
				document.theSpreadsheet.expr.value = "You cannot multliply the changing row by zero."
				}
			else if ( (i < 0) || (i > numRows) || (Math.round(i) != i))
				{
				okToRoll = false;
				document.theSpreadsheet.expr.value = "Row "+i+" is not a valid row number."
				}
			else if ( (j < 0) || (j > numRows) || (Math.round(j) != j) || (j == i))
				{
				okToRoll = false;
				document.theSpreadsheet.expr.value = "Row "+j+" is not a valid row number."
				}
			} // end of second batch of tests

		if (okToRoll)
			{
			rowOp(theMatrix,i, j, a, b);
			doNotReduce = true;
			displayMatrix();
			doNotReduce = false;

			} // of okToRoll
		okToRoll = true;	// reset
		}

	// Option 6 Display Operations Stack (for debugging)
	else  if (num == 6)
		{
		document.theSpreadsheet.output.value = "";
		var str = "Pointer is at #" + stackPtr + cr;
		str += "Format: 0/1 , row#,  multiple,  +/-,  row# multiple" + cr;
		for (var i = 1; i <= stackPtr; i++)
			{
			str += operationsStack[i] + cr;
			}
		document.theSpreadsheet.output.value = str;
		} // of this option
			
	// Option 7 Row Swap
	else if (num == 7)
		{
		readMatrix();
		var pst = document.theSpreadsheet.p.value;
		var qst = document.theSpreadsheet.q.value;
		if (!looksLikeANumber(pst) ||  !looksLikeANumber(qst) ) 
			{
			okToRoll = false;
			document.theSpreadsheet.expr.value = "You must first enter row numbers in both fields."
			}
		var p = eval(pst);
		var q = eval(qst);
		if (okToRoll)
			{
			if ( (p < 0) || (p > numRows) || (Math.round(p) != p))
				{
				okToRoll = false;
				document.theSpreadsheet.expr.value = "Row "+p+" is not a valid row number."
				}
			else if ( (q < 0) || (q > numRows) || (Math.round(q) != q))
				{
				okToRoll = false;
				document.theSpreadsheet.expr.value = "Row "+j+" is not a valid row number."
				}
			} // end of this round of tests

		if (okToRoll)
			{
			swapRows(theMatrix,p,q);
			displayMatrix();
			} // of okToRoll
		okToRoll = true;	// reset

		} // end of option 7

		// Option 8 Row Reduce
	else if (num == 8)
		{
		readMatrix();
		rowReduce();
		displayMatrix();
		document.theSpreadsheet.expr.value = "The matrix is reduced."
		} // end of Option 8

} // end of doIt

